欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python進(jìn)階篇之多線程爬取網(wǎng)頁(yè)

 更新時(shí)間:2021年10月21日 15:43:18   作者:HuiSoul  
這篇文章主要為大家介紹了Python進(jìn)階中利用多線程來(lái)爬取網(wǎng)頁(yè)的示例實(shí)現(xiàn)及解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步

一、前情提要

相信來(lái)看這篇深造爬蟲文章的同學(xué),大部分已經(jīng)對(duì)爬蟲有不錯(cuò)的了解了,也在之前已經(jīng)寫過(guò)不少爬蟲了,但我猜爬取的數(shù)據(jù)量都較小,因此沒(méi)有過(guò)多的關(guān)注爬蟲的爬取效率。這里我想問(wèn)問(wèn)當(dāng)我們要爬取的數(shù)據(jù)量為幾十萬(wàn)甚至上百萬(wàn)時(shí),我們會(huì)不會(huì)需要要等幾天才能將數(shù)據(jù)全都爬取完畢呢?

唯一的辦法就是讓爬蟲可以 7×24 小時(shí)不間斷工作。因此我們能做的就是多叫幾個(gè)爬蟲一起來(lái)爬數(shù)據(jù),這樣便可大大提升爬蟲的效率。

但在介紹Python 如何讓多個(gè)爬蟲一起爬取數(shù)據(jù)之前,我想先為大家介紹一個(gè)概念——并發(fā)。

二、并發(fā)的概念

為了讓大家簡(jiǎn)單易懂,我就用例子代替復(fù)雜的文章來(lái)向大家介紹吧

第一個(gè)例子
我們用 requests 成功請(qǐng)求一個(gè)網(wǎng)頁(yè),實(shí)際上 requests 做了三件事:
1、根據(jù)鏈接、參數(shù)等組合成一個(gè)請(qǐng)求;
2、把這個(gè)請(qǐng)求發(fā)往要爬取的網(wǎng)站,等待網(wǎng)站響應(yīng);
3、網(wǎng)站響應(yīng)后,把結(jié)果包裝成一個(gè)響應(yīng)對(duì)象方便我們使用。

在這里插入圖片描述

其中步驟 2 花費(fèi)的時(shí)間是最長(zhǎng)的,取決于被爬網(wǎng)站的性能,這個(gè)時(shí)間可能達(dá)到幾十到幾百毫秒。

對(duì)這個(gè)程序來(lái)說(shuō):綠色部分代表代碼是在 運(yùn)行 的,黃色部分(步驟 2)代表程序是 空閑 的,因?yàn)樵诘却W(wǎng)站響應(yīng)。 所以,爬蟲代碼真正運(yùn)行的時(shí)間很短,大部分時(shí)間都浪費(fèi)在等待網(wǎng)站響應(yīng)上了。

第二個(gè)例子
我們連續(xù)用 requests 請(qǐng)求三個(gè)網(wǎng)頁(yè) A、B、C,執(zhí)行的過(guò)程如下圖所示:

在這里插入圖片描述

同樣的,每次步驟 1、3 和 2 所花費(fèi)時(shí)間的差異很大。我們假設(shè)步驟 1 和步驟 3 都要花費(fèi) 1 毫秒,步驟 2 要花費(fèi) 98 毫秒。那么一個(gè)網(wǎng)頁(yè)要花費(fèi) 100 毫秒,爬取 A、B、C 三個(gè)網(wǎng)頁(yè)一共花費(fèi)了 300 毫秒。

這時(shí)我們其實(shí)遇到一個(gè)問(wèn)題:整個(gè)過(guò)程的 300 毫秒里,代碼運(yùn)行的時(shí)間只有 6 毫秒,剩下有 294 毫秒我們的程序只是空閑在那里等待著網(wǎng)站響應(yīng)。

第三個(gè)例子

想一想,第一個(gè)例子里,順序必須是 1-2-3,因?yàn)椴襟E 2 依賴步驟 1 的結(jié)果,步驟 3 依賴步驟 2 的結(jié)果。但是第二個(gè)例子里,步驟為什么必須是 A1-A2-A3-B1-B2-B3-C1-C2-C3 呢?「爬取網(wǎng)頁(yè) B」的步驟 1 其實(shí)和「爬取網(wǎng)頁(yè) A」的步驟 3 并沒(méi)有依賴關(guān)系。

在這里插入圖片描述

這張圖是什么意思呢?其實(shí)就是:在「爬取網(wǎng)頁(yè) A」這個(gè)過(guò)程進(jìn)行到步驟 2 的時(shí)候,程序空閑下來(lái)了,這時(shí)我們讓「爬取網(wǎng)頁(yè) B」的步驟 1 開(kāi)始執(zhí)行;同樣的,「爬取網(wǎng)頁(yè) B」的步驟 1 執(zhí)行完,程序又空閑下來(lái),于是我們安排「爬取網(wǎng)頁(yè) C」開(kāi)始執(zhí)行。

