多進(jìn)程
關(guān)于多進(jìn)程爬蟲和多進(jìn)程方面的知識可以參考崔慶才的博客和python文檔
協(xié)程
關(guān)于協(xié)程的介紹,強(qiáng)烈推薦大家看看這份指南
由于GIL的原因,python解釋器中總是只有一個線程存在雏门,因此很難利用多線程來達(dá)到并行的目的搓逾。但多進(jìn)程并不受此限制尺迂,因此我們可以利用python中的多進(jìn)程來達(dá)到并行的目的具温。同時,我們可以在每個進(jìn)程中使用協(xié)程來實現(xiàn)異步的處理癞揉。綜上纸肉,我們可以利用多進(jìn)程+協(xié)程來極大的提升我們爬蟲的效率。
導(dǎo)入模塊
from multiprocessing import Pool, cpu_count
import requests
from gevent import monkey
from gevent.pool import Pool as ge_pool
from gevent.queue import Queue
import json
import copy
failed_urls = [] # 用于記錄爬取失敗的url喊熟,以備后續(xù)的繼續(xù)爬取
finished_urls = [] # 用于記錄爬取成功的url
分割url柏肪,在這里大家需要提前準(zhǔn)備好自己的url,因為作者是先爬取了所有所需網(wǎng)頁的url后芥牌,再來爬取每個url的內(nèi)容的
def split_urls(urls):
if not urls:
print('no url in urls')
return [urls]
num_urls = len(urls)
num_cpus = cpu_count()
if num_urls < num_cpus:
return [urls]
num_urls_per_cpu = int(num_urls / num_cpus)
splitted_urls = []
for i in range(num_cpus):
if i == 0:
splitted_urls.append(urls[: (i + 1) * num_urls_per_cpu])
elif i == num_cpus - 1:
splitted_urls.append(urls[i * num_urls_per_cpu:])
else:
splitted_urls.append(urls[i * num_urls_per_cpu: (i + 1) * num_urls_per_cpu])
return splitted_urls
注意:在這里我們是根據(jù)自己機(jī)器的cpu核心數(shù)來劃分url的烦味,這樣可以充分利用機(jī)器的cpu。不建議開啟多于自己機(jī)器cpu核心數(shù)的線程數(shù)量壁拉,因為這會造成不必要的線程切換的時間上的浪費谬俄。
抓取網(wǎng)頁
def crawling_web(url):
try:
res = requests.get(url, headers=headers, cookies=cookies, timeout=10)
data = json.loads(res.text).get('data')
print('success crawled {}'.format(url))
finished_urls.append(url)
except:
failed_urls.append(url)
print('fali to crawle {}'.format(url))
注意:由于作者所爬取的網(wǎng)頁比較簡單,得到的數(shù)據(jù)是以json格式展示的弃理,所以不需要過多的處理溃论,如果讀者需要對爬取的網(wǎng)頁做進(jìn)一步的處理,可以另寫一個處理的函數(shù)痘昌;讀者需要自己準(zhǔn)備好自己的headers和cookies以及proxies
協(xié)程
def greenlet_crawler(urls):
greenlet_pool = ge_pool(10)
for url in urls:
greenlet_pool.apply_async(crawling_web, (url, ))
greenlet_pool.join()
我們在這里使用了一個pool來維護(hù)10個協(xié)程钥勋,pool里面的協(xié)程異步的爬取網(wǎng)頁炬转。
多進(jìn)程以及調(diào)度器
def scheduler(urls):
global failed_urls
failed_urls = []
splitted_urls = split_urls(urls)
process_pool = Pool(processes=cpu_count())
for urls in splitted_urls:
process_pool.apply_async(greenlet_crawler, (urls,))
process_pool.close()
process_pool.join()
if not failed_urls:
scheduler(copy.deepcopy(failed_urls))
scheduler中多進(jìn)程部分的代碼如下
process_pool = Pool(processes=cpu_count())
for urls in splitted_urls:
process_pool.apply_async(greenlet_crawler, (urls,))
process_pool.close()
process_pool.join()
我們開啟了與自己機(jī)器cpu核心數(shù)相同的線程,并使用線程池來維護(hù)這些線程算灸。
注意:因為我們無法保證每個網(wǎng)頁都被成功抓取下來了返吻,因此我們需要對抓取失敗的url再次進(jìn)行抓取,這里我們在sheduler中使用了遞歸的方式來保證失敗的url會被再次抓取乎婿。
if __name__ == '__main__':
scheduler(urls)
結(jié)語: 只要我們能夠保持線程的并行以及每個線程內(nèi)部多個協(xié)程之間的異步,我們就可以使用多進(jìn)程+協(xié)程的方式來大幅提升我們的爬蟲的效率街佑,作者使用這種方式相比于單線程的爬蟲谢翎,速度的提升是20-40倍(當(dāng)然每個人要面對的場景以及所使用的資源都不一樣,這個速度的比值僅供參考)沐旨,最后還是推薦大家在爬蟲的時候試試這種多進(jìn)程+協(xié)程的方式森逮。