1. 程式人生 > 其它 >python 高效能非同步爬蟲執行緒&執行緒池

python 高效能非同步爬蟲執行緒&執行緒池

爬蟲本質

其實爬蟲的本質就是Client發請求批量獲取Server的響應資料,如果我們有多個url待爬取,只用一個執行緒且採用序列的方式執行,那隻能等待爬取一個結束後才能繼續下一個,效率會非常低。需要強調的是:對於單執行緒下序列N個任務,並不完全等同於低效,如果這N個任務都是純計算的任務,那麼該執行緒對CPU的利用率仍然會很高,之所以單執行緒下序列多個爬蟲任務低效,是因為爬蟲任務是明顯的IO密集型(阻塞)程式。那麼該如何提高爬取效能呢?

同步呼叫

即提交一個任務後就在原地等待任務結束,等到拿到任務的結果後再繼續下一行程式碼,效率低下

import requests
def parse_page(res):
    print('解析 %s' %(len(res)))
def get_page(url):
    print('下載 %s' %url)
    response=requests.get(url)
    if response.status_code == 200:
        return response.text
urls = [
    'http://v.stu.126.net/mooc-video/nos/mp4/2017/02/28/1005853348_f171329df9a543528f1d3661025dafb4_shd.mp4?ak=99ed7479ee303d1b1361b0ee5a4abcee533c409d7b6d3ba248b25062b105fb8a0daa0c5df5b2483dc54be2b27e62827d917f197453fd5e721b09e15b813d89270015e48ffc49c659b128bfe612dda086d65894b8ef217f1626539e3c9eb40879c29b730d22bdcadb1b4f67996129275fa4c38c6336120510aea1ae1790819de86e0fa3e09eeabea1b068b3d9b9b6597acf0c219eb000a69c12ce9d568813365b3e099fcdb77c69ca7cd6141d92c122af',
    'http://v.stu.126.net/mooc-video/nos/mp4/2017/02/28/1005857381_359806fd2b7743459a756c23a5bc74f5_shd.mp4?ak=99ed7479ee303d1b1361b0ee5a4abcee533c409d7b6d3ba248b25062b105fb8a9bac4ab36b09a42f7cfb2ae827a13bbc0dcfecf63835960a43d311794f003b570015e48ffc49c659b128bfe612dda086d65894b8ef217f1626539e3c9eb40879c29b730d22bdcadb1b4f67996129275fa4c38c6336120510aea1ae1790819de86e0fa3e09eeabea1b068b3d9b9b6597acf0c219eb000a69c12ce9d568813365b3e099fcdb77c69ca7cd6141d92c122af',
    'http://v.stu.126.net/mooc-video/nos/mp4/2017/02/28/1005857376_8adb233ed5f447618ad06eec04de1c72_shd.mp4?ak=99ed7479ee303d1b1361b0ee5a4abcee533c409d7b6d3ba248b25062b105fb8a20e529816fcf5545ed862ea3625aba274af0bdd7a1c1a8142b45237805f97b2f0015e48ffc49c659b128bfe612dda086d65894b8ef217f1626539e3c9eb40879c29b730d22bdcadb1b4f67996129275fa4c38c6336120510aea1ae1790819de86e0fa3e09eeabea1b068b3d9b9b6597acf0c219eb000a69c12ce9d568813365b3e099fcdb77c69ca7cd6141d92c122af'
]
for url in urls:
    res=get_page(url) #呼叫一個任務,就在原地等待任務結束拿到結果後才繼續往後執行
    parse_page(res)

