天天看點

多程序下載下傳小說的爬蟲

之前寫過一篇文章來下載下傳小說,不過速度堪憂,因為大量的時間都浪費在了檔案的寫入上,那麼有沒有辦法優化呢?

文章目錄

      • 優化面臨的問題
      • 解決辦法
      • 圖示
      • 隊列的建立
      • 為不同的程序配置設定不同的隊列
      • 程序的任務
      • 爬蟲的自我修養
      • 完整代碼
      • 如何了解多程序

優化面臨的問題

  • 文章是有序的
  • 檔案的讀寫(一般來說目前檔案正在讀寫時是不允許其它程式來通路該檔案)

解決辦法

文章是有序的

可以使用隊列來進行FIFO操作,這樣能確定有序

檔案的讀寫(一般來說目前檔案正在讀寫時是不允許其它程式來通路該檔案)

如果我們能把小說拆分為兩個甚至更多的部分(本文把文章拆成了兩部分),讓程式并行地存儲不同的部分,那麼就能提高檔案的寫入效率(不同的程序寫入的檔案是不一樣的)

圖示

多程式下載下傳小說的爬蟲

總得來說,就是建立兩個程序,每一個線程配置設定一個

URL隊列

和一個

檔案

,線程會根據

URL隊列

進行網頁的爬取并把結果存到

檔案

裡面

隊列的建立

首先,我們要先得到所有章節的連結,并把連結拆分成為兩個部分

def add_tasks(dict_queue):
    original_url = 'http://www.37zw.net/0/761/'
    original_url_soup = spider(original_url)
    i = 0
    for a in original_url_soup.find(id = "list").find_all('a'):
        url = original_url + str(a.get('href'))
        '''
        把章節數大于700的存入到第二個隊列
        小于700的存入到第二個隊列
        '''
        if i > 700:
            dict_queue[2].put(url)
        else:
            dict_queue[1].put(url)
        i += 1
    return dict_queue

one = multiprocessing.Queue()
two = multiprocessing.Queue()
dict_queue = {1: one, 2:two}
full_queue = add_tasks(dict_queue)
           

為不同的程序配置設定不同的隊列

for i in range(1, 3):
    	'''
    	full_queue[i]代表了不同的隊列
    	'''
        p = multiprocessing.Process(
            target=process_tasks, args=(full_queue[i], i))
        processes.append(p)
        '''
        啟動程序
        '''
        p.start()
    for p in processes:
    	'''
    	等待程序的完成
    	'''
        p.join()
           

程序的任務

每一個線程配置設定一個

URL隊列

和一個

檔案

,線程會根據

URL隊列

進行網頁的爬取并把結果存到

檔案

裡面
def process_tasks(queue, i):
	'''
	對于不同的程序打開不同的檔案
	'''
    f = open('第{}部分.txt'.format(i), 'w', encoding='utf-8')
    while not queue.empty():
    	'''
    	從隊列裡面擷取URL
    	'''
        url = queue.get()
        soup = spider(url)
        '''
        擷取章節名
        '''
        chapter_name = soup.find("div", {"class": "bookname"}).h1.string
        print(chapter_name)
        try:
            f.write('\n' + chapter_name + '\n')
        except:
        	pass
        '''
        擷取章節内容
        '''
        for each in soup.find(id = "content").strings:
        	try:
        	    f.write('%s%s' % (each.replace('\xa0', ''), os.linesep))
        	except:
        		pass
    f.close()
    return True
           

如此頻繁的進行檔案寫入操作,難免會出現一兩個寫入錯誤,我的建議是如果出現錯誤就不要管它,如果我們管了那麼很不幸,其中一個程序就會卡死,我曾經試過這樣寫

try:
            f.write('\n' + chapter_name + '\n')
        except:
        	f.write("章節丢失")
           

這樣寫有個好處就行可以記錄丢失的章節,但是别忘了,我們是怎麼記錄的?

f.write()

寫是檔案操作仍有可能造成檔案的寫入錯誤

最好的方法當然不是直接

pass

