線程鎖
當多個線程幾乎同時修改某一個共享數據的時候,需要進行同步控制 某個線程要更改共享數據時,先將其鎖定,此時資源的狀態為"鎖定",其他線程不能改變,只到該線程釋放資源,將資源的狀態變成"非鎖定",其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操作,從而保證了多線程情況下數據的正確性。
創建鎖 mutex = threading.Lock() ? 鎖定 mutex.acquire() ? 解鎖 mutex.release()
Queue線程
在線程中,訪問一些全局變量,加鎖是一個經常的過程。如果你是想把一些數據存儲到某個隊列中,那么Python內置了一個線程安全的模塊叫做queue模塊。Python中的queue模塊中提供了同步的、線程安全的隊列類,包括FIFO(先進先出)隊列Queue,LIFO(后入先出)隊列LifoQueue。這些隊列都實現了鎖原語(可以理解為原子操作,即要么不做,要么都做完),能夠在多線程中直接使用。可以使用隊列來實現線程間的同步。
初始化Queue(maxsize):創建一個先進先出的隊列。 empty():判斷隊列是否為空。 full():判斷隊列是否滿了。 get():從隊列中取最后一個數據。 put():將一個數據放到隊列中。
生產者與消費者模式
生產者和消費者模式是多線程開發中常見的一種模式。通過生產者和消費者模式,可以讓代碼達到高內聚低耦合的目標,線程管理更加方便,程序分工更加明確。 生產者的線程專門用來生產一些數據,然后存放到容器中(中間變量)。消費者在從這個中間的容器中取出數據進行消費
使用單線程下載表情包
?
import re
import requests
from urllib.request import urlretrieve
from lxml import etree
"""
將http://www.godoutu.com/face/hot/page/1.html下10頁數據的表情包全部抓取
print(45*1801) ?8w多條數據?
圖片數據 二進制 ?
保存 ??
with open ?wb模式 ?
urllib
找到圖片的路徑 ?圖片名字 ?
"""
for i in range(1,2):
? ? url = f'http://www.godoutu.com/face/hot/page/{i}.html'
? ? headers = {
? ? ? ? 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36'
? ? }
? ? response = requests.get(url, headers=headers)
? ? response.encoding = 'utf-8'
? ? html = response.text
? ? # print(html)
? ? # xpath對象
? ? element = etree.HTML(html)
? ? alldiv = element.xpath('//div[@class="ui segment imghover"]/div[@class="tagbqppdiv"]')
? ? # print(alldiv,len(alldiv))
? ? for j in alldiv:
? ? ? ? everyhref = j.xpath('./a/img/@data-original')[0]
? ? ? ? # print(everyhref)
? ? ? ? title = j.xpath('./a/@title')[0] ?# 必須要是合法的
? ? ? ? # print(title)
? ? ? ? newtitle = re.sub('[\/:*?<>|]','',title)
? ? ? ? # print(type(newtitle),type(everyhref))
? ? ? ? # 保存 jpg ?gif
? ? ? ? if str(everyhref).endswith('jpg'):
? ? ? ? ? ? urlretrieve(everyhref,f'images/{newtitle}.jpg')
? ? ? ? ? ? print(f'{newtitle}.jpg下載成功!')
? ? ? ? else:
? ? ? ? ? ? urlretrieve(everyhref, f'images/{newtitle}.gif')
? ? ? ? ? ? print(f'{newtitle}.gif下載成功!')
使用生產者與消費者模式下載表情包
?
import threading
import time
import re
import requests
from lxml import etree
from queue import Queue
from urllib.request import urlretrieve# 生產者模型
class Producer(threading.Thread):
? ? headers = {
? ? ? ? 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36'
? ? }? ? def __init__(self, page_queue,img_queue ): ?# RuntimeError: thread.__init__() not called
? ? ? ? # 在自寫的類中的init中,先初始化Thread
? ? ? ? threading.Thread.__init__(self) ?# 或則 super().__init__()
? ? ? ? self.page_queue = page_queue
? ? ? ? self.img_queue = img_queue? ? def run(self):
? ? ? ? while True: ?# 讓我們創建的三個生產者一直工作
? ? ? ? ? ? if self.page_queue.empty():
? ? ? ? ? ? ? ? break
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? url = self.page_queue.get()
? ? ? ? ? ? ? ? print(url)
? ? ? ? ? ? # 獲取到了url就可以去解析數據了
? ? ? ? ? ? self.Parse_html(url)
? ? def Parse_html(self,url):
? ? ? ? # 上鎖
? ? ? ? lock.acquire()
? ? ? ? # 發請求,獲取響應
? ? ? ? res = requests.get(url, headers=self.headers)
? ? ? ? text = res.text
? ? ? ? # 隨機延遲
? ? ? ? # time.sleep(random.random())
? ? ? ? # 解析數據,拿真的圖片地址
? ? ? ? element = etree.HTML(text)? ? ? ? # 將獲取的所有img標簽放到列表里面
? ? ? ? alldiv = element.xpath('//div[@class="ui segment imghover"]/div[@class="tagbqppdiv"]')? ? ? ? # 解鎖
? ? ? ? lock.release()
? ? ? ? # 取出每一個圖片的地址
? ? ? ? for j in alldiv:
? ? ? ? ? ? everyhref = j.xpath('./a/img/@data-original')[0]
? ? ? ? ? ? print(everyhref)
? ? ? ? ? ? title = j.xpath('./a/@title')[0] ?# 必須要是合法的
? ? ? ? ? ? print(title)
? ? ? ? ? ? newtitle = re.sub('[\/:*?<>|]', '', title)
? ? ? ? ? ? # 將獲取到的img_url和title數據存放在另一個隊列種然后再交給消費者進行處理
? ? ? ? ? ? self.img_queue.put((everyhref,newtitle)) # 用元組打包作為整體進行處理
? ? ? ? ? ? # 檢測我獲取的數據量是否正確
? ? ? ? print(self.img_queue.qsize())# 消費者模型
class Consumer(threading.Thread):
? ? def __init__(self, img_queue): ?# RuntimeError: thread.__init__() not called
? ? ? ? # 在自寫的類中的init中,先初始化Thread
? ? ? ? threading.Thread.__init__(self) ?# 或則 super().__init__()
? ? ? ? self.img_queue = img_queue? ? def run(self):
? ? ? ? while True: ?# 讓我們創建的三個生產者一直工作
? ? ? ? ? ? if self.img_queue.empty():
? ? ? ? ? ? ? ? break
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? img_data = self.img_queue.get() ?# 元組類型數據
? ? ? ? ? ? # 解包
? ? ? ? ? ? img_url, filename = img_data
? ? ? ? ? ? # 下載操作
? ? ? ? ? ? if str(img_url).endswith('jpg'):
? ? ? ? ? ? ? ? urlretrieve(img_url, f'imagesss/{filename}.jpg')
? ? ? ? ? ? ? ? print(f'{filename}.jpg下載成功!')
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? urlretrieve(img_url, f'imagesss/{filename}.gif')
? ? ? ? ? ? ? ? print(f'{filename}.gif下載成功!')# 程序主入口
if __name__ == '__main__':
? ? # 創建一把鎖
? ? lock = threading.Lock()? ? # 1 將所有的url存放在隊列中
? ? page_queue = Queue() ?# 創建一個隊列 然后通過put方法存放進去? ? # ?創建一個存放數據的隊列
? ? img_queue = Queue() ?# 同樣將這個隊列通過init初始化傳到生產者模型中? ? for i in range(1, 11):
? ? ? ? url = f'http://www.godoutu.com/face/hot/page/{i}.html'
? ? ? ? page_queue.put(url)? ? p_list = []
? ? # 2 創建生產者對象 ?三個
? ? for i in range(3):
? ? ? ? t = Producer(page_queue,img_queue) ?# 將隊列傳給生產者處理 那再創建對象進行傳參的過程中我們需要進行接收 init
? ? ? ? t.start() ?# 開啟多線程 ? 執行的是run方法
? ? ? ? p_list.append(t)? ? for p in p_list:
? ? ? ? p.join()? ? # 創建三個消費者
? ? for j in range(3):
? ? ? ? t = Consumer(img_queue)
? ? ? ? t.start()