最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python進階篇之多線程爬取網(wǎng)頁

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

一、前情提要

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

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

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

二、并發(fā)的概念

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

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

在這里插入圖片描述

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

對這個程序來說:綠色部分代表代碼是在 運行 的,黃色部分(步驟 2)代表程序是 空閑 的,因為在等待網(wǎng)站響應(yīng)。 所以,爬蟲代碼真正運行的時間很短,大部分時間都浪費在等待網(wǎng)站響應(yīng)上了。

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

在這里插入圖片描述

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

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

第三個例子

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

在這里插入圖片描述

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

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

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

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

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

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

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

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

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

import time
import requests
class Adapter(requests.adapters.HTTPAdapter):
  def send(self, *args, **kwargs):
    global start
    print(
      "步驟 1 結(jié)束,耗時",
      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é)束,耗時",
  round(r.elapsed.total_seconds() * 1000),
  "毫秒"
)
print(
  "步驟 3 結(jié)束,耗時",
  int((end -start - r.elapsed.total_seconds()) * 1000),
  "毫秒"
)
//輸出結(jié)果↓
//步驟 1 結(jié)束,耗時 2 毫秒
//步驟 2 結(jié)束,耗時 66 毫秒
//步驟 3 結(jié)束,耗時 1 毫秒

通過以上的講解,相信大家已經(jīng)對并發(fā)有一個初步的認(rèn)識了,接下來我們再來講講多線程

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

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

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

在這里插入圖片描述

我們來比較一下,有用多線程和沒有用多線程的爬蟲程序的耗時究竟相差多少!

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

# 假設(shè)我們要爬取 30 個網(wǎng)頁
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("普通爬蟲耗時", end1-start1, "秒")

# 多線程爬蟲
# 初始化一個線程池,最大的同時任務(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("多線程爬蟲耗時", end2-start2, "秒")

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

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

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

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

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

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

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

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

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

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

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

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

四、線程池

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

我們先來回憶一下一開始的第三個例子,在這個例子里,之所以切換到第二個任務(wù)可以提高我們的效率,是因為第一個任務(wù)已經(jīng)處于空閑狀態(tài)。

在這里插入圖片描述

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

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

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

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

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

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

相關(guān)文章

  • python中g(shù)event庫的用法詳情

    python中g(shù)event庫的用法詳情

    這篇文章主要介紹了python中g(shù)event庫的用法詳情,Greenlet全部運行在主程序操作系統(tǒng)的過程中,但是它們是協(xié)作調(diào)度的,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值
    2022-07-07
  • Python?seaborn數(shù)據(jù)可視化繪圖(直方圖,密度圖,散點圖)

    Python?seaborn數(shù)據(jù)可視化繪圖(直方圖,密度圖,散點圖)

    這篇文章主要介紹了Python?seaborn數(shù)據(jù)可視化繪圖(直方圖,密度圖,散點圖),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下
    2022-07-07
  • python框架flask表單實現(xiàn)詳解

    python框架flask表單實現(xiàn)詳解

    這篇文章主要介紹了python框架flask表單實現(xiàn)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-11-11
  • Flask和Django框架中自定義模型類的表名、父類相關(guān)問題分析

    Flask和Django框架中自定義模型類的表名、父類相關(guān)問題分析

    這篇文章主要介紹了Flask和Django框架中自定義模型類的表名、父類相關(guān)問題,結(jié)合實例形式對比分析了Flask框架與Django框架表名定義方式的不同之處,并簡單描述了框架的父類繼承問題,需要的朋友可以參考下
    2018-07-07
  • python生成二維矩陣的兩種方法小結(jié)

    python生成二維矩陣的兩種方法小結(jié)

    本文主要介紹了python生成二維矩陣,包含列表生成m行n列的矩陣和numpy生成想要維度的矩陣的兩種方法,具有一定的參考價值,感興趣的可以了解一下
    2024-08-08
  • Python3 itchat實現(xiàn)微信定時發(fā)送群消息的實例代碼

    Python3 itchat實現(xiàn)微信定時發(fā)送群消息的實例代碼

    使用微信,定時往指定的微信群里發(fā)送指定信息。接下來通過本文給大家分享Python3 itchat實現(xiàn)微信定時發(fā)送群消息的實例代碼,需要的朋友可以參考下
    2019-07-07
  • Python之多線程退出與停止的一種實現(xiàn)思路

    Python之多線程退出與停止的一種實現(xiàn)思路

    這篇文章主要介紹了Python之多線程退出與停止的一種實現(xiàn)思路,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • Windows安裝Anaconda3的方法及使用過程詳解

    Windows安裝Anaconda3的方法及使用過程詳解

    今天帶大家學(xué)習(xí)的是關(guān)于Python的相關(guān)知識,文章圍繞著在windows上Anaconda3的安裝方法及使用展開,文中有非常詳細(xì)的介紹,需要的朋友可以參考下
    2021-06-06
  • 淺析python的優(yōu)勢和不足之處

    淺析python的優(yōu)勢和不足之處

    在本篇內(nèi)容中小編給大家整理了關(guān)于分析python的優(yōu)勢和不足的分析,有需要的朋友們參考下。
    2018-11-11
  • Python編碼解碼之encode()函數(shù)詳解

    Python編碼解碼之encode()函數(shù)詳解

    這篇文章主要給大家介紹了關(guān)于Python編碼解碼之encode()函數(shù)的相關(guān)資料,Python的encode函數(shù)用于將字符串按照指定的編碼方式進行編碼,返回一個bytes類型的對象,需要的朋友可以參考下
    2023-07-07

最新評論

大埔县| 威宁| 淮滨县| 阿克苏市| 卢湾区| 本溪市| 泽普县| 乐昌市| 芒康县| 闽侯县| 宣武区| 梨树县| 万载县| 庆安县| 泉州市| 寿光市| 许昌县| 综艺| 江源县| 兴业县| 郯城县| 洪洞县| 美姑县| 昌平区| 湘潭县| 治县。| 曲松县| 洛川县| 堆龙德庆县| 华亭县| 永宁县| 玉龙| 大荔县| 永清县| 唐海县| 台江县| 久治县| 吉木乃县| 高密市| 通许县| 定西市|