前言:
??????個(gè)人簡(jiǎn)介:以山河作禮。
??????:Python領(lǐng)域新星創(chuàng)作者,CSDN實(shí)力新星認(rèn)證
?????第一篇文章《1.認(rèn)識(shí)網(wǎng)絡(luò)爬蟲》獲得全站熱榜第一,python領(lǐng)域熱榜第一
。
?? ??第四篇文章《4.網(wǎng)絡(luò)爬蟲—Post請(qǐng)求(實(shí)戰(zhàn)演示)》全站熱榜第八
。
?? ??第八篇文章《8.網(wǎng)絡(luò)爬蟲—正則表達(dá)式RE實(shí)戰(zhàn)》全站熱榜第十二
。
?? ??第十篇文章《10.網(wǎng)絡(luò)爬蟲—MongoDB詳講與實(shí)戰(zhàn)》全站熱榜第八,領(lǐng)域熱榜第二
????《Python網(wǎng)絡(luò)爬蟲》專欄累計(jì)發(fā)表十一篇文章,上榜四篇。歡迎免費(fèi)訂閱!歡迎大家一起學(xué)習(xí),一起成長(zhǎng)??!
????悲索之人烈焰加身,墮落者不可饒恕。永恒燃燒的羽翼,帶我脫離凡間的沉淪。
線程
?? ??上一章節(jié)我們講解了多線程,我們來大致回顧一下,如有疑問,可以閱讀之前文章《網(wǎng)絡(luò)爬蟲—多線程詳講與實(shí)戰(zhàn)》幫助理解。
- Python 線程是輕量級(jí)執(zhí)行單元,它允許程序同時(shí)運(yùn)行多個(gè)線程,每個(gè)線程執(zhí)行不同的任務(wù)。
Python 的線程有兩種實(shí)現(xiàn)方式:使用 threading 模塊或使用 _thread 模塊
。
?? 使用 threading 模塊創(chuàng)建線程:
- 導(dǎo)入 threading 模塊
- 定義一個(gè)函數(shù)作為線程的執(zhí)行體
- 創(chuàng)建一個(gè)線程對(duì)象,將函數(shù)作為參數(shù)傳入
- 調(diào)用 start() 方法啟動(dòng)線程
例如:
import threading
def print_numbers():
for i in range(1, 11):
print(i)
t = threading.Thread(target=print_numbers)
t.start()
?? 使用 _thread 模塊創(chuàng)建線程:
- 導(dǎo)入 _thread 模塊
- 定義一個(gè)函數(shù)作為線程的執(zhí)行體
- 創(chuàng)建一個(gè)線程對(duì)象,使用 _thread.start_new_thread() 方法傳入函數(shù)作為參數(shù)
- 調(diào)用 time.sleep() 方法等待線程執(zhí)行完成
例如:
import _thread
import time
def print_numbers():
for i in range(1, 11):
print(i)
time.sleep(1)
_thread.start_new_thread(print_numbers, ())
time.sleep(10)
隊(duì)列
?? ??Python中的隊(duì)列是一種數(shù)據(jù)結(jié)構(gòu),用于存儲(chǔ)一組有序的元素。它支持兩種基本操作:入隊(duì)和出隊(duì)
。入隊(duì)操作將一個(gè)元素添加到隊(duì)列的末尾,而出隊(duì)操作則從隊(duì)列的開頭移除一個(gè)元素。
Python標(biāo)準(zhǔn)庫中提供了多種隊(duì)列實(shí)現(xiàn),包括:
?? 1. Queue模塊:提供了同步隊(duì)列的實(shí)現(xiàn),支持多線程環(huán)境下的安全操作。
?? 2. deque模塊:提供了雙端隊(duì)列的實(shí)現(xiàn),可以在隊(duì)列的兩端進(jìn)行入隊(duì)和出隊(duì)操作。
?? 3. PriorityQueue模塊:提供了優(yōu)先隊(duì)列的實(shí)現(xiàn),可以按照元素的優(yōu)先級(jí)進(jìn)行出隊(duì)操作。
Queue模塊介紹
**Queue模塊是Python內(nèi)置的線程安全的隊(duì)列模塊,提供了多種隊(duì)列類型,**包括FIFO(先進(jìn)先出)隊(duì)列
、LIFO(后進(jìn)先出)隊(duì)列
和優(yōu)先級(jí)隊(duì)列
。Queue模塊的主要作用是在多線程編程中實(shí)現(xiàn)線程之間的通信和同步。
Queue模塊中的主要類有三種
:
-
Queue類:FIFO隊(duì)列,即先進(jìn)先出隊(duì)列。
-
LifoQueue類:LIFO隊(duì)列,即后進(jìn)先出隊(duì)列。
-
PriorityQueue類:優(yōu)先級(jí)隊(duì)列,可以給每個(gè)元素指定一個(gè)優(yōu)先級(jí),優(yōu)先級(jí)高的元素先出隊(duì)列。
Queue模塊中的主要方法有以下幾個(gè):
1. put(item[, block[, timeout]])
:將item放入隊(duì)列中,如果隊(duì)列已滿,則block為True時(shí)會(huì)阻塞等待,timeout表示等待時(shí)間。
-
get([block[, timeout]])
:從隊(duì)列中取出一個(gè)元素,如果隊(duì)列為空,則block為True時(shí)會(huì)阻塞等待,timeout表示等待時(shí)間。 -
qsize()
:返回隊(duì)列中的元素個(gè)數(shù)。 -
empty()
:判斷隊(duì)列是否為空。 -
full()`:判斷隊(duì)列是否已滿。
-
task_done()
:在完成一項(xiàng)工作之后調(diào)用,用于通知隊(duì)列該項(xiàng)工作已完成。 -
join()
:阻塞調(diào)用線程,直到隊(duì)列中所有的任務(wù)都被處理完畢。
Queue模塊的使用可以簡(jiǎn)化多線程編程,提高程序的可讀性和可維護(hù)性,避免了多線程編程中常見的問題,如競(jìng)爭(zhēng)條件和死鎖等。
下面是一個(gè)使用Queue模塊實(shí)現(xiàn)的隊(duì)列示例:
import queue
# 創(chuàng)建一個(gè)隊(duì)列對(duì)象
q = queue.Queue()
# 入隊(duì)操作
q.put(1)
q.put(2)
q.put(3)
# 出隊(duì)操作
while not q.empty():
print(q.get())
輸出結(jié)果為:
1
2
3
在這個(gè)示例中,我們首先創(chuàng)建了一個(gè)Queue對(duì)象,并使用put()方法將三個(gè)元素加入隊(duì)列。然后使用empty()方法檢查隊(duì)列是否為空,并使用get()方法逐個(gè)取出隊(duì)列中的元素。
線程和隊(duì)列的關(guān)系
?? ??Python中的線程和隊(duì)列是密切相關(guān)的,因?yàn)殛?duì)列是線程之間共享數(shù)據(jù)的一種方式,可以用來在多線程環(huán)境中傳遞信息和實(shí)現(xiàn)線程之間的協(xié)作。
?? 線程可以將數(shù)據(jù)放入隊(duì)列中,其他線程可以從隊(duì)列中獲取數(shù)據(jù)。這種方式可以避免線程之間直接訪問共享變量帶來的并發(fā)問題,保證線程安全。
線程和隊(duì)列的配合使用可以實(shí)現(xiàn)許多并發(fā)編程的場(chǎng)景
,如生產(chǎn)者消費(fèi)者模式、線程池等。生產(chǎn)者消費(fèi)者模式是一種常見的并發(fā)模式
,其中生產(chǎn)者線程生成數(shù)據(jù)并將其放入隊(duì)列中,而消費(fèi)者線程則從隊(duì)列中獲取數(shù)據(jù)并進(jìn)行處理。通過使用隊(duì)列,可以使得生產(chǎn)者和消費(fèi)者線程之間解耦,從而實(shí)現(xiàn)更好的并發(fā)性能和可維護(hù)性。
線程池是另一種常見的并發(fā)模式
,其中線程池維護(hù)一組線程,可以接受任務(wù)并將其放入隊(duì)列中,線程池中的線程可以從隊(duì)列中獲取任務(wù)并進(jìn)行處理。通過使用線程池和隊(duì)列,可以實(shí)現(xiàn)更高效的任務(wù)處理,避免了線程的頻繁創(chuàng)建和銷毀,從而提高了系統(tǒng)的性能和可伸縮性。
生產(chǎn)者消費(fèi)者模式
?? ??Python生產(chǎn)者消費(fèi)者模式是一種多線程編程模式,用于解決并發(fā)編程中的資源競(jìng)爭(zhēng)和線程安全問題。
?? - 在該模式中,生產(chǎn)者線程負(fù)責(zé)生產(chǎn)數(shù)據(jù)并將其放入共享緩沖區(qū)中,而消費(fèi)者線程則從緩沖區(qū)中取出數(shù)據(jù)并進(jìn)行處理。
?? - 生產(chǎn)者和消費(fèi)者線程之間通過一個(gè)共享的緩沖區(qū)進(jìn)行通信,生產(chǎn)者線程將數(shù)據(jù)放入緩沖區(qū)后通知消費(fèi)者線程,消費(fèi)者線程從緩沖區(qū)取出數(shù)據(jù)后通知生產(chǎn)者線程,以此循環(huán)往復(fù)。
在Python中,可以使用Queue模塊來實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式
。Queue是一個(gè)線程安全的隊(duì)列,提供了put()和get()方法用于向隊(duì)列中添加和獲取數(shù)據(jù)。生產(chǎn)者線程通過put()方法將數(shù)據(jù)放入隊(duì)列中,消費(fèi)者線程通過get()方法從隊(duì)列中取出數(shù)據(jù)。在多線程環(huán)境中,Queue會(huì)自動(dòng)處理線程同步和鎖問題,避免了資源競(jìng)爭(zhēng)和線程安全的問題
。
?? 下面是一個(gè)簡(jiǎn)單的Python生產(chǎn)者消費(fèi)者模式的示例代碼:
import threading
import time
import queue
class Producer(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
for i in range(10):
item = "item " + str(i)
self.queue.put(item)
print("Producer produced", item)
time.sleep(1)
class Consumer(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
while True:
item = self.queue.get()
print("Consumer consumed", item)
time.sleep(2)
queue = queue.Queue()
producer = Producer(queue)
consumer = Consumer(queue)
producer.start()
consumer.start()
producer.join()
consumer.join()
在這個(gè)示例中,Producer類和Consumer類都繼承了threading.Thread類,并重寫了run()方法。Producer類的run()方法會(huì)循環(huán)10次,每次生成一個(gè)數(shù)據(jù)并放入隊(duì)列中,并輸出一條生產(chǎn)者生成的信息。Consumer類的run()方法會(huì)一直循環(huán),從隊(duì)列中取出數(shù)據(jù)并輸出一條消費(fèi)者消費(fèi)的信息。
最后,創(chuàng)建一個(gè)隊(duì)列實(shí)例,創(chuàng)建一個(gè)生產(chǎn)者線程和一個(gè)消費(fèi)者線程,并啟動(dòng)它們。使用join()方法等待線程結(jié)束。運(yùn)行以上代碼,可以看到生產(chǎn)者線程不斷地生成數(shù)據(jù)并放入隊(duì)列中,消費(fèi)者線程不斷地從隊(duì)列中取出數(shù)據(jù)并進(jìn)行處理。
實(shí)戰(zhàn)演示
王者榮耀照片下載(使用生產(chǎn)者消費(fèi)者模式)
以王者榮耀游戲網(wǎng)站照片為例,我們先使用前面學(xué)到的知識(shí)來獲取它。在使用生產(chǎn)者消費(fèi)者模式之前,我們需要先用之前的方式將數(shù)據(jù)獲取下來,并保證操作的爭(zhēng)正確性,接著我們?cè)匍_始使用生產(chǎn)者消費(fèi)者模式來提高效率。
這些照片也可以直接下載,但是下載所有的照片就比較費(fèi)時(shí)間
?? ??使用我們之前學(xué)過的知識(shí),我們先把包含照片數(shù)據(jù)的連接抓取下來,然后再進(jìn)行解析,獲取我們想要的數(shù)據(jù),然后批量下載。
通過檢查我們可以看到,左邊是我們想要的照片,右邊是我們檢查的數(shù)據(jù),在右邊,有照片和一個(gè)文檔,這個(gè)文檔里面就包含我們需要的所有數(shù)據(jù),包括照片的名字,鏈接等各種參數(shù)。
我們來查看文檔。
?? ??在預(yù)覽中我們看到下面這些數(shù)據(jù),在這些數(shù)據(jù)中包含的有照片各種數(shù)據(jù),接下來我們就要進(jìn)行分析,獲取我們想要的數(shù)據(jù)。不過第一步還是先通過代碼將這些數(shù)據(jù)獲取到本地。
?? 理論講解完畢,我們來寫代碼??!
import crawles
url = 'https://apps.game.qq.com/cgi-bin/ams/module/ishow/V1.0/query/workList_inc.cgi'
cookies = {
'RK': 'dudRHvHoV7',
'ptcz': 'e9f27de6b2494bb66582be41b9fbc6f53a75342cb113935ed433754d3149db7d',
'pgv_pvid': '7146443171',
'LW_sid': 'i1M658J0m738G4r6t1c3c2G2R2',
'LW_uid': 'v1I6H8G0c7g8U4y6d1F3h2m2w9',
'eas_sid': 'O1o6S8r037g8B4S6w1c303A3Q9',
'pgv_info': 'ssid=s2960000074',
'pvpqqcomrouteLine': 'index_wallpaper_wallpaper',
}
headers = {
'authority': 'apps.game.qq.com',
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'pragma': 'no-cache',
'referer': 'https://pvp.qq.com/',
'sec-ch-ua': '\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '\"Windows\"',
'sec-fetch-dest': 'script',
'sec-fetch-mode': 'no-cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
}
params = {
'activityId': '2735',
'sVerifyCode': 'ABCD',
'sDataType': 'JSON',
'iListNum': '20',
'totalpage': '0',
'page': '1',
'iOrder': '0',
'iSortNumClose': '1',
'jsoncallback': '',
'iAMSActivityId': '51991',
'_everyRead': 'true',
'iTypeId': '2',
'iFlowId': '267733',
'iActId': '2735',
'iModuleId': '2735',
'_': '1680785113806',
}
response = crawles.get(url, headers=headers, params=params, cookies=cookies)
print(response.text)
?? 通過上述代碼,我們可以獲取到剛才的數(shù)據(jù)到本地。
?? 通過觀察我們發(fā)現(xiàn),數(shù)據(jù)分為兩大部分,我們來對(duì)數(shù)據(jù)進(jìn)行解析看看。
response = crawles.get(url, headers=headers, params=params, cookies=cookies)
print(response.text)
for data in response.json['List']:
# 從字典中獲取需要的數(shù)據(jù)
image_name = parse.unquote(data['sProdName'])
image_url = str(parse.unquote(data['sProdImgNo_6']))
print(image_name, image_url)
?? 我們得到了一頁數(shù)據(jù),包含照片名字和照片鏈接,我們可以點(diǎn)擊照片鏈接進(jìn)去查看:
????鎧-銀白詠嘆調(diào)
?? 接著我們使用循環(huán)獲取前十頁的數(shù)據(jù)并保存(數(shù)據(jù)較多,且下載較慢,僅展示部分內(nèi)容)
?? ??到此我們完成了第一部分,接下來我們來完成第二部分,將代碼封裝成函數(shù)然后使用隊(duì)列來操作方便我們獲取數(shù)據(jù):
?? 完整代碼如下:
from os.path import exists
from queue import Queue
from threading import Thread, BoundedSemaphore
from urllib import parse
import crawles
q = Queue(6)
lock = BoundedSemaphore(3)
s = 0
url = 'https://apps.game.qq.com/cgi-bin/ams/module/ishow/V1.0/query/workList_inc.cgi'
cookies = {
'RK': 'dudRHvHoV7',
'ptcz': 'e9f27de6b2494bb66582be41b9fbc6f53a75342cb113935ed433754d3149db7d',
'pgv_pvid': '7146443171',
'LW_sid': 'i1M658J0m738G4r6t1c3c2G2R2',
'LW_uid': 'v1I6H8G0c7g8U4y6d1F3h2m2w9',
'eas_sid': 'O1o6S8r037g8B4S6w1c303A3Q9',
'pgv_info': 'ssid=s2960000074',
'pvpqqcomrouteLine': 'index_wallpaper_wallpaper',
}
headers = {
'authority': 'apps.game.qq.com',
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'pragma': 'no-cache',
'referer': 'https://pvp.qq.com/',
'sec-ch-ua': '\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '\"Windows\"',
'sec-fetch-dest': 'script',
'sec-fetch-mode': 'no-cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
}
params = {
'activityId': '2735',
'sVerifyCode': 'ABCD',
'sDataType': 'JSON',
'iListNum': '20',
'totalpage': '0',
'page': '1',
'iOrder': '0',
'iSortNumClose': '1',
'jsoncallback': '',
'iAMSActivityId': '51991',
'_everyRead': 'true',
'iTypeId': '2',
'iFlowId': '267733',
'iActId': '2735',
'iModuleId': '2735',
'_': '1680785113806',
}
def page_data_get(page_value):
"""數(shù)據(jù)獲取"""
params['page'] = str(page_value)
response = crawles.get(url, headers=headers, params=params, cookies=cookies)
for data in response.json['List']:
# 從字典中獲取需要的數(shù)據(jù)
image_name = parse.unquote(data['sProdName'])
image_url = str(parse.unquote(data['sProdImgNo_6'])).replace('.jpg/200', '.jpg/0')
q.put((image_name, image_url)) # 將數(shù)據(jù)存放到隊(duì)列
lock.release() # 解鎖
def image_save():
"""保存文件"""
while True: # 讓線程一直存活
print(f'目前隊(duì)列大小{q.qsize()}')
try: # 在規(guī)定時(shí)間內(nèi)沒有再獲取圖片,代表數(shù)據(jù)獲取結(jié)束
image_name, image_url = q.get(timeout=5)
except:
return
iamge_path = f'image/{image_name}.jpg'
if exists(iamge_path): # 判斷圖片是否存儲(chǔ)
global s
iamge_path = f'image/{image_name}_{s}.jpg' # 如果存在就修改名稱
s += 1
print(iamge_path)
f = open(iamge_path, 'wb') # 創(chuàng)建文件
f.write(crawles.get(image_url,headers=headers).content) # 請(qǐng)求數(shù)據(jù)
f.close() # 關(guān)閉文件 保存數(shù)據(jù)
for i in range(5): # 消費(fèi)者模式 先運(yùn)行
t = Thread(target=image_save)
t.start()
for page in range(0, 10): # 生產(chǎn)者
lock.acquire()
t = Thread(target=page_data_get, args=(page,))
t.start()
?? 代碼講解:
這段代碼使用了生產(chǎn)者-消費(fèi)者模式實(shí)現(xiàn)了多線程爬取圖片數(shù)據(jù)和保存圖片的功能。
for i in range(5): # 消費(fèi)者模式 先運(yùn)行
t = Thread(target=image_save)
t.start()
for page in range(0, 10): # 生產(chǎn)者
lock.acquire()
t = Thread(target=page_data_get, args=(page,))
t.start()
具體來說,使用了Python中的
Thread類
創(chuàng)建了多個(gè)線程,其中包括5個(gè)線程用于保存圖片數(shù)據(jù),以及根據(jù)需要生產(chǎn)的頁面數(shù)創(chuàng)建的若干個(gè)線程用于獲取圖片的數(shù)據(jù)。為了防止多個(gè)線程同時(shí)訪問同一資源導(dǎo)致數(shù)據(jù)錯(cuò)誤,使用了Lock類
來進(jìn)行線程同步。
其中,生產(chǎn)者線程使用了page_data_get
函數(shù)獲取圖片的數(shù)據(jù),消費(fèi)者線程使用了image_save
函數(shù)來保存圖片。
最終,通過多線程的并發(fā)執(zhí)行,實(shí)現(xiàn)了高效的圖片爬取和保存。
?? 結(jié)果展示:文章來源:http://www.zghlxwxcb.cn/news/detail-417437.html
寫在最后:
????本專欄所有文章是博主學(xué)習(xí)筆記,僅供學(xué)習(xí)使用,爬蟲只是一種技術(shù),希望學(xué)習(xí)過的人能正確使用它。
博主也會(huì)定時(shí)一周三更爬蟲相關(guān)技術(shù)更大家系統(tǒng)學(xué)習(xí),如有問題,可以私信我,沒有回,那我可能在上課或者睡覺,寫作不易,感謝大家的支持??!??????文章來源地址http://www.zghlxwxcb.cn/news/detail-417437.html
到了這里,關(guān)于12.網(wǎng)絡(luò)爬蟲—線程隊(duì)列詳講(實(shí)戰(zhàn)演示)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!