解決同步呼叫方案之多執行緒/多程序(不建議使用)
1.好處:在伺服器端使用多執行緒(或多程序)。多執行緒(或多程序)的目的是讓每個連線都擁有獨立的執行緒(或程序),這樣任何一個連線的阻塞都不會影響其他的連線。
2.弊端:開啟多程序或都執行緒的方式,我們是無法無限制地開啟多程序或多執行緒的:在遇到要同時處理成百上千個的連線請求時,則無論多執行緒還是多程序都會嚴重佔據系統資源,降低系統對外界響應效率,而且執行緒與程序本身也更容易進入假死狀態。
解決同步呼叫方案之執行緒/程序池(適當使用)
1.好處:很多程式設計師可能會考慮使用“執行緒池”或“連線池”。“執行緒池”旨在減少建立和銷燬執行緒的頻率,其維持一定合理數量的執行緒,並讓空閒的執行緒重新承擔新的執行任務。可以很好的降低系統開銷。
2.弊端:“執行緒池”和“連線池”技術也只是在一定程度上緩解了頻繁建立和銷燬執行緒帶來的資源佔用。而且,所謂“池”始終有其上限,當請求大大超過上限時,“池”構成的系統對外界的響應並不比沒有池的時候效果好多少。所以使用“池”必須考慮其面臨的響應規模,並根據響應規模調整“池”的大小。

對比同步和使用執行緒池的執行效率

同步執行

#同步執行
import time
def sayhello(str):
    print("Hello ",str)
    time.sleep(2)
name_list =['code_community','aa','bb','cc']
start_time = time.time()
for i in range(len(name_list)):
    sayhello(name_list[i])
print('%d second'% (time.time()-start_time))

返回結果:
Hello code_community
Hello aa
Hello bb
Hello cc
8 second

非同步基於執行緒池

#非同步基於執行緒池
import time
from multiprocessing.dummy import Pool
def sayhello(str):
    print("Hello ",str)
    time.sleep(2)
start = time.time()
name_list =['code_community','aa','bb','cc']
#例項化執行緒池物件,開啟了4個執行緒
pool = Pool(4)
pool.map(sayhello,name_list)
pool.close()
pool.join()
end = time.time()
print(end-start)

返回結果:
Hello code_community
Hello aa
Hello bb
Hello cc
2.0805933475494385

基於multiprocessing.dummy執行緒池爬取梨視訊的視訊資訊

import requests
import random
from lxml import etree
import re
from fake_useragent import UserAgent
#安裝fake-useragent庫:pip install fake-useragent
#匯入執行緒池模組
from multiprocessing.dummy import Pool
#例項化執行緒池物件
pool = Pool()
url = 'http://www.pearvideo.com/category_1'
#隨機產生UA
ua = UserAgent().random
headers = {
    'User-Agent':ua
}
#獲取首頁頁面資料
page_text = requests.get(url=url,headers=headers).text
#對獲取的首頁頁面資料中的相關視訊詳情連結進行解析
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@id="listvideoList"]/ul/li')
detail_urls = []#儲存二級頁面的url
for li in li_list:
    detail_url = 'http://www.pearvideo.com/'+li.xpath('./div/a/@href')[0]
    title = li.xpath('.//div[@class="vervideo-title"]/text()')[0]
    detail_urls.append(detail_url)
vedio_urls = []#儲存視訊的url
for url in detail_urls:
    page_text = requests.get(url=url,headers=headers).text
    vedio_url = re.findall('srcUrl="(.*?)"',page_text,re.S)[0]
    vedio_urls.append(vedio_url)
#使用執行緒池進行視訊資料下載
func_request = lambda link:requests.get(url=link,headers=headers).content
video_data_list = pool.map(func_request,vedio_urls)
#使用執行緒池進行視訊資料儲存
func_saveData = lambda data:save(data)
pool.map(func_saveData,video_data_list)
def save(data):
    fileName = str(random.randint(1,10000))+'.mp4'
    with open(fileName,'wb') as fp:
        fp.write(data)
        print(fileName+'已儲存')
pool.close()
pool.join()

總結
對應上例中的所面臨的可能同時出現的上千甚至上萬次的客戶端請求,“執行緒池”或“連線池”或許可以緩解部分壓力,但是不能解決所有問題。總之,多執行緒模型可以方便高效的解決小規模的服務請求,但面對大規模的服務請求,多執行緒模型也會遇到瓶頸,可以用非阻塞介面來嘗試解決這個問題。