依然假設(shè)步驟 1 和 3 需要花費(fèi) 1 毫秒,步驟 2 花費(fèi) 98 毫秒。算一算,只需要102 毫秒!
我們要爬 10 個(gè)或者 20 個(gè)網(wǎng)頁(yè),現(xiàn)在預(yù)計(jì)分別只需要 109 毫秒和 119 毫秒。而假如我們用第二個(gè)例子里的方式運(yùn)行,則分別需要 1000 毫秒和 2000 毫秒!

可以看到,我們僅僅是利用了爬蟲等待網(wǎng)站響應(yīng)的空閑時(shí)間,爬蟲的效率就提升了數(shù)十倍。當(dāng)爬取數(shù)據(jù)量更大時(shí),爬蟲效率提升會(huì)更加的顯著。

回到問(wèn)題:什么叫并發(fā)?

上面第二個(gè)例子就不是并發(fā):我要做三件事,然后我一件一件完成它們。

上面的第三個(gè)例子就是并發(fā):我們明明要做三件事,但是在這段時(shí)間內(nèi),我們交錯(cuò)著做這三件事,就好像在 同時(shí)做這些事 !

而上面第一個(gè)例子里,我們只需要做一件事情,這時(shí)不管我們寫并發(fā)的代碼或者普通的代碼,它總是步驟 1-2-3 這樣被執(zhí)行完,沒(méi)有什么區(qū)別。

上面第三種例子這種情況,在計(jì)算機(jī)中被稱為并發(fā)

讓我們用一段代碼,來(lái)讓大家直觀的看看并發(fā)是什么:

import time
import requests
class Adapter(requests.adapters.HTTPAdapter):
  def send(self, *args, **kwargs):
    global start
    print(
      "步驟 1 結(jié)束,耗時(shí)",
      round((time.time() - start) * 1000),
      "毫秒"
    )
    return super().send(*args, **kwargs)
s = requests.Session()
s.mount("https://", Adapter())
start = time.time()
r = s.get('https://www.baidu.com')
end = time.time()
print(
  "步驟 2 結(jié)束,耗時(shí)",
  round(r.elapsed.total_seconds() * 1000),
  "毫秒"
)
print(
  "步驟 3 結(jié)束,耗時(shí)",
  int((end -start - r.elapsed.total_seconds()) * 1000),
  "毫秒"
)
//輸出結(jié)果↓
//步驟 1 結(jié)束,耗時(shí) 2 毫秒
//步驟 2 結(jié)束,耗時(shí) 66 毫秒
//步驟 3 結(jié)束,耗時(shí) 1 毫秒

通過(guò)以上的講解,相信大家已經(jīng)對(duì)并發(fā)有一個(gè)初步的認(rèn)識(shí)了,接下來(lái)我們?cè)賮?lái)講講多線程

三、并發(fā)與多線程

操作系統(tǒng)為我們提供了兩個(gè)東西:進(jìn)程和線程。利用這兩樣?xùn)|西,我們可以輕易地實(shí)現(xiàn)代碼的并發(fā),而不用考慮細(xì)枝末節(jié)。

例如,我們把下面三個(gè)任務(wù)丟到三個(gè)線程中,操作系統(tǒng)就能讓任務(wù)A等待時(shí),啟動(dòng)任務(wù)B,任務(wù)AB等待時(shí),啟動(dòng)任務(wù)C,而當(dāng)任務(wù)A等待結(jié)束了,接著回去完成任務(wù)A,以此類推,在最短的時(shí)間內(nèi)完成所有的任務(wù),而不用擠占時(shí)間。

在這里插入圖片描述

我們來(lái)比較一下,有用多線程和沒(méi)有用多線程的爬蟲程序的耗時(shí)究竟相差多少!

import time
import requests
# 導(dǎo)入 concurrent.futures 這個(gè)包
from concurrent import futures

# 假設(shè)我們要爬取 30 個(gè)網(wǎng)頁(yè)
urls = ["https://wpblog.x0y1.com/?p=34"] * 30
session = requests.Session()

# 普通爬蟲
start1 = time.time()
results = []
for url in urls:
  r = session.get(url)
  results.append(r.text)

end1 = time.time()
print("普通爬蟲耗時(shí)", end1-start1, "秒")

# 多線程爬蟲
# 初始化一個(gè)線程池,最大的同時(shí)任務(wù)數(shù)是 5
executor = futures.ThreadPoolExecutor(max_workers=5)
start2 = time.time()
fs = []
for url in urls:
  # 提交任務(wù)到線程池
  f = executor.submit(session.get, url)
  fs.append(f)

# 等待這些任務(wù)全部完成
futures.wait(fs)
# 獲取任務(wù)的結(jié)果
result = [f.result().text for f in fs]
end2 = time.time()
print("多線程爬蟲耗時(shí)", end2-start2, "秒")

#輸出結(jié)果↓  耗時(shí)與線上環(huán)境和硬件條件有關(guān)
#普通爬蟲耗時(shí) 3.626128673553467 秒
#多線程爬蟲耗時(shí) 2.0856518745422363 秒

看到結(jié)果對(duì)比之后就會(huì)知道,通常情況下多線程爬蟲的效率會(huì)比單線程高很多。而且需要處理的任務(wù)量越多的時(shí)候,這個(gè)差異會(huì)越明顯。

好,我們?cè)賮?lái)仔細(xì)解讀一下這部分多線程爬蟲代碼,我們?nèi)〕鲫P(guān)鍵部分看看

# 導(dǎo)入 concurrent.futures 這個(gè)包
from concurrent import futures

# 初始化一個(gè)線程池,最大的同時(shí)任務(wù)數(shù)是 5
executor = futures.ThreadPoolExecutor(max_workers=5)

concurrent是 Python 自帶的庫(kù),這個(gè)庫(kù)具有線程池和進(jìn)程池、管理并行編程任務(wù)、處理非確定性的執(zhí)行流程、進(jìn)程/線程同步等功能。
executor 就是我們剛剛初始化的線程池,我們調(diào)用 executor 的 submit() 方法往里面提交任務(wù)。第一個(gè)參數(shù) session.get 是提交要運(yùn)行的函數(shù),第二個(gè)參數(shù) url 是提交的函數(shù)運(yùn)行時(shí)的參數(shù)。

fs = []
for url in urls:
  # 提交任務(wù)到線程池
  f = executor.submit(session.get, url)
  fs.append(f)

executor 就是我們剛剛初始化的線程池,我們調(diào)用 executor 的 submit() 方法往里面提交任務(wù)。第一個(gè)參數(shù) session.get 是提交要運(yùn)行的函數(shù),第二個(gè)參數(shù) url 是提交的函數(shù)運(yùn)行時(shí)的參數(shù)。
executor.submit() 方法會(huì)給我們一個(gè)返回值,它是一個(gè) future 對(duì)象,我們把它賦值給變量 f。

# 等待這些任務(wù)全部完成
futures.wait(fs)

fs 是保存了上面所有任務(wù)的 future 對(duì)象的列表,futures.wait() 方法可以等待直到 fs 里面所有的 future 對(duì)象都有結(jié)果為止。

# 獲取任務(wù)的結(jié)果
result = [f.result().text for f in fs]

fs 是保存了上面所有任務(wù)的 future 對(duì)象的列表,我們遍歷所有任務(wù)的 future 對(duì)象,調(diào)用 future 對(duì)象的 result() 方法,就能得到任務(wù)的結(jié)果。
那結(jié)果是什么類型的呢?取決于提交的任務(wù)。比如我們提交的是 session.get(url),它的返回值是一個(gè) response 對(duì)象,那我們調(diào)用它的 text 屬性就能得到響應(yīng)的完整內(nèi)容了。

四、線程池

前面我們講過(guò),線程是操作系統(tǒng)提供給我們的能力,可以把不同的任務(wù)放到不同的線程里,這樣它們可以同時(shí)運(yùn)行。但是這個(gè)能力一定是有限的,并不能無(wú)止境的制造線程。如果運(yùn)行的線程數(shù)太多,操作系統(tǒng)在安排這些線程的執(zhí)行順序等事情上要花費(fèi)很大的代價(jià)。

我們先來(lái)回憶一下一開(kāi)始的第三個(gè)例子,在這個(gè)例子里,之所以切換到第二個(gè)任務(wù)可以提高我們的效率,是因?yàn)榈谝粋€(gè)任務(wù)已經(jīng)處于空閑狀態(tài)。

在這里插入圖片描述

但假如我們的線程數(shù)非常多,步驟 1 可以一直往圖的右下堆疊,直到占滿了空閑時(shí)間。這時(shí)再加線程對(duì)爬蟲而言是沒(méi)有意義的,任務(wù)同樣要排隊(duì)來(lái)運(yùn)行。

所以線程池其實(shí)就是限制了最多同時(shí)運(yùn)行的線程數(shù)。比如我們初始化一個(gè)最大任務(wù)數(shù)為 5 的線程池,這樣即使我們提交了 100 任務(wù)到這個(gè)池子里,同時(shí)在運(yùn)行的也只有五個(gè)。而一個(gè)任務(wù)被完成后,也會(huì)被移出線程池騰出空間。所以,用線程池可以避免上面提到的兩個(gè)問(wèn)題。

其實(shí)還有第三個(gè)問(wèn)題,就是考慮到被爬網(wǎng)站的性能和其反爬機(jī)制,我們也不應(yīng)該讓機(jī)器過(guò)快地去運(yùn)行爬蟲。線程池的數(shù)量建議可以在 10 左右,電腦性能好而且不擔(dān)心被爬取網(wǎng)站封禁的可以考慮加到幾十,性能差的可以考慮降到 5。

下一篇文章我會(huì)介紹一個(gè)并發(fā)爬取的項(xiàng)目實(shí)戰(zhàn),希望有需要的同學(xué)來(lái)看看!!
文章鏈接:Python進(jìn)階多線程爬取網(wǎng)頁(yè)項(xiàng)目實(shí)戰(zhàn)

本次分享到此結(jié)束,非常感謝大家閱讀??!
有問(wèn)題歡迎評(píng)論區(qū)留言??!

更多關(guān)于Python多線程爬取網(wǎng)頁(yè)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 對(duì)python GUI實(shí)現(xiàn)完美進(jìn)度條的示例詳解

    對(duì)python GUI實(shí)現(xiàn)完美進(jìn)度條的示例詳解

    今天小編就為大家分享一篇對(duì)python GUI實(shí)現(xiàn)完美進(jìn)度條的示例詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-12-12
  • Python計(jì)算序列相似度的算法實(shí)例

    Python計(jì)算序列相似度的算法實(shí)例

    這篇文章主要介紹了Python計(jì)算序列相似度的算法實(shí)例,求兩個(gè)序列轉(zhuǎn)換的最少交換步驟和最小交換距離,本文提供了部分實(shí)現(xiàn)代碼與解決思路,對(duì)開(kāi)發(fā)非常有幫助,需要的朋友可以參考下
    2023-07-07
  • jupyter .ipynb轉(zhuǎn).py的實(shí)現(xiàn)操作

    jupyter .ipynb轉(zhuǎn).py的實(shí)現(xiàn)操作

    這篇文章主要介紹了jupyter .ipynb轉(zhuǎn).py的實(shí)現(xiàn)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-03-03
  • Pandas實(shí)現(xiàn)groupby分組統(tǒng)計(jì)的實(shí)踐

    Pandas實(shí)現(xiàn)groupby分組統(tǒng)計(jì)的實(shí)踐

    本文主要介紹了Pandas實(shí)現(xiàn)groupby分組統(tǒng)計(jì)的實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Python中Django的URL反向解析

    Python中Django的URL反向解析

    這篇文章主要介紹了Python中Django的URL反向解析,url反向解析是指在視圖或模板中,用path定義的名稱來(lái)動(dòng)態(tài)查找或計(jì)算出相應(yīng)的路由,本文提供了部分實(shí)現(xiàn)代碼與解決思路,需要的朋友可以參考下
    2023-09-09
  • django模型層(model)進(jìn)行建表、查詢與刪除的基礎(chǔ)教程

    django模型層(model)進(jìn)行建表、查詢與刪除的基礎(chǔ)教程

    這篇文章主要給大家介紹了關(guān)于django模型層(model)進(jìn)行建表、查詢與刪除的等基礎(chǔ)操作的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-11-11
  • Python繪制頻率分布直方圖的示例

    Python繪制頻率分布直方圖的示例

    今天小編就為大家分享一篇Python繪制頻率分布直方圖的示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-07-07
  • Python中openpyxl實(shí)現(xiàn)vlookup函數(shù)的實(shí)例

    Python中openpyxl實(shí)現(xiàn)vlookup函數(shù)的實(shí)例

    在本篇文章里小編給大家整理的是關(guān)于Python中openpyxl實(shí)現(xiàn)vlookup函數(shù)的實(shí)例內(nèi)容,有興趣的朋友們可以學(xué)習(xí)參考下。
    2020-10-10
  • 淺析Python中的for 循環(huán)

    淺析Python中的for 循環(huán)

    這篇文章主要介紹了淺析Python中的for 循環(huán)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-06-06
  • 一文帶你吃透Python中的os和sys模塊

    一文帶你吃透Python中的os和sys模塊

    os?模塊是?Python中的一個(gè)內(nèi)置模塊,也是?Python中整理文件和目錄最為常用的模塊。sys?模塊主要負(fù)責(zé)與?Python?解釋器進(jìn)行交互,該模塊提供了一系列用于控制?Python?運(yùn)行時(shí)環(huán)境的不同部分(函數(shù)和變量等)。本文主要來(lái)聊聊這兩個(gè)模塊的使用,希望對(duì)大家有所幫助
    2023-02-02

最新評(píng)論