多線程爬蟲與異步爬蟲的性能測試

如何提升爬蟲的性能

如果你使用過爬蟲框架scrapy,那么你多多少少會驚異于她的并發(fā)和高效。
在scrapy中,你可以通過在settings中設(shè)置線程數(shù)來輕松定制一個多線程爬蟲。這得益于scrappy的底層twisted異步框架。
異步在爬蟲開發(fā)中經(jīng)常突顯奇效,因為他可以是單個鏈接爬蟲不堵塞
不阻塞可以理解為:在A線程等待response的時候,B線程可以發(fā)起requests,或者C線程可以進行數(shù)據(jù)處理。
要單個爬蟲線程不阻塞,python可以使用到的庫有:

  • threading
  • gevent
  • asyncio

一個常規(guī)的阻塞爬蟲

下面的代碼實現(xiàn)了一個獲取 貓眼電影top100 的爬蟲,網(wǎng)站反爬較弱,帶上UA即可。
我們給爬蟲寫一個裝飾器,記錄其爬取時間。

import requests
import time
from lxml import etree
from threading import Thread
from functools import cmp_to_key


# 給輸出結(jié)果排序
def sortRule(x, y):
    for i in x.keys():
        c1 = int(i)
    for i in y.keys():
        c2 = int(i)
    if c1 > c2:
        return 1
    elif c1 < c2:
        return -1
    else:
        return 0


# 計算時間的裝飾器
def caltime(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        print("costtime: ", time.time() - start)

    return wrapper


# 獲取頁面
def getPage(url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36',
        # 'Cookie': '__mta=141898381.1589978369143.1590927122695.1590927124319.9; uuid_n_v=v1; uuid=EDAA8A109A9611EABDA40952C053E9B506991609A05441F5AFBA3872BEA6088C; _csrf=f36a7050eb60429b197a902b4f1d66317db95bde0879648c8bff0e8237e937de; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1589978364; mojo-uuid=8b4dad0e1f472f08ffd3f3f67b75f2ab; _lxsdk_cuid=17232188c2f0-022085e6f29b1b-30657c06-13c680-17232188c30c8; _lxsdk=EDAA8A109A9611EABDA40952C053E9B506991609A05441F5AFBA3872BEA6088C; mojo-session-id={"id":"afcd899e03fe72ca70e34368fe483d15","time":1590927095603}; __mta=141898381.1589978369143.1590063115667.1590927111235.7; mojo-trace-id=10; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1590927124; _lxsdk_s=1726aa4fd86-ba9-904-221%7C%7C15',
    }
    try:
        resp = requests.get(url=url, headers=headers)
        if resp.status_code == 200:
            return resp.text
        return None
    except Exception as e:
        print(e)
        return None


# 獲取單個頁面數(shù)據(jù)
def parsePage(page):
    if not page:
        yield
    data = etree.HTML(page).xpath('.//dl/dd')
    for d in data:
        rank = d.xpath("./i/text()")[0]
        title = d.xpath(".//p[@class='name']/a/text()")[0]
        yield {
            rank: title
        }


# 調(diào)度
def schedule(url, f):
    page = getPage(url)
    for data in parsePage(page):
        f.append(data)


# 數(shù)據(jù)展示
def show(f):
    f.sort(key=cmp_to_key(sortRule))
    for x in f:
        print(x)


@caltime
def main():
    urls = ['https://maoyan.com/board/4?offset={offset}'.format(offset=i) for i in range(0, 100, 10)]
    f = []
    for url in urls:
        schedule(url, f)
    show(f)


if __name__ == '__main__':
    main()

成功爬取完top100平均花費2.8s左右。
這個爬蟲程序總共有10個小的爬蟲線程,每個爬蟲線程爬取10條數(shù)據(jù)。當(dāng)前面的線程未成功收到response時,后面所有的線程都阻塞了。
這也是這個爬蟲程序低效的原因。因為線程之間有明確的先后順序,后面的線程無法越過前面的線程發(fā)送請求。


threading打破線程的優(yōu)先級?

接下來我們使用多線程打破這種優(yōu)先順序。修改main函數(shù)

def main():
    urls = ['https://maoyan.com/board/4?offset={offset}'.format(offset=i) for i in range(0, 100, 10)]
    threads = []
    f = []
    for url in urls:
        # schedule(url, f)
        t = Thread(target=schedule, args=(url, f))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    show(f)

記得導(dǎo)入threading庫

from threading import Thread

點擊運行,發(fā)現(xiàn)時間縮短為0.4s,性能的提升還是很客觀的。
threading的作用在于開啟了多個線程,每個線程同時競爭GIL,當(dāng)拿到GIL發(fā)出requests后。該線程又立即釋放GIL。進入等待Response的狀態(tài)。
釋放掉的GIL又馬上被其他線程獲取...如此以來,每個線程都是平等的,無先后之分。看起來就好像同時進行著(實際并不是,因為GIL的原因)。
所以效率大大提升了。


gevent異步協(xié)程搞一波?

gevent是一個優(yōu)先的異步網(wǎng)絡(luò)庫,可以輕松支持高并發(fā)的網(wǎng)絡(luò)訪問。我們現(xiàn)在試著把阻塞的爬蟲加上gevent試試

@caltime
def main():
    threads = []
    urls = ['https://maoyan.com/board/4?offset={offset}'.format(offset=i) for i in range(0, 100, 10)]
    f = []
    for url in urls:
        threads.append(gevent.spawn(schedule, url, f))
    gevent.joinall(threads)
    show(f)

同樣這里也要導(dǎo)入gevent庫

import gevent
from gevent import monkey
monkey.patch_all()

點擊運行,平均時間在0.45上左右,和多線程差不多。


新版異步庫ascyncio搞一波?

ascyncion是python前不久剛推出的基于協(xié)程的異步庫,號稱最有野心的庫。要使ascyncio支持我們的程序,必須對getPage做點修改:
因為requests是不支持異步的,所以我們這里使用aiohttp庫替換requests,并用它來實現(xiàn)getPage函數(shù)。

# 異步requests
async def getPage(url):
    headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36'}
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers = headers) as resp:
            return await resp.text()

main函數(shù)也需要修改

@caltime
def main():
    urls = ['https://maoyan.com/board/4?offset={offset}'.format(offset=i) for i in range(0, 100, 10)]
    loop = asyncio.get_event_loop()
    f = []
    threads = []
    for url in urls:
        threads.append(schedule(url,f))
    loop.run_until_complete(asyncio.wait(threads))
    show(f)

記得導(dǎo)入相關(guān)庫

import asyncio
import aiohttp

點擊運行,平均時間在0.35左右,性能稍優(yōu)于多線程和gevent一點。


結(jié)語

對于爬蟲技術(shù),其實有些比較新的東西是值得去了解一下的。比如:

  • 提升并發(fā)方面:asyncio, aiohttp
  • 動態(tài)渲染:pyppeteer(puppeteer的python版,支持異步)
  • 驗證碼破解:機器學(xué)習(xí),模型訓(xùn)練

還有一些數(shù)據(jù)解析方面的工具性能大概如下:

  • re > lxml > bs4
  • 但是即便是同一種解析方法,不同工具實現(xiàn)的,性能也不一樣。比如同樣是xpath,lxml的性能略好于parsel(scrapy團隊開發(fā)的數(shù)據(jù)解析工具,支持css,re,xpath)的。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,084評論 2 375