亚洲熟女综合色一区二区三区,亚洲精品中文字幕无码蜜桃,亚洲va欧美va日韩va成人网,亚洲av无码国产一区二区三区,亚洲精品无码久久久久久久

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

從網(wǎng)站中抓取數(shù)據(jù)是開發(fā)者的一個(gè)典型“用例”。無論它是屬于副業(yè)項(xiàng)目,還是你正在成立一個(gè)初創(chuàng)公司,抓取數(shù)據(jù)似乎都很有必要。

舉個(gè)例子,倘若您想要?jiǎng)?chuàng)建一個(gè)比價(jià)網(wǎng)站,那么您會需要從各種電商網(wǎng)站上抓取價(jià)格信息;或者您想要構(gòu)建一個(gè)可以識別商品并在亞馬遜上自動(dòng)查找價(jià)格的“人工智能”。類似的場景還有很多。

但是您有沒有注意到,獲取所有頁面信息的速度有多慢呢?您會選擇一個(gè)接一個(gè)地去抓取商品嗎?應(yīng)該會有更好的解決方案吧?答案是肯定的。

抓取網(wǎng)頁可能非常耗時(shí),因?yàn)槟仨毣〞r(shí)間等待服務(wù)器響應(yīng),抑或是速率受限。這就是為什么我們要向您展示如何通過在 Python 中使用并發(fā)來加速您的網(wǎng)頁數(shù)據(jù)抓取項(xiàng)目。

前提

為了使代碼正常運(yùn)行,您需要安裝 python 3。部分系統(tǒng)可能已經(jīng)預(yù)裝了它。然后您還需要使用?pip install?安裝所有必要的庫。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

如果您了解并發(fā)背后的基礎(chǔ)知識,可以跳過理論部分直接進(jìn)入實(shí)際操作環(huán)節(jié)。

并發(fā)

并發(fā)是一個(gè)術(shù)語,用于描述同時(shí)運(yùn)行多個(gè)計(jì)算任務(wù)的能力。

當(dāng)您按順序向網(wǎng)站發(fā)出請求時(shí),您可以選擇一次發(fā)出一個(gè)請求并等待結(jié)果返回,然后再發(fā)出下一個(gè)請求。

不過,您也可以同時(shí)發(fā)送多個(gè)請求,并在它們返回時(shí)處理對應(yīng)的結(jié)果,這種方式的速度提升效果是非常顯著的。與順序請求相比,并發(fā)請求無論是否并行運(yùn)行(多個(gè) CPU),都會比前者快得多 -- 稍后會詳細(xì)介紹。

要理解并發(fā)的優(yōu)勢。我們需要了解順序處理和并發(fā)處理任務(wù)之間的區(qū)別。假設(shè)我們有五個(gè)任務(wù),每個(gè)任務(wù)需要 10 秒才能完成。當(dāng)按順序處理它們時(shí),完成五個(gè)任務(wù)所需的時(shí)間為 50 秒;而并發(fā)處理時(shí),僅需要 10 秒即可完成。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

除了提高處理速度之外,并發(fā)還允許我們通過將網(wǎng)頁抓取任務(wù)負(fù)載分布于多個(gè)進(jìn)程中,來實(shí)現(xiàn)在更短的時(shí)間內(nèi)完成更多的工作。

這里有幾種實(shí)現(xiàn)并行化請求的方式:例如?multiprocessing?和?asyncio。從網(wǎng)頁抓取的角度來看,我們可以使用這些庫來并行處理對不同網(wǎng)站或同一網(wǎng)站不同頁面的請求。在本文中,我們將重點(diǎn)關(guān)注?asyncio,這是一個(gè) Python 內(nèi)置的模塊,它提供了使用協(xié)程編寫單線程并發(fā)代碼的基礎(chǔ)設(shè)施。

由于并發(fā)意味著更復(fù)雜的系統(tǒng)和代碼,因此在使用前請考慮在您的使用場景中是否利大于弊。

并發(fā)的優(yōu)勢

  • 在更短的時(shí)間內(nèi)完成更多的工作
  • 可以將空閑的網(wǎng)絡(luò)時(shí)間投入到其他請求中