掉,而是使用日志等記錄下來,可是對于我們這個程式來說丢失一兩章完全不影響,難不成我們記錄了日志還要去網上複制粘貼到檔案裡面去?

爬蟲的自我修養

曾經的我作為一個懵懂無知的爬蟲小白,上去就是

for

循環一把梭,諸不知這樣會給網站帶來很大的負擔,是以我們的爬蟲程式也需要稍稍休息一下,萬一把網站爬壞了,哪來的小說看呢?

def spider(url):
	time.sleep(0.01)
    req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
    response = urlopen(req)
    '''
    忽略中文編碼
    '''
    html = response.read().decode('gbk', 'ignore')
    soup = BeautifulSoup(html, 'html.parser')
    return soup
           

當然我在測試時,沒有加

time.sleep(0.01)

,不過對于本例來說大部分的時間都用來寫入檔案,通路的話基本上是每秒100次左右,其實還是有點高了

多程式下載下傳小說的爬蟲

完整代碼

from bs4 import BeautifulSoup
from urllib.request import Request, urlopen
import os
import time
import multiprocessing


def spider(url):
	time.sleep(0.01)
    req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
    response = urlopen(req)
    '''
    忽略中文編碼
    '''
    html = response.read().decode('gbk', 'ignore')
    soup = BeautifulSoup(html, 'html.parser')
    return soup

def add_tasks(dict_queue):
    original_url = 'http://www.37zw.net/0/761/'
    original_url_soup = spider(original_url)
    i = 0
    for a in original_url_soup.find(id = "list").find_all('a'):
        url = original_url + str(a.get('href'))
        '''
        把章節數大于700的存入到第二個隊列
        小于700的存入到第二個隊列
        '''
        if i > 700:
            dict_queue[2].put(url)
        else:
            dict_queue[1].put(url)
        i += 1
    return dict_queue

def process_tasks(queue, i):
	'''
	對于不同的程序打開不同的檔案
	'''
    f = open('第{}部分.txt'.format(i), 'w', encoding='utf-8')
    while not queue.empty():
    	'''
    	從隊列裡面擷取URL
    	'''
        url = queue.get()
        soup = spider(url)
        '''
        擷取章節名
        '''
        chapter_name = soup.find("div", {"class": "bookname"}).h1.string
        print(chapter_name)
        try:
            f.write('\n' + chapter_name + '\n')
        except:
        	pass
        '''
        擷取章節内容
        '''
        for each in soup.find(id = "content").strings:
        	try:
        	    f.write('%s%s' % (each.replace('\xa0', ''), os.linesep))
        	except:
        		pass
    f.close()
    return True


def run():
    one = multiprocessing.Queue()
    two = multiprocessing.Queue()
    dict_queue = {1: one, 2:two}
    full_queue = add_tasks(dict_queue)
    processes = []
    start = time.time()
    for i in range(1, 3):
    	'''
    	full_queue[i]代表了不同的隊列
    	'''
        p = multiprocessing.Process(
            target=process_tasks, args=(full_queue[i], i))
        processes.append(p)
        '''
        啟動程序
        '''
        p.start()
    for p in processes:
    	'''
    	等待程序的完成
    	'''
        p.join()
    print(f'Time taken = {time.time() - start:.10f}')

if __name__ == '__main__':
    run()
           

加上注釋也就才94行代碼,寫一個多程序爬蟲是不是很簡單

如何了解多程序

對于本例子來說,我想到了一個絕佳的方式來了解,那就是開兩個指令行來運作兩個單程序版的爬蟲,那麼效果其實是跟這個例子是一樣的

本例子的運作結果

多程式下載下傳小說的爬蟲

可以看到是兩個程序在同時下載下傳

那麼跟我們開兩個指令行來運作單機版的爬蟲,一個讓它下前七百章,一個讓它下後面的章節不是跟本例子的效果是一樣的嗎?(隻是舉例,我并沒實際運作)

多程式下載下傳小說的爬蟲

參考資料:

Python3 multiprocessing 文檔

繼續閱讀