人妖在线一区,国产日韩欧美一区二区综合在线,国产啪精品视频网站免费,欧美内射深插日本少妇

新聞動(dòng)態(tài)

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

發(fā)布日期:2021-12-21 16:10 | 文章來(lái)源:腳本之家

一、前情提要

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

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

但在介紹Python 如何讓多個(gè)爬蟲(chóng)一起爬取數(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)。 所以,爬蟲(chó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 依賴(lài)步驟 1 的結(jié)果,步驟 3 依賴(lài)步驟 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)有依賴(là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 毫秒!

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

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

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

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

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

上面第三種例子這種情況,在計(jì)算機(jī)中被稱(chēng)為并發(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)講講多線(xiàn)程

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

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

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

我們來(lái)比較一下,有用多線(xiàn)程和沒(méi)有用多線(xiàn)程的爬蟲(chóng)程序的耗時(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()
# 普通爬蟲(chóng)
start1 = time.time()
results = []
for url in urls:
  r = session.get(url)
  results.append(r.text)
end1 = time.time()
print("普通爬蟲(chóng)耗時(shí)", end1-start1, "秒")
# 多線(xiàn)程爬蟲(chóng)
# 初始化一個(gè)線(xiàn)程池,最大的同時(shí)任務(wù)數(shù)是 5
executor = futures.ThreadPoolExecutor(max_workers=5)
start2 = time.time()
fs = []
for url in urls:
  # 提交任務(wù)到線(xiàn)程池
  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("多線(xiàn)程爬蟲(chóng)耗時(shí)", end2-start2, "秒")
#輸出結(jié)果↓  耗時(shí)與線(xiàn)上環(huán)境和硬件條件有關(guān)
#普通爬蟲(chóng)耗時(shí) 3.626128673553467 秒
#多線(xiàn)程爬蟲(chóng)耗時(shí) 2.0856518745422363 秒

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

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

# 導(dǎo)入 concurrent.futures 這個(gè)包
from concurrent import futures
# 初始化一個(gè)線(xiàn)程池,最大的同時(shí)任務(wù)數(shù)是 5
executor = futures.ThreadPoolExecutor(max_workers=5)

concurrent是 Python 自帶的庫(kù),這個(gè)庫(kù)具有線(xiàn)程池和進(jìn)程池、管理并行編程任務(wù)、處理非確定性的執(zhí)行流程、進(jìn)程/線(xiàn)程同步等功能。
executor 就是我們剛剛初始化的線(xiàn)程池,我們調(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ù)到線(xiàn)程池
  f = executor.submit(session.get, url)
  fs.append(f)

executor 就是我們剛剛初始化的線(xiàn)程池,我們調(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é)果是什么類(lèi)型的呢?取決于提交的任務(wù)。比如我們提交的是 session.get(url),它的返回值是一個(gè) response 對(duì)象,那我們調(diào)用它的 text 屬性就能得到響應(yīng)的完整內(nèi)容了。

四、線(xiàn)程池

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

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


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

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

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

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

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

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

版權(quán)聲明:本站文章來(lái)源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來(lái)源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來(lái)源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來(lái),僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。

相關(guān)文章

實(shí)時(shí)開(kāi)通

自選配置、實(shí)時(shí)開(kāi)通

免備案

全球線(xiàn)路精選!

全天候客戶(hù)服務(wù)

7x24全年不間斷在線(xiàn)

專(zhuān)屬顧問(wèn)服務(wù)

1對(duì)1客戶(hù)咨詢(xún)顧問(wèn)

在線(xiàn)
客服

在線(xiàn)客服:7*24小時(shí)在線(xiàn)

客服
熱線(xiàn)

400-630-3752
7*24小時(shí)客服服務(wù)熱線(xiàn)

關(guān)注
微信

關(guān)注官方微信
頂部