并發(fā)的危險(xiǎn)之處

  • 更不易于開發(fā)和調(diào)試
  • 可能存在競爭條件
  • 需要檢查并使用線程安全的函數(shù)
  • 一不小心就會增加程序阻塞的概率
  • 并發(fā)自帶系統(tǒng)開銷,因此需要設(shè)置合理的并發(fā)級別
  • 針對小型站點(diǎn)請求過多的話,可能會變成 DDoS 攻擊

為何選擇 asyncio

在做出選擇之前,我們有必要了解一下?asyncio?和?multiprocessing?之間的區(qū)別,以及 IO 密集型與 CPU 密集型之間的區(qū)別。

asyncio?“是一個(gè)使用 async/await 語法編寫并發(fā)代碼的庫”,它在單個(gè)處理器上運(yùn)行。

multiprocessing“是一個(gè)支持使用 API 生產(chǎn)進(jìn)程的包 [...] 允許程序員充分利用給定機(jī)器上的多個(gè)處理器”。每個(gè)進(jìn)程將在不同的 CPU 中啟動(dòng)自己的 Python 解釋器。

IO 密集型意味著程序?qū)⑹?I/O 影響而變得運(yùn)行緩慢。在我們的案例中,主要指的是網(wǎng)絡(luò)請求。

CPU 密集型意味著程序會由于 CPU 計(jì)算壓力導(dǎo)致運(yùn)行緩慢 -- 例如數(shù)學(xué)計(jì)算。

為什么這會影響我們選擇用于并發(fā)的庫?因?yàn)椴l(fā)成本的很大一部分是創(chuàng)建和維護(hù)線程/進(jìn)程。對于 CPU 密集型問題,在不同的 CPU 中擁有多個(gè)進(jìn)程將會提升效率。但對于 I/O 密集型的場景,情況可能并非如此。

由于網(wǎng)頁數(shù)據(jù)抓取主要受 I/O 限制,因此我們選擇了?asyncio。但如果有疑問(或只是為了好玩),您可以使用?multiprocessing?嘗試這個(gè)場景并比較一下結(jié)果。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

順序?qū)崿F(xiàn)的版本

我們將從抓取?scrapeme.live?作為示例開始,這是一個(gè)專門用于測試的電子商務(wù)網(wǎng)站。

首先,我們將從順序抓取的版本開始。以下幾個(gè)片段是所有案例的一部分,因此它們將保持不變。

通過訪問目標(biāo)主頁,我們發(fā)現(xiàn)它有 48 個(gè)子頁面。由于是測試環(huán)境,這些子頁面不會很快發(fā)生變化,我們會使用到以下兩個(gè)常量:

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

現(xiàn)在,從目標(biāo)產(chǎn)品中提取基礎(chǔ)數(shù)據(jù)。為此,我們使用?requests.get?獲取 HTML 內(nèi)容,然后使用?BeautifulSoup?解析它。我們將遍歷每個(gè)產(chǎn)品并從中獲取一些基本信息。所有選擇器都來自對內(nèi)容的手動(dòng)審查(使用 DevTools),但為簡潔起見,我們不會在這里詳細(xì)介紹。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

extract_details?函數(shù)將獲取一個(gè)頁碼并將其連接起來,用于創(chuàng)建子頁面的 URL。獲取內(nèi)容并創(chuàng)建產(chǎn)品數(shù)組后返回。這意味著返回的值將是一個(gè)字典列表,這是一個(gè)后續(xù)使用的必要細(xì)節(jié)。

我們需要為每個(gè)頁面運(yùn)行上面的函數(shù),獲取所有結(jié)果,并存儲它們。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

運(yùn)行上面的代碼將獲得兩個(gè)產(chǎn)品頁面,提取產(chǎn)品(總共 32 個(gè)),并將它們存儲在一個(gè)名為?pokemon.csv?的 CSV 文件中。?store_results?函數(shù)不影響順序或并行模式下的抓取。你可以跳過它。

由于結(jié)果是列表,我們必須將它們展平以允許?writerows?完成其工作。這就是為什么我們將變量命名為list_of_lists(即使它有點(diǎn)奇怪),只是為了提醒大家它不是扁平的。

