一、前言
當爬取很多頁的内容時候,爬取的單線程顯得尤其慢,是以就在并行中,就可以使用多線程進行爬蟲,可以大大提高效率。當然python的底層編碼是不适合做多線程,因為存在GIL鎖(想要了解這個網上也很多資料),但是對于送出請求多的并行任務,python的多線程還是優于多程序并行的(多程序并行主要應用于計算量大且複雜的場景)。是以今天我們就來使用兩種方式對某個小說網站進行爬取《抗日之肥膽英雄》的章節。該小說網址是https://www.biquge.lol/book/9370/3118042.html。
二、爬蟲資料預處理準備
get_url
函數作用:需要拿到要爬取章節的url,同時也得去發現網站下一頁url是怎麼變化,或者還是異步加載問題。此網站是有規律,對于網站異步加載找url可以看看這個文章:《python網絡爬蟲實戰——利用逆向工程爬取動态網頁》https://blog.csdn.net/zou_gr/article/details/109625855。
clean
函數作用:拿到小說内容的string會有奇奇怪怪的符号,使用此函數去除怪符号,多空格。
get_data
函數作用:使用正規表達式拿到小說内容标題還有一個辨別(index),這個辨別的作用就是為了後面并行爬取的時候,可能會把小說章節順序搞亂,有了這個辨別我就可以重新排名,打回正常順序。
三、串行方法爬取
為了展示并行的使用和強大,得找個對比,有了上面的函數,爬取并行就簡單了:
def main():
page_urls = get_url(num, num1)
page = []
for page_url in page_urls:
try:
title_content = get_data(page_url)
page.append(title_content)
except:
page.append("")
continue
with open(r"抗日之肥膽英雄_串行.txt","w",encoding="utf8") as f:
f.write("\n".join(page))
if __name__ == "__main__":
start = time.time()
num = 3118042
num1 = num + 10
main()
end = time.time()
print("串行總共花費時間:{}".format(end - start))
print("串行爬取小說每章花費時間:{} s/章(2頁)\n".format(((end - start) / (num1 - num))))
輸出結果:

四、threading方法爬取《抗日之肥膽英雄》
page = []
num = 3118042
num1 = num + 20
urls = get_url(num,num1)
print(len(urls))
if __name__ == "__main__":
#error = 0
start = time.time()
threads = []
# 可以調節線程數, 進而控制抓取速度
threadNum = 4
index = [i for i in range(0,len(urls),4)]
for i in index:
t1 = threading.Thread(target=main, args=(urls[i],))
t2 = threading.Thread(target=main, args=(urls[i + 1],))
t3 = threading.Thread(target=main, args=(urls[i + 2],))
t4 = threading.Thread(target=main, args=(urls[i + 3],))
threads.append(t1)
threads.append(t2)
threads.append(t3)
threads.append(t4)
for t in threads:
t.start()
for t in threads: # 確定主線程最後退出, 且各個線程間沒有阻塞
t.join()
end = time.time()
print("threading開啟4核并行花費時間:{}\n".format(end - start))
print("threading開啟4核并行爬取小說每章花費時間:{} s/章(2頁)\n".format(((end - start) / (num1 - num))))
輸出結果:
五、concurrent方法爬取《抗日之肥膽英雄》
def _add_content(url): #拿到get_url傳回的小說内容和标題,放在page這個清單
page = []
try:
#num = page_url.replace("https://www.biquge.lol/book/9370/","").replace(".html","").replace("_",".")
title_content = get_data(url)
page.append(title_content)
print('正在下載下傳:' + url)
except:
page.append("")
print('下載下傳失敗:' + url)
return page
if __name__ == "__main__":
num = 3118042
num1 = num + 10
start = time.time()
urls = get_url(num, num1)
executor = ThreadPoolExecutor(max_workers=20) #20個線程池
con = executor.map(_add_content, urls)
con_list = list(con)
cont_list_= [i[0] for i in con_list]
con_str = "\n".join(cont_list_)
with open(r"抗日之肥膽英雄.txt", "w", encoding='utf8') as f:
f.write(con_str)
end = time.time()
print("concurrent開啟20核并行花費時間:{}\n".format(end - start))
print("concurrent開啟20核并行爬取小說每章花費時間:{} s/章(2頁)\n".format(((end - start) / (num1 - num))))
輸出結果:
## 五、總結
結果展示:
1.我們估算爬取每個章節的時間來作為爬取速度的量化,并且每一個請求休眠了2s,怕網站承受不起。
2.從理論來說,concurrent的方法會更合适爬蟲的并行,編寫也是相對簡單,但是不考慮編寫程式的繁瑣性,threadding的方法也是可以的。
3.有個奇怪的地方,就是我開了20個線程池的concurrent都比不上開4個核的threading,嘗試使用更多或者更少的線程池也沒用,速度還是比不上threading,爬取一章節的時間大約是concurrent的二分之一。