python : 利用 asyncio 進行快速抓取

2021-02-15 Python開發者

(點擊上方藍字,快速關注我們)

來源:伯樂在線專欄作者 - 慕容老匹夫

如有好文章投稿,請點擊 → 這裡了解詳情

如需轉載,發送「轉載」二字查看說明

web數據抓取是一個經常在python的討論中出現的主題。有很多方法可以用來進行web數據抓取,然而其中好像並沒有一個最好的辦法。有一些如scrapy這樣十分成熟的框架,更多的則是像mechanize這樣的輕量級庫。DIY自己的解決方案同樣十分流行:你可以使用requests、beautifulsoup或者pyquery來實現。

方法如此多樣的原因在於,數據「抓取」實際上包括很多問題:你不需要使用相同的工具從成千上萬的頁面中抓取數據,同時使一些Web工作流自動化(例如填一些表單然後取回數據)。我喜歡DIY的原因在於其靈活性,但是卻不適合用來做大量數據的抓取,因為需要請求同步,所以大量的請求意味著你不得不等待很長時間。

在本文中,我將會為你展示一個基於新的異步庫(aiohttp)的請求的代替品。我使用它寫了一些速度的確很快的小數據抓取器,下面我將會為你演示是如何做到的。

asyncio的基本概念

asyncio是在python3.4中被引進的異步IO庫。你也可以通過python3.3的pypi來安裝它。它相當的複雜,而且我不會介紹太多的細節。相反,我將會解釋你需要知道些什麼,以利用它來寫異步的代碼。

簡而言之,有兩件事情你需要知道:協同程序和事件循環。協同程序像是方法,但是它們可以在代碼中的特定點暫停和繼續。當在等待一個IO(比如一個HTTP請求),同時執行另一個請求的時候,可以用來暫停一個協同程序。我們使用關鍵字yield from來設定一個狀態,表明我們需要一個協同程序的返回值。而事件循環則被用來安排協同程序的執行。

關於asyncio還有很多很多,但是以上是我們到目前為止需要知道的。可能你還有些不清楚,那麼讓我們來看一些代碼吧。

aiohttp

aiohttp是一個利用asyncio的庫,它的API看起來很像請求的API。到目前為止,相關文檔還不健全。但是這裡有一些非常有用的例子。我們將會演示它的基本用法。

首先,我們會定義一個協同程序用來獲取頁面,並列印出來。我們使用 asyncio.coroutine將一個方法裝飾成一個協同程序。aiohttp.request是一個協同程序,所以它是一個可讀方法,我們需要使用yield from來調用它們。除了這些,下面的代碼看起來相當直觀:

@asyncio.coroutine

def print_page(url):

    response = yield from aiohttp.request('GET', url)

    body = yield from response.read_and_close(decode=True)

    print(body)

如你所見,我們可以使用yield from從另一個協同程序中調用一個協同程序。為了從同步代碼中調用一個協同程序,我們需要一個事件循環。我們可以通過asyncio.get_event_loop()得到一個標準的事件循環,之後使用它的run_until_complete()方法來運行協同程序。所以,為了使之前的協同程序運行,我們只需要做下面的步驟:

loop = asyncio.get_event_loop()

loop.run_until_complete(print_page('http://example.com'))

一個有用的方法是asyncio.wait,通過它可以獲取一個協同程序的列表,同時返回一個將它們全包括在內的單獨的協同程序,所以我們可以這樣寫:

loop.run_until_complete(asyncio.wait([print_page('http://example.com/foo'),

                                      print_page('http://example.com/bar')]))

另一個是asyncio.as_completed,通過它可以獲取一個協同程序的列表,同時返回一個按完成順序生成協同程序的迭代器,因此當你用它迭代時,會儘快得到每個可用的結果。

數據抓取

現在我們知道了如何做異步HTTP請求,因此我們可以來寫一個數據抓取器了。我們僅僅還需要一些工具來讀取html頁面,我使用了beautifulsoup來做這個事情,其餘的像 pyquery或lxml也可以實現。

在這個例子中,我們會寫一個小數據抓取器來從海盜灣抓取一些linux distributions的torrent 鏈路(海盜灣(英語:The Pirate Bay,縮寫:TPB)是一個專門存儲、分類及搜索Bittorrent種子文件的網站,並自稱「世界最大的BitTorrent tracker(BT種子伺服器)」,提供的BT種子除了有自由版權的收集外,也有不少被著作人聲稱擁有版權的音頻、視頻、應用軟體與電子遊戲等,為網絡分享與下載的重要網站之一–譯者注來自維基百科)

首先,需要一個輔助協同程序來獲取請求:

@asyncio.coroutine

def get(*args, **kwargs):

    response = yield from aiohttp.request('GET', *args, **kwargs)

    return (yield from response.read_and_close(decode=True))

解析部分。本文並非介紹beautifulsoup的,所以這部分我會簡寫:我們獲取了這個頁面的第一個磁鏈。

def first_magnet(page):

    soup = bs4.BeautifulSoup(page)

    a = soup.find('a', title='Download this torrent using magnet')

    return a['href']

在這個協同程序中,url的結果通過種子的數量進行排序,所以排名第一的結果實際上是種子最多的:

@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))

最後,用下面的代碼來調用以上所有的方法。

distros = ['archlinux', 'ubuntu', 'debian']

loop = asyncio.get_event_loop()

f = asyncio.wait([print_magnet(d) for d in distros])

loop.run_until_complete(f)

結論

好了,現在我們來到了這個部分。你有了一個異步工作的小抓取器。這意味著多個頁面可以同時被下載,所以這個例子要比使用請求的相同代碼快3倍。現在你應該可以用相同的方法寫出你自己的抓取器了。

你可以在這裡(https://gist.github.com/madjar/9312452)找到生成的代碼,也包括一些額外的建議。

你一旦熟悉了這一切,我建議你看一看asyncio的文檔和aiohttp的範例,這些都能告訴你 asyncio擁有怎樣的潛力。

這種方法(事實上是所有手動的方法)的一個局限在於,沒有一個獨立的庫可以用來處理表單。機械化的方法擁有很多輔助工具,這使得提交表單變得十分簡單,但是如果你不使用它們,你將不得不自己去處理這些事情。這可能會導致一些bug的出現,所以同時我可能會寫一個這樣的庫(不過目前為止無需為此擔心)。

額外的建議:不要敲打伺服器

同時做3個請求很酷,但是同時做5000個就不那麼好玩了。如果你打算同時做太多的請求,連結有可能會斷掉。你甚至有可能會被禁止連結網絡。

為了避免這些,你可以使用semaphore。這是一個可以被用來限制同時工作的協同程序數量的同步工具。我們只需要在建立循環之前創建一個semaphore ,同時把我們希望允許的同時請求的數量作為參數傳給它既可:

sem = asyncio.Semaphore(5)

然後,我們只需要將下面

page = yield from get(url, compress=True)

替換成被semaphore 保護的同樣的東西。

with (yield from sem):

    page = yield from get(url, compress=True)

這就可以保證同時最多有5個請求會被處理。

額外建議:進度條

這個東東是免費的哦:tqdm是一個用來生成進度條的優秀的庫。這個協同程序就像asyncio.wait一樣工作,不過會顯示一個代表完成度的進度條。

@asyncio.coroutine

def wait_with_progress(coros):

    for f in tqdm.tqdm(asyncio.as_completed(coros), total=len(coros)):

        yield from f

覺得本文對你有幫助?請分享給更多人

關注「Python開發者」

看更多技術乾貨

相關焦點

  • Python中asyncio神器的入門
    而異步io則應用而生,本文將從協程、asyncio、aiohttp這三個方面介紹異步io的使用。協程協程和線程的區別協程,即協作式程序,其思想是,一系列互相依賴的協程間依次使用CPU,每次只有一個協程工作,而其他協程處於休眠狀態。協程實際上是在一個線程中,只不過每個協程對CPU進行順序分時使用。任務執行順序用戶可控。
  • Python異步編程模塊asyncio學習
    同時asyncio也支持調度代碼在將來的某個特定事件運行,從而支持一個協程等待另一個協程完成,以處理系統信號和識別其他一些事件。Python異步編程模塊asyncio學習作者:cxapython | 來源:Python學習開發異步並發的概念對於其他的並發模型大多數採取的都是線性的方式編寫。
  • python異步編程模塊asyncio學習(二)
    在上次我們說如何使用asyncio,詳情點擊:python異步編程模塊asyncio學習(一)。接下來我們繼續學習關於異步模塊asyncio的其他操作。儘管asyncio應用通常作為單線程運行,不過仍被構建為並發應用。由於I/O以及其他外部事件的延遲和中斷,每個協程或任務可能按一種不可預知的順序執行。
  • python之asyncio包處理並發2
    此外,適合asyncio 的協程要由調用方驅動,並由調用方通過yield from 調用;或者把協程傳給asyncio 包中的某個函數,例如asyncio.async(...) 和本章要介紹的其他函數,從而驅動協程。最後,@asyncio.coroutine 裝飾器應該應用在協程上。
  • Python | 非常適合小白的 Asyncio 教程
    協程可以:* 等待一個 future 結束* 等待另一個協程(產生一個結果,或引發一個異常)* 產生一個結果給正在等它的協程* 引發一個異常給正在等它的協程asyncio.sleep 也是一個協程,所以 await asyncio.sleep(x) 就是等待另一個協程。
  • Python 協程模塊 asyncio 使用指南
    我們用到是 Python 標準庫的 asyncio 模塊。asyncio 是異步教程中必須學習的基礎模塊。協程的好處就是單線程即可輕鬆實現百萬級的並發,同時速度上也得到大大提升,在爬蟲領域和 Web 開發領域等都有很大的用途,但是,這些模塊都是要配合協程模塊 asyncio 使用的。
  • 小學生在網吧用python抓取LOL英雄皮膚,步驟簡單,附帶所有源碼
    你也許或一定玩過LOL英雄聯盟,但你一定沒有嘗試過用Python抓取LOL的各種英雄皮膚。隨著python在中小教育中的普及,就連小學生也開始能用python抓取LOL英雄皮膚了,不得不說,這以後買皮膚剩下的錢,都可以輕輕鬆鬆談場初戀了!
  • python數據抓取3種方法總結
    這篇文章主要給大家介紹了關於python數據抓取的3種方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值
  • 推薦 :手把手教你用Python進行Web抓取(附代碼)
    用Python實現一個簡單的網絡爬蟲的快速示例,您可以在GitHub上找到本教程中所介紹的完整代碼。GitHub連結:https://github.com/kaparker/tutorials/blob/master/pythonscraper/websitescrapefasttrack.py以下是本文使用Python進行網頁抓取的簡短教程概述:連接到網頁使用BeautifulSoup解析html
  • 如何利用並發性加速你的python程序(一):相關概念
    如果你聽過很多關於 asyncio 被添加到 python 的討論,但是好奇它與其他並發方法相比怎麼樣,或者你很好奇什麼是並發,以及它如何加速你的程序,那麼你需要看下 Jim Anderson 的這篇文章,雷鋒網編譯整理。在本文中,你將了解以下內容:什麼是並發?什麼是並行?
  • python之asyncio包處理並發5
    #改進asyncio下載腳本 展示如何使用100 個並發請求(-m 100)從ERROR 伺服器中下載100 面國旗(-al 100)。運行flags2_asyncio.py 腳本#重寫後的函數可以供concurrent.future 版重用。
  • Python與協程從Python2—Python3
    協程的缺點:1)無法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程需要和進程配合才能運行在多CPU上2)進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序Python2中的協程
  • Python協程:概念及其用法
    Python2.x協程python2.x協程應用:python2.x中支持協程的模塊不多,gevent算是比較常用的,這裡就簡單介紹一下gevent的用法。python3.x協程應用: asynico + yield from(python3.4) asynico + await(python3.5) geventPython3.4以後引入了asyncio模塊,可以很好的支持協程。
  • 利用 Python,四步掌握機器學習
    (點擊上方藍字,快速關注我們)譯文:伯樂在線專欄作者 - J.F.
  • Python多線程實戰
    2 Python線程模塊python主要是通過thread和threading這兩個模塊來實現多線程支持。python的thread模塊是比較底層的模塊,python的threading模塊是對thread做了一些封裝,可以更加方便的被使用。
  • Python裡最難的Asyncio,這裡有一份非常適合小白的教程
    協程可以:等待一個 future 結束等待另一個協程(產生一個結果,或引發一個異常)產生一個結果給正在等它的協程引發一個異常給正在等它的協程asyncio.sleep 也是一個協程,所以 await asyncio.sleep(x) 就是等待另一個協程。
  • python實踐:利用爬蟲刷網課
    前言:用過python的人應該都會知道爬蟲這個東西,網絡爬蟲(又被稱為網頁蜘蛛,網絡機器人,在FOAF社區中間,更經常的稱為網頁追逐者),是一種按照一定的規則,自動地抓取全球資訊網信息的程序或者腳本。今天就來講講如何利用爬蟲技術刷網課。實戰:最近學校又推送了一波網課,一個一個的看實在太費時間,於是乎就想到了爬蟲來自動刷網課。
  • Python3.9來了,這十個新特性值得關注
    此外該版本也對許多模塊進行了改進,如 ast、asyncio、concurrent.futures、multiprocessing、xml 等。圖源:https://twitter.com/PrasoonPratham/status/1313392420038483968現在讓我們一起探索 Python 3.9 的新特性。
  • Python新手爬蟲,簡單製作抓取廖雪峰的教程的小爬蟲
    先看幾張對比圖,分別是官網截圖和抓取下來的 txt文檔的截圖,不算那難看的排版的話,內容是一致的,圖片用 url替換了!在整個抓取過程中,除了普通的文本以外,還需要處理 3個地方,分別是:代碼、圖片、視頻,因為目前只寫到了文本文件,所以直接抓到圖片或者視頻的地址,標識清楚後寫入到 txt,這裡可以在進一步,寫到 word/pdf 或者其他文件,留待以後改進!
  • 【翻譯】Awesome Asyncio 中文版
    Awesome-asyncio是 Timo Furrer 發起並維護的 Python Asyncio 資源列表。本項目是其中文版,在這裡,收集了大量的 Asyncio 的最棒、最新的資源,供大家探索 Python 異步編程世界。