輸出 CSV 文件的示例:

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

如果您要為每個(gè)頁面 (48) 運(yùn)行腳本,它將生成一個(gè)包含 755 個(gè)產(chǎn)品的 CSV 文件,并花費(fèi)大約 30 秒。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

asyncio 介紹

我們知道我們可以做得更好。如果我們同時(shí)執(zhí)行所有請求,它應(yīng)該花費(fèi)更少時(shí)間,對吧?也許會和執(zhí)行最慢的請求所花費(fèi)的時(shí)間相等。

并發(fā)確實(shí)應(yīng)該運(yùn)行得更快,但它也涉及一些開銷。所以這不是線性的數(shù)學(xué)改進(jìn)。

為此,我們將使用上面提到的?asyncio。它允許我們在事件循環(huán)中的同一個(gè)線程上運(yùn)行多個(gè)任務(wù)(就像 Javascript 一樣)。它將運(yùn)行一個(gè)函數(shù),并在運(yùn)行時(shí)允許時(shí)將上下文切換到不同的上下文。在我們的例子中,HTTP 請求允許這種切換。

我們將開始看到一個(gè) sleep 一秒鐘的示例。并且腳本應(yīng)該需要一秒鐘才能運(yùn)行。請注意,我們不能直接調(diào)用?main。我們需要讓?asyncio?知道它是一個(gè)需要執(zhí)行的異步函數(shù)。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

簡單的并行代碼

接下來,我們將擴(kuò)展一個(gè)示例案例來運(yùn)行一百個(gè)函數(shù)。它們每個(gè)都會 sleep 一秒鐘并打印一個(gè)文本。如果我們按順序運(yùn)行它們大約需要一百秒。使用?asyncio,只需要一秒!

這就是并發(fā)背后的力量。如前所述,對于純 I/O 密集型任務(wù),它將執(zhí)行得更快 - sleep 不是,但它對示例很重要。

我們需要?jiǎng)?chuàng)建一個(gè)輔助函數(shù),它會 sleep 一秒鐘并打印一條消息。然后,我們編輯?main 以調(diào)用該函數(shù)一百次,并將每個(gè)調(diào)用存儲在一個(gè)任務(wù)列表中。最后也是關(guān)鍵的部分是執(zhí)行并等待所有任務(wù)完成。這就是?asyncio.gather?所做的事情。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

正如預(yù)期的那樣,一百條消息和一秒鐘的執(zhí)行時(shí)間。完美!

使用 asyncio 進(jìn)行抓取

我們需要將這些知識應(yīng)用于數(shù)據(jù)抓取。遵循的方法是同時(shí)請求并返回產(chǎn)品列表,并在所有請求完成后存儲它們。每次請求后或者分批保存數(shù)據(jù)可能會更好,以避免實(shí)際情況下的數(shù)據(jù)丟失。

我們的第一次嘗試不會有并發(fā)限制,所以使用時(shí)要小心。在使用數(shù)千個(gè) URL 運(yùn)行它的情況下......好吧,它幾乎會同時(shí)執(zhí)行所有這些請求。這可能會給服務(wù)器帶來巨大的負(fù)載,并可能會損害您的計(jì)算機(jī)。

requests 不支持開箱即用的異步,因此我們將使用?aiohttp?來避免復(fù)雜化。?requests?可以完成這項(xiàng)工作,并且沒有實(shí)質(zhì)性的性能差異。但是使用?aiohttp?代碼更具可讀性。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

CSV 文件應(yīng)該像以前一樣包含每個(gè)產(chǎn)品的信息 (共 755 個(gè))。由于我們同時(shí)執(zhí)行所有頁面調(diào)用,結(jié)果不會按順序到達(dá)。如果我們將結(jié)果添加到?extract_details?內(nèi)的文件中,它們可能是無序的。但我們會等待所有任務(wù)完成然后處理它們,因此順序性不會有太大影響。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

我們做到了!速度提升了 3 倍,但是……不應(yīng)該是 40 倍嗎?沒那么簡單。許多因素都會影響性能(網(wǎng)絡(luò)、CPU、RAM 等)。

