在Python3中使用asyncio庫(kù)進(jìn)行快速數(shù)據(jù)抓取的教程
web數(shù)據(jù)抓取是一個(gè)經(jīng)常在python的討論中出現(xiàn)的主題。有很多方法可以用來(lái)進(jìn)行web數(shù)據(jù)抓取,然而其中好像并沒(méi)有一個(gè)最好的辦法。有一些如scrapy這樣十分成熟的框架,更多的則是像mechanize這樣的輕量級(jí)庫(kù)。DIY自己的解決方案同樣十分流行:你可以使用requests、beautifulsoup或者pyquery來(lái)實(shí)現(xiàn)。
方法如此多樣的原因在于,數(shù)據(jù)“抓取”實(shí)際上包括很多問(wèn)題:你不需要使用相同的工具從成千上萬(wàn)的頁(yè)面中抓取數(shù)據(jù),同時(shí)使一些Web工作流自動(dòng)化(例如填一些表單然后取回?cái)?shù)據(jù))。我喜歡DIY的原因在于其靈活性,但是卻不適合用來(lái)做大量數(shù)據(jù)的抓取,因?yàn)樾枰?qǐng)求同步,所以大量的請(qǐng)求意味著你不得不等待很長(zhǎng)時(shí)間。
在本文中,我將會(huì)為你展示一個(gè)基于新的異步庫(kù)(aiohttp)的請(qǐng)求的代替品。我使用它寫(xiě)了一些速度的確很快的小數(shù)據(jù)抓取器,下面我將會(huì)為你演示是如何做到的。
asyncio的基本概念
asyncio是在python3.4中被引進(jìn)的異步IO庫(kù)。你也可以通過(guò)python3.3的pypi來(lái)安裝它。它相當(dāng)?shù)膹?fù)雜,而且我不會(huì)介紹太多的細(xì)節(jié)。相反,我將會(huì)解釋你需要知道些什么,以利用它來(lái)寫(xiě)異步的代碼。
簡(jiǎn)而言之,有兩件事情你需要知道:協(xié)同程序和事件循環(huán)。協(xié)同程序像是方法,但是它們可以在代碼中的特定點(diǎn)暫停和繼續(xù)。當(dāng)在等待一個(gè)IO(比如一個(gè)HTTP請(qǐng)求),同時(shí)執(zhí)行另一個(gè)請(qǐng)求的時(shí)候,可以用來(lái)暫停一個(gè)協(xié)同程序。我們使用關(guān)鍵字yield from來(lái)設(shè)定一個(gè)狀態(tài),表明我們需要一個(gè)協(xié)同程序的返回值。而事件循環(huán)則被用來(lái)安排協(xié)同程序的執(zhí)行。
關(guān)于asyncio還有很多很多,但是以上是我們到目前為止需要知道的??赡苣氵€有些不清楚,那么讓我們來(lái)看一些代碼吧。
aiohttp
aiohttp是一個(gè)利用asyncio的庫(kù),它的API看起來(lái)很像請(qǐng)求的API。到目前為止,相關(guān)文檔還不健全。但是這里有一些非常有用的例子。我們將會(huì)演示它的基本用法。
首先,我們會(huì)定義一個(gè)協(xié)同程序用來(lái)獲取頁(yè)面,并打印出來(lái)。我們使用 asyncio.coroutine將一個(gè)方法裝飾成一個(gè)協(xié)同程序。aiohttp.request是一個(gè)協(xié)同程序,所以它是一個(gè)可讀方法,我們需要使用yield from來(lái)調(diào)用它們。除了這些,下面的代碼看起來(lái)相當(dāng)直觀(guān):
@asyncio.coroutine def print_page(url): response = yield from aiohttp.request('GET', url) body = yield from response.read_and_close(decode=True) print(body)
如你所見(jiàn),我們可以使用yield from從另一個(gè)協(xié)同程序中調(diào)用一個(gè)協(xié)同程序。為了從同步代碼中調(diào)用一個(gè)協(xié)同程序,我們需要一個(gè)事件循環(huán)。我們可以通過(guò)asyncio.get_event_loop()得到一個(gè)標(biāo)準(zhǔn)的事件循環(huán),之后使用它的run_until_complete()方法來(lái)運(yùn)行協(xié)同程序。所以,為了使之前的協(xié)同程序運(yùn)行,我們只需要做下面的步驟:
loop = asyncio.get_event_loop() loop.run_until_complete(print_page('http://example.com'))
一個(gè)有用的方法是asyncio.wait,通過(guò)它可以獲取一個(gè)協(xié)同程序的列表,同時(shí)返回一個(gè)將它們?nèi)ㄔ趦?nèi)的單獨(dú)的協(xié)同程序,所以我們可以這樣寫(xiě):
loop.run_until_complete(asyncio.wait([print_page('http://example.com/foo'), print_page('http://example.com/bar')]))
另一個(gè)是asyncio.as_completed,通過(guò)它可以獲取一個(gè)協(xié)同程序的列表,同時(shí)返回一個(gè)按完成順序生成協(xié)同程序的迭代器,因此當(dāng)你用它迭代時(shí),會(huì)盡快得到每個(gè)可用的結(jié)果。
數(shù)據(jù)抓取
現(xiàn)在我們知道了如何做異步HTTP請(qǐng)求,因此我們可以來(lái)寫(xiě)一個(gè)數(shù)據(jù)抓取器了。我們僅僅還需要一些工具來(lái)讀取html頁(yè)面,我使用了beautifulsoup來(lái)做這個(gè)事情,其余的像 pyquery或lxml也可以實(shí)現(xiàn)。
在這個(gè)例子中,我們會(huì)寫(xiě)一個(gè)小數(shù)據(jù)抓取器來(lái)從海盜灣抓取一些linux distributions的torrent 鏈路(海盜灣(英語(yǔ):The Pirate Bay,縮寫(xiě):TPB)是一個(gè)專(zhuān)門(mén)存儲(chǔ)、分類(lèi)及搜索Bittorrent種子文件的網(wǎng)站,并自稱(chēng)“世界最大的BitTorrent tracker(BT種子服務(wù)器)”,提供的BT種子除了有自由版權(quán)的收集外,也有不少被著作人聲稱(chēng)擁有版權(quán)的音頻、視頻、應(yīng)用軟件與電子游戲等,為網(wǎng)絡(luò)分享與下載的重要網(wǎng)站之一–譯者注來(lái)自維基百科)
首先,需要一個(gè)輔助協(xié)同程序來(lái)獲取請(qǐng)求:
@asyncio.coroutine def get(*args, **kwargs): response = yield from aiohttp.request('GET', *args, **kwargs) return (yield from response.read_and_close(decode=True))
解析部分。本文并非介紹beautifulsoup的,所以這部分我會(huì)簡(jiǎn)寫(xiě):我們獲取了這個(gè)頁(yè)面的第一個(gè)磁鏈。
def first_magnet(page): soup = bs4.BeautifulSoup(page) a = soup.find('a', title='Download this torrent using magnet') return a['href']
在這個(gè)協(xié)同程序中,url的結(jié)果通過(guò)種子的數(shù)量進(jìn)行排序,所以排名第一的結(jié)果實(shí)際上是種子最多的:
@asyncio.coroutine def print_magnet(query): url = 'http://thepiratebay.se/search/{}/0/7/0'.format(query) page = yield from get(url, compress=True) magnet = first_magnet(page) print('{}: {}'.format(query, magnet))
最后,用下面的代碼來(lái)調(diào)用以上所有的方法。
distros = ['archlinux', 'ubuntu', 'debian'] loop = asyncio.get_event_loop() f = asyncio.wait([print_magnet(d) for d in distros]) loop.run_until_complete(f)
結(jié)論
好了,現(xiàn)在我們來(lái)到了這個(gè)部分。你有了一個(gè)異步工作的小抓取器。這意味著多個(gè)頁(yè)面可以同時(shí)被下載,所以這個(gè)例子要比使用請(qǐng)求的相同代碼快3倍?,F(xiàn)在你應(yīng)該可以用相同的方法寫(xiě)出你自己的抓取器了。
你可以在這里找到生成的代碼,也包括一些額外的建議。
你一旦熟悉了這一切,我建議你看一看asyncio的文檔和aiohttp的范例,這些都能告訴你 asyncio擁有怎樣的潛力。
這種方法(事實(shí)上是所有手動(dòng)的方法)的一個(gè)局限在于,沒(méi)有一個(gè)獨(dú)立的庫(kù)可以用來(lái)處理表單。機(jī)械化的方法擁有很多輔助工具,這使得提交表單變得十分簡(jiǎn)單,但是如果你不使用它們,你將不得不自己去處理這些事情。這可能會(huì)導(dǎo)致一些bug的出現(xiàn),所以同時(shí)我可能會(huì)寫(xiě)一個(gè)這樣的庫(kù)(不過(guò)目前為止無(wú)需為此擔(dān)心)。
額外的建議:不要敲打服務(wù)器
同時(shí)做3個(gè)請(qǐng)求很酷,但是同時(shí)做5000個(gè)就不那么好玩了。如果你打算同時(shí)做太多的請(qǐng)求,鏈接有可能會(huì)斷掉。你甚至有可能會(huì)被禁止鏈接網(wǎng)絡(luò)。
為了避免這些,你可以使用semaphore。這是一個(gè)可以被用來(lái)限制同時(shí)工作的協(xié)同程序數(shù)量的同步工具。我們只需要在建立循環(huán)之前創(chuàng)建一個(gè)semaphore ,同時(shí)把我們希望允許的同時(shí)請(qǐng)求的數(shù)量作為參數(shù)傳給它既可:
sem = asyncio.Semaphore(5)
然后,我們只需要將下面
page = yield from get(url, compress=True)
替換成被semaphore 保護(hù)的同樣的東西。
with (yield from sem): page = yield from get(url, compress=True)
這就可以保證同時(shí)最多有5個(gè)請(qǐng)求會(huì)被處理。
額外建議:進(jìn)度條
這個(gè)東東是免費(fèi)的哦:tqdm是一個(gè)用來(lái)生成進(jìn)度條的優(yōu)秀的庫(kù)。這個(gè)協(xié)同程序就像asyncio.wait一樣工作,不過(guò)會(huì)顯示一個(gè)代表完成度的進(jìn)度條。
@asyncio.coroutine def wait_with_progress(coros): for f in tqdm.tqdm(asyncio.as_completed(coros), total=len(coros)): yield from f
相關(guān)文章
改變 Python 中線(xiàn)程執(zhí)行順序的方法
這篇文章主要介紹了改變 Python 中線(xiàn)程的執(zhí)行順序的方法,幫助大家更好的理解和學(xué)習(xí)python,感興趣的朋友可以了解下2020-09-09基于python實(shí)現(xiàn)數(shù)組格式參數(shù)加密計(jì)算
這篇文章主要介紹了基于python實(shí)現(xiàn)數(shù)組格式參數(shù)加密計(jì)算,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Python爬蟲(chóng)采集微博視頻數(shù)據(jù)
這篇文章主要介紹了利用Python爬蟲(chóng)采集微博的視頻數(shù)據(jù),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)python的小伙伴們有很好地幫助,需要的朋友可以參考下2021-12-12python自動(dòng)從arxiv下載paper的示例代碼
這篇文章主要介紹了python自動(dòng)從arxiv下載paper的示例代碼,幫助大家更好的理解和學(xué)習(xí)python,感興趣的朋友可以了解下2020-12-12Python 正則表達(dá)式(?=...)和(?<=...)符號(hào)的使用
本文主要介紹Python 正則表達(dá)式(?=...)和(?<=...)符號(hào)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05Python利用matplotlib實(shí)現(xiàn)制作動(dòng)態(tài)條形圖
說(shuō)到用 Python 制作動(dòng)態(tài)圖,首先想到的肯定是一些直接拿來(lái)就用的庫(kù),雖然我沒(méi)做過(guò),但是我相信一定有且不止一個(gè),搜了一圈后發(fā)現(xiàn)有個(gè)bar chart race庫(kù)看起來(lái)不錯(cuò),感興趣的可以跟隨小編一起學(xué)習(xí)一下2022-10-10Django實(shí)現(xiàn)登錄隨機(jī)驗(yàn)證碼的示例代碼
登錄驗(yàn)證碼是每個(gè)網(wǎng)站登錄時(shí)的基本標(biāo)配,這篇文章主要介紹了Django實(shí)現(xiàn)登錄隨機(jī)驗(yàn)證碼的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06利用python數(shù)據(jù)分析處理進(jìn)行炒股實(shí)戰(zhàn)行情
這篇文章主要介紹了利用python數(shù)據(jù)分析進(jìn)行炒股實(shí)戰(zhàn)行情,本文主要介紹三部分:數(shù)據(jù)采集,數(shù)據(jù)預(yù)處理,利用SVM算法進(jìn)行建模,本文僅供參考借鑒2021-08-08