??引言
在網(wǎng)絡(luò)爬蟲的世界里,效率是關(guān)鍵。為了快速地獲取大量數(shù)據(jù),我們需要運(yùn)用一些高級技巧,如多線程和多進(jìn)程。在本篇博客中,我們將學(xué)習(xí)如何使用Python的多線程和多進(jìn)程來構(gòu)建一個高效的網(wǎng)絡(luò)爬蟲,以便更快速地獲取目標(biāo)網(wǎng)站上的信息。
??為什么要使用多線程和多進(jìn)程?
在單線程爬蟲中,我們按照順序一個個頁面地下載和解析數(shù)據(jù)。這在小型網(wǎng)站上可能沒有問題,但在處理大規(guī)模數(shù)據(jù)時會變得非常緩慢。多線程和多進(jìn)程可以幫助我們同時處理多個頁面,從而提高爬蟲的效率。
-
多線程:在一個進(jìn)程內(nèi),多個線程可以并發(fā)執(zhí)行,共享相同的內(nèi)存空間。這意味著它們可以更快速地完成任務(wù),但需要小心線程安全問題。
-
多進(jìn)程:多個進(jìn)程是獨(dú)立的,每個進(jìn)程都有自己的內(nèi)存空間。這可以避免線程安全問題,但在創(chuàng)建和管理進(jìn)程時會產(chǎn)生額外的開銷。
??線程的常用方法
-
threading.Thread(target, args=(), kwargs={}):創(chuàng)建一個線程對象,用于執(zhí)行指定的目標(biāo)函數(shù)。
target:要執(zhí)行的目標(biāo)函數(shù)。
args:目標(biāo)函數(shù)的位置參數(shù),以元組形式傳遞。
kwargs:目標(biāo)函數(shù)的關(guān)鍵字參數(shù),以字典形式傳遞。 -
thread.start():啟動線程,使其開始執(zhí)行目標(biāo)函數(shù)。
-
thread.join(timeout=None):等待線程完成執(zhí)行??蛇x的timeout參數(shù)用于指定最長等待時間,如果超時,將繼續(xù)執(zhí)行主線程。
-
thread.is_alive():檢查線程是否在運(yùn)行。如果線程仍在執(zhí)行中,返回True;否則返回False。
-
threading.current_thread():返回當(dāng)前線程對象,可以用于獲取當(dāng)前線程的信息。
-
threading.active_count():返回當(dāng)前活動線程的數(shù)量。
-
threading.enumerate():返回當(dāng)前所有活動線程的列表。
-
threading.Thread.getName()和threading.Thread.setName(name):獲取和設(shè)置線程的名稱。
-
threading.Thread.isDaemon()和threading.Thread.setDaemon(daemonic):獲取和設(shè)置線程的守護(hù)狀態(tài)。守護(hù)線程在主線程結(jié)束時會被強(qiáng)制終止。
-
threading.Thread.ident:獲取線程的唯一標(biāo)識符。
-
threading.Lock():創(chuàng)建一個互斥鎖對象,用于實(shí)現(xiàn)線程同步。
-
lock.acquire():獲取鎖,進(jìn)入臨界區(qū)。
-
lock.release():釋放鎖,退出臨界區(qū)。
-
.sleep() 方法是線程對象(threading.Thread)中的一個非常常用的方法之一,它用于使線程暫停執(zhí)行一段指定的時間,但是用到的庫是time
??線程鎖(也稱為互斥鎖或簡稱鎖)
用于線程間的同步,主要有以下兩個目的:
-
防止競爭條件(Race Condition): 競爭條件指的是多個線程在同時訪問共享資源時,由于執(zhí)行順序不確定而導(dǎo)致的不確定性和錯誤。當(dāng)多個線程嘗試同時修改共享數(shù)據(jù)時,可能會導(dǎo)致數(shù)據(jù)不一致性和錯誤結(jié)果。線程鎖可以防止競爭條件,使得只有一個線程能夠訪問共享資源,其他線程需要等待鎖釋放。
-
確保數(shù)據(jù)一致性: 在多線程環(huán)境下,當(dāng)多個線程同時訪問和修改共享數(shù)據(jù)時,可能會導(dǎo)致數(shù)據(jù)的不一致性。使用鎖可以確保在任何時候只有一個線程能夠訪問和修改共享數(shù)據(jù),從而確保數(shù)據(jù)的一致性。
這里可以舉個例子:
當(dāng)我們在銀行取錢的時候,如果沒有線程鎖的保護(hù),一大堆人同時取錢,那么余額就不好計(jì)算了。所以在多個線程同時進(jìn)行存款和取款操作,但由于線程鎖的保護(hù),這些操作不會導(dǎo)致賬戶數(shù)據(jù)的混亂或錯誤。最后,我們打印出最終的賬戶余額,以確保數(shù)據(jù)一致性。
??小案例
這里我們通過一個小案例感受一下多線程的魅力吧~
import time
import threading
import requests
from bs4 import BeautifulSoup
urls = []
for j in range(1, 5):
url = f'https://example.com/page{j}'
urls.append(url)
# 存儲字典
results = {}
lock = threading.Lock()
def func(url):
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
title = soup.title.string
# 使用線程鎖確保字典操作的線程安全
with lock:
results[url] = title
# 創(chuàng)建線程列表
threads = []
start = time.time()
# 創(chuàng)建并啟動線程
for url in urls:
thread = threading.Thread(target=func, args=(url,))
threads.append(thread)
thread.start()
# 等待所有線程完成
for thread in threads:
thread.join()
stop = time.time()
# 打印爬取結(jié)果
for url, title in results.items():
print(f'URL: {url}, Title: {title}')
print(stop-start)
這里我們用測試網(wǎng)頁中的title
在這個示例中,我們定義了一個包含多個網(wǎng)頁url的列表 urls,然后創(chuàng)建了多個線程來并發(fā)地爬取這些網(wǎng)頁的標(biāo)題。每個線程使用 requests 庫發(fā)送請求,解析網(wǎng)頁內(nèi)容,提取標(biāo)題,并將結(jié)果存儲在一個共享的 results 字典中。為了確保字典操作的線程安全,我們使用了一個線程鎖 lock。最后用time測試了一次時間。
??實(shí)戰(zhàn)—手辦網(wǎng)
先將之前相關(guān)爬取手辦網(wǎng)放在下方
# 導(dǎo)入模塊
import requests
from bs4 import BeautifulSoup
import time
import threading
# 定義url和請求頭
_headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"Cookie": "utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105"
}
"""
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=1
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=2
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=3
"""
start = time.time()
# 獲取前五頁的url
urls = []
for i in range(1,5):
url ='https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page={}'.format(i)
urls.append(url)
items = []
for d_url in urls:
# 發(fā)送請求
response = requests.get(d_url, headers=_headers)
content = response.content.decode('utf8')
# 實(shí)例化對象
soup = BeautifulSoup(content, 'lxml')
# 名稱
data = soup.find_all('ul',class_="hpoi-glyphicons-list")
for i in data:
data_1 = i.find_all('li')
for j in data_1:
data_2 = j.find_all('div',class_="hpoi-detail-grid-right")
for k in data_2:
title = k.find_all('a')[0].string
changshang = k.find_all('span')[0].text[3:]
chuhe = k.find_all('span')[1].text[3:]
price = k.find_all('span')[2].text[3:]
data_3 = {
"名稱": title,
"廠商":changshang,
"出荷":chuhe,
"價位":price
}
items.append(data_3)
print(items)
stop = time.time()
print(stop-start)
運(yùn)行結(jié)果如下
之后我們使用多線程進(jìn)行實(shí)現(xiàn)
# 導(dǎo)入模塊
import requests
from bs4 import BeautifulSoup
import time
import threading
# 定義url和請求頭
_headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"Cookie": "utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105utoken=UTKbd23efb1729a444898977cf2a91381c0; JSESSIONID=9EF5C8BA4F3C3E29278A9972A946408A; Hm_lvt_05b494824003cbf80b23e846462269d1=1688387237,1688712147,1689130492,1689145041; allOrder=release; Hm_lpvt_05b494824003cbf80b23e846462269d1=1689145105"
}
"""
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=1
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=2
https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page=3
"""
start = time.time()
# 獲取前五頁的url
items = []
def shouban(url):
# for d_url in urls:
# 發(fā)送請求
response = requests.get(url, headers=_headers)
content = response.content.decode('utf8')
# 實(shí)例化對象
soup = BeautifulSoup(content, 'lxml')
# 名稱
data = soup.find_all('ul',class_="hpoi-glyphicons-list")
for i in data:
data_1 = i.find_all('li')
for j in data_1:
data_2 = j.find_all('div',class_="hpoi-detail-grid-right")
for k in data_2:
title = k.find_all('a')[0].string
changshang = k.find_all('span')[0].text[3:]
chuhe = k.find_all('span')[1].text[3:]
price = k.find_all('span')[2].text[3:]
data_3 = {
"名稱": title,
"廠商":changshang,
"出荷":chuhe,
"價位":price
}
items.append(data_3)
urls = []
t_list = []
for i in range(1,5):
url =f'https://www.hpoi.net/hobby/all?order=release&r18=-1&workers=&view=3&category=100&page={i}'
t1 = threading.Thread(target=shouban, args=(url,))
t1.start()
t_list.append(t1)
for i in t_list:
i.join()
stop = time.time()
print(items)
print(stop-start)
運(yùn)行結(jié)果如下
注意:可以適當(dāng)添加sleep,防止被封,如果爬取大量數(shù)據(jù),多線程表現(xiàn)的會更明顯一點(diǎn)
??總結(jié)
通過使用多線程和多進(jìn)程,我們可以顯著提高網(wǎng)絡(luò)爬蟲的效率,更快地獲取大量數(shù)據(jù)。然而,要小心線程安全問題和進(jìn)程管理的開銷。在實(shí)際項(xiàng)目中,還需要考慮異常處理、數(shù)據(jù)存儲等更多細(xì)節(jié),感謝看到結(jié)尾的小伙伴,感謝您的支持!
文章來源:http://www.zghlxwxcb.cn/news/detail-723890.html
挑戰(zhàn)與創(chuàng)造都是很痛苦的,但是很充實(shí)。文章來源地址http://www.zghlxwxcb.cn/news/detail-723890.html
到了這里,關(guān)于Python實(shí)戰(zhàn):用多線程和多進(jìn)程打造高效爬蟲的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!