在這個(gè)演示頁面中,我們注意到當(dāng)執(zhí)行多個(gè)調(diào)用時(shí),響應(yīng)時(shí)間會變慢,這可能是設(shè)計(jì)使然。一些服務(wù)器/提供商可以限制并發(fā)請求的數(shù)量,以避免來自同一 IP 的過多流量。它不是一種阻塞,而是一個(gè)隊(duì)列。你會得到服務(wù)響應(yīng),但需要稍等片刻。

要查看真正的加速,您可以針對延遲[6]頁面進(jìn)行測試。這是另一個(gè)測試頁面,它將等待 2 秒然后返回響應(yīng)。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

這里去掉了所有的提取和存儲邏輯,只調(diào)用了延遲 URL 48 次,并在 3 秒內(nèi)運(yùn)行完畢。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

使用信號量限制并發(fā)

如上所述,我們應(yīng)該限制并發(fā)請求的數(shù)量,尤其是針對單個(gè)域名。

asyncio 帶有 Semaphore,一個(gè)將獲取和釋放鎖的對象。它的內(nèi)部功能將阻塞一些調(diào)用,直到獲得鎖,從而創(chuàng)建最大的并發(fā)性。

我們需要?jiǎng)?chuàng)建盡可能最大值的信號量。然后等待提取函數(shù)運(yùn)行,直到?async with sem?可用。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

它完成了工作,并且相對容易實(shí)現(xiàn)!這是最大并發(fā)設(shè)置為 3 的輸出。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

這表明無限并發(fā)的版本并沒有全速運(yùn)行。如果我們將限制增加到 10,總時(shí)間與未限制的腳本運(yùn)行時(shí)間相近。

使用 TCPConnector 限制并發(fā)

aiohttp 提供了一種替代解決方案,可提供進(jìn)一步的配置。我們可以創(chuàng)建傳入自定義?TCPConnector?的客戶端會話。

我們可以使用兩個(gè)適合我們需求的參數(shù)來構(gòu)建它:

  • limit?- “同時(shí)連接的總數(shù)”。
  • limit_per_host?- “限制同時(shí)連接到同一端點(diǎn)的連接數(shù)”(同一主機(jī)、端口和?is_ssl)。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

這種寫法也易于實(shí)施和維護(hù)!這是每個(gè)主機(jī)最大并發(fā)設(shè)置為 3 的輸出。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

與?Semaphore?相比的優(yōu)勢是可以選擇限制每個(gè)域的并發(fā)調(diào)用和請求的總量。我們可以使用同一個(gè)會話來抓取不同的站點(diǎn),每個(gè)站點(diǎn)都有自己的限制。

缺點(diǎn)是它看起來有點(diǎn)慢。需要針對真實(shí)案例,使用更多頁面和實(shí)際數(shù)據(jù)運(yùn)行一些測試。

multiprocessing

就像我們之前看到的那樣,數(shù)據(jù)抓取是 I/O 密集型的。但是,如果我們需要將它與一些 CPU 密集型計(jì)算混合怎么辦?為了測試這種情況,我們將使用一個(gè)函數(shù),該函數(shù)將在每個(gè)抓取的頁面之后?count_a_lot。這是強(qiáng)制 CPU 忙碌一段時(shí)間的簡單(且有些愚蠢)的方法。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

對于 asyncio 版本,只需像以前一樣運(yùn)行它??赡苄枰荛L時(shí)間?。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

現(xiàn)在,比較難理解的部分來了:

直接引入?multiprocessing?看起來有點(diǎn)困難。實(shí)際上,我們需要?jiǎng)?chuàng)建一個(gè)?ProcessPoolExecutor,它能夠“使用一個(gè)進(jìn)程池來異步執(zhí)行調(diào)用”。它將處理不同 CPU 中每個(gè)進(jìn)程的創(chuàng)建和控制。

但它不會分配負(fù)載。為此,我們將使用?NumPy?的?array_split,它會根據(jù) CPU 的數(shù)量將頁面范圍分割成相等的塊。

main?函數(shù)的其余部分類似于?asyncio?版本,但更改了一些語法以匹配?multiprocessing?的語法風(fēng)格。

此處的本質(zhì)區(qū)別是我們不會直接調(diào)用extract_details。實(shí)際上是可以的,但我們將嘗試通過將?multiprocessing?與?asyncio?混合使用來獲得最好的執(zhí)行效率。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

長話短說,每個(gè) CPU 進(jìn)程都會有幾頁需要抓取。一共有 48 個(gè)頁面,假設(shè)你的機(jī)器有 8 個(gè) CPU,每個(gè)進(jìn)程將請求 6 個(gè)頁面(6 * 8 = 48)。

這六個(gè)頁面將同時(shí)運(yùn)行!之后,計(jì)算將不得不等待,因?yàn)樗鼈兪?CPU 密集型的。但是我們有很多 CPU,所以它們應(yīng)該比純 asyncio 版本運(yùn)行得更快。

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

這就是神奇的地方。每個(gè) CPU 進(jìn)程將使用頁面的子集啟動(dòng)一個(gè) asyncio(例如,第一個(gè)頁面從 1 到 6)。

然后,每一個(gè)都將調(diào)用幾個(gè) URL,使用已知的?extract_details?函數(shù)。

上述內(nèi)容需要花點(diǎn)時(shí)間來吸收它。整個(gè)過程是這樣的:

  1. 創(chuàng)建執(zhí)行器
  2. 拆分頁面
  3. 每個(gè)進(jìn)程啟動(dòng) asyncio
  4. 創(chuàng)建一個(gè)?aiohttp?會話并創(chuàng)建頁面子集的任務(wù)
  5. 提取每一頁的數(shù)據(jù)
  6. 合并并存儲結(jié)果

下面是本次的執(zhí)行時(shí)間。雖然之前我們沒有提到它,但這里的?user?時(shí)間卻很顯眼。對于僅運(yùn)行 asyncio 的腳本:

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

具有?asyncio?和多個(gè)進(jìn)程的版本:

抓取速度提升 3 倍!Python 的這個(gè)內(nèi)置庫你用上了嗎?

發(fā)現(xiàn)區(qū)別了嗎?實(shí)際運(yùn)行時(shí)間方面第一個(gè)用了兩分鐘多,第二個(gè)用了 40 秒。但是在總 CPU 時(shí)間(user?時(shí)間)中,第二個(gè)超過了三分鐘!看起來系統(tǒng)開銷的耗時(shí)確實(shí)有點(diǎn)多。

這表明并行處理“浪費(fèi)”了更多時(shí)間,但程序是提前完成的。顯然,您在決定選擇哪種方法時(shí),需要考慮到開發(fā)和調(diào)試的復(fù)雜度。

結(jié)論

我們已經(jīng)看到?asyncio?足以用于抓取,因?yàn)榇蟛糠诌\(yùn)行時(shí)間都用于網(wǎng)絡(luò)請求,這種場景屬于 I/O 密集型并且適用于單核中的并發(fā)處理。

如果收集的數(shù)據(jù)需要一些 CPU 密集型工作,這種情況就會改變。雖然有關(guān)計(jì)數(shù)的例子有一點(diǎn)愚蠢,但至少你理解了這種場景。

在大多數(shù)情況下,帶有?aiohttp?的?asyncio?比異步的?requests?更適合完成目標(biāo)工作。同時(shí)我們可以添加自定義連接器以限制每個(gè)域名的請求數(shù)、并發(fā)請求總數(shù)。有了這三個(gè)部分,您就可以開始構(gòu)建一個(gè)可以擴(kuò)展的數(shù)據(jù)抓取程序了。

另一個(gè)重要的部分是允許新的 URL/任務(wù)加入程序運(yùn)行(類似于隊(duì)列),但這是另一篇文章的內(nèi)容。敬請關(guān)注!

原文:https://mp.weixin.qq.com/s/4cOpRpwUV0UJs0xaQVxYbw

相關(guān)新聞

歷經(jīng)多年發(fā)展,已成為國內(nèi)好評如潮的Linux云計(jì)算運(yùn)維、SRE、Devops、網(wǎng)絡(luò)安全、云原生、Go、Python開發(fā)專業(yè)人才培訓(xùn)機(jī)構(gòu)!