前言:
??????個(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é)我們講過,每個(gè)爬蟲都是你的分身,我們本章節(jié)就教大家怎么使用分身。
程序
?? ??程序是一系列指令或代碼的集合,用于指導(dǎo)計(jì)算機(jī)執(zhí)行特定的任務(wù)或操作。
程序可以是
計(jì)算機(jī)程序
、應(yīng)用程序
、腳本程序
等,可以用不同的編程語言編寫。程序通過計(jì)算機(jī)的處理和執(zhí)行,實(shí)現(xiàn)了人類所需要的各種功能和應(yīng)用。
進(jìn)程
?? ??進(jìn)程是計(jì)算機(jī)中正在運(yùn)行的程序的實(shí)例。
它是計(jì)算機(jī)為了完成某個(gè)任務(wù)而創(chuàng)建的一個(gè)執(zhí)行單元,包含程序代碼
、數(shù)據(jù)
和執(zhí)行狀態(tài)
等信息。
- 每個(gè)進(jìn)程都有自己的
內(nèi)存地址空間
、文件句柄
、網(wǎng)絡(luò)連接
等資源。 - 進(jìn)程可以與其他進(jìn)程進(jìn)行通信和協(xié)作,也可以被操作系統(tǒng)調(diào)度和管理。
- 在
多任務(wù)操作系統(tǒng)中,多個(gè)進(jìn)程可以同時(shí)運(yùn)行
,共享計(jì)算機(jī)的資源,提高計(jì)算機(jī)的效率和性能。
線程
?? ??線程是進(jìn)程中的一個(gè)執(zhí)行單元,是計(jì)算機(jī)執(zhí)行程序時(shí)的最小單位。
-
一個(gè)進(jìn)程可以包含多個(gè)線程,每個(gè)線程都有自己的執(zhí)行路徑、堆棧和局部變量等
。 - 不同的線程可以同時(shí)執(zhí)行不同的任務(wù),共享進(jìn)程的資源,提高計(jì)算機(jī)的效率和性能。
- 線程可以被操作系統(tǒng)調(diào)度和管理,也可以通過同步機(jī)制來協(xié)調(diào)各自的執(zhí)行。
- 線程的優(yōu)點(diǎn)
是可以充分利用多核處理器的并行性,提高程序的響應(yīng)速度和并發(fā)處理能力
。
線程常用方法
?? ??線程常用方法包括:
-
start()
:?jiǎn)?dòng)線程,開始執(zhí)行線程的run()方法。
2.run()
:線程的主體方法,包含線程要執(zhí)行的代碼。
-
join()
:等待線程結(jié)束,讓主線程等待子線程執(zhí)行完畢。 -
sleep()
:使線程暫停執(zhí)行一段時(shí)間,以便其他線程有機(jī)會(huì)執(zhí)行。 -
interrupt()
:中斷線程,通知線程停止運(yùn)行。 -
isAlive()
:判斷線程是否還在運(yùn)行。 -
setDaemon()
:將線程設(shè)置為守護(hù)線程,當(dāng)主線程結(jié)束后,守護(hù)線程會(huì)自動(dòng)結(jié)束。 -
yield()
:讓出CPU,讓其他線程有機(jī)會(huì)執(zhí)行。 -
wait()
:使線程進(jìn)入等待狀態(tài),直到其他線程調(diào)用notify()
或notifyAll()
方法喚醒它。 -
notify()和notifyAll()
:?jiǎn)拘训却械木€程,使其繼續(xù)執(zhí)行。
多線程的優(yōu)點(diǎn)
?? ??多線程的優(yōu)點(diǎn)主要有以下幾點(diǎn):
-
提高爬取速度:使用多線程技術(shù)可以同時(shí)處理多個(gè)任務(wù),從而提高爬取速度。在單線程模式下,程序需要一個(gè)一個(gè)地處理每個(gè)任務(wù),而在多線程模式下,程序可以同時(shí)處理多個(gè)任務(wù),從而節(jié)省了大量的時(shí)間。
-
提高效率:在處理大量數(shù)據(jù)時(shí),使用多線程技術(shù)可以大大縮短程序的運(yùn)行時(shí)間,從而提高效率。這對(duì)于需要處理大量數(shù)據(jù)的應(yīng)用場(chǎng)景非常有用,比如搜索引擎、數(shù)據(jù)挖掘等。
-
降低資源占用率:使用多線程技術(shù)可以將任務(wù)分配到不同的線程中執(zhí)行,從而降低了單個(gè)線程對(duì)資源的占用率,比如CPU、內(nèi)存等。這樣可以避免單個(gè)線程因?yàn)橘Y源占用過高而導(dǎo)致程序崩潰或者運(yùn)行緩慢的問題。
-
提高程序的可擴(kuò)展性:使用多線程技術(shù)可以將任務(wù)分配到不同的線程中執(zhí)行,從而方便程序的擴(kuò)展和升級(jí)。如果需要增加新的功能或者處理更多的數(shù)據(jù),只需要增加更多的線程即可。
join() 案例
?? ??為了方便理解,代碼注釋放在了代碼片中。
from time import sleep, time # 導(dǎo)入需要使用的模塊和函數(shù)
import threading # 多線程模塊,內(nèi)置模塊
def main1():
print('開始運(yùn)行main1')
sleep(3)
print('運(yùn)行完成main1')
def main2():
print('開始運(yùn)行main2')
sleep(3)
print('運(yùn)行完成main2')
# 定義兩個(gè)函數(shù)main1和main2,分別打印開始運(yùn)行和運(yùn)行完成的信息,并在函數(shù)中使用sleep函數(shù)模擬函數(shù)執(zhí)行時(shí)間。
start = time() # 使用time函數(shù)記錄程序開始運(yùn)行的時(shí)間。
t1 = threading.Thread(target=main1)
t1.start()
t2 = threading.Thread(target=main2)
t2.start()
# 使用Thread函數(shù)創(chuàng)建兩個(gè)線程t1和t2,分別執(zhí)行main1和main2函數(shù)。
t1.join() # 阻塞主線程 等待t1線程運(yùn)行完成
t2.join()
# 使用join方法阻塞主線程,等待t1和t2線程運(yùn)行完成后再繼續(xù)執(zhí)行主線程
stop = time() # 使用time函數(shù)記錄程序結(jié)束運(yùn)行的時(shí)間。
print(stop - start) # 輸出程序運(yùn)行時(shí)間。
整體來說,就是創(chuàng)建了兩個(gè)線程t1和t2,分別執(zhí)行main1和main2函數(shù)。在主線程中,使用join方法阻塞主線程,等待t1和t2線程運(yùn)行完成后再繼續(xù)執(zhí)行主線程。最后輸出程序運(yùn)行時(shí)間。
使用多線程模塊threading實(shí)現(xiàn)并發(fā)執(zhí)行多個(gè)函數(shù)的效果,提高程序的運(yùn)行效率。
共享全局變量資源競(jìng)爭(zhēng)
?? ??全局變量是在模塊級(jí)別定義的,因此在多個(gè)線程或進(jìn)程中訪問全局變量可能會(huì)發(fā)生資源競(jìng)爭(zhēng)
為了避免這種情況,可以使用線程鎖
或進(jìn)程鎖
來同步對(duì)全局變量的訪問。
?? ??在使用多線程的情況下,可以使用Python的threading模塊中的Lock類
來實(shí)現(xiàn)線程鎖。在訪問全局變量之前,線程可以調(diào)用acquire()方法
來獲取鎖,并在完成后調(diào)用release()方法
來釋放鎖。這樣,只有一個(gè)線程可以同時(shí)訪問全局變量,從而避免了資源競(jìng)爭(zhēng)。
以下是一個(gè)使用線程鎖來避免全局變量資源競(jìng)爭(zhēng)的示例代碼:
import threading
# 定義全局變量
count = 0
# 定義線程鎖
lock = threading.Lock()
# 定義線程函數(shù)
def increment():
global count
# 獲取線程鎖
lock.acquire()
try:
# 訪問全局變量
count += 1
finally:
# 釋放線程鎖
lock.release()
# 創(chuàng)建多個(gè)線程
threads = []
for i in range(10):
t = threading.Thread(target=increment)
threads.append(t)
# 啟動(dòng)線程
for t in threads:
t.start()
# 等待所有線程完成
for t in threads:
t.join()
# 輸出結(jié)果
print("count = ", count)
在上面的代碼中,我們定義了一個(gè)全局變量count和一個(gè)線程鎖lock。然后,我們定義了一個(gè)increment()`函數(shù),該函數(shù)通過獲取線程鎖來訪問全局變量count,并將其遞增。最后,我們創(chuàng)建了10個(gè)線程,并在每個(gè)線程中調(diào)用increment()函數(shù)。在訪問全局變量之前,每個(gè)線程都會(huì)獲取線程鎖,并在完成后釋放它。這樣,我們可以確保只有一個(gè)線程可以同時(shí)訪問全局變量count,從而避免了資源競(jìng)爭(zhēng)。最后,我們輸出count的值,以驗(yàn)證它是否正確地遞增了。
互斥鎖&死鎖
互斥鎖
?? ??互斥鎖(Mutex)是一種同步機(jī)制,用于保護(hù)共享資源不被并發(fā)訪問。在Python中,可以使用threading
模塊中的Lock
類來實(shí)現(xiàn)互斥鎖。
下面是一個(gè)簡(jiǎn)單的例子,說明如何使用互斥鎖來保護(hù)共享變量:
import threading
x = 0
lock = threading.Lock()
def increment():
global x
for i in range(100000):
lock.acquire()
x += 1
lock.release()
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
t1.start()
t2.start()
t1.join()
t2.join()
print(x)
?? ??上述代碼中,我們定義了一個(gè)全局變量x
,然后創(chuàng)建了兩個(gè)線程t1
和t2
,每個(gè)線程都會(huì)對(duì)x
進(jìn)行100000次加1操作。為了避免并發(fā)訪問導(dǎo)致數(shù)據(jù)出錯(cuò),我們使用了Lock
類來保護(hù)x
。具體來說,當(dāng)一個(gè)線程獲取到鎖時(shí),其他線程就不能獲取鎖,直到該線程釋放鎖為止。
死鎖
?? ??死鎖指的是兩個(gè)或多個(gè)線程在等待對(duì)方釋放資源的狀態(tài),從而導(dǎo)致程序無法繼續(xù)執(zhí)行。
在Python中,死鎖通常發(fā)生在多個(gè)線程同時(shí)獲取多個(gè)鎖的情況下。為了避免死鎖,我們應(yīng)該盡量避免使用多個(gè)鎖。
下面是一個(gè)簡(jiǎn)單的死鎖例子:
import threading
lock1 = threading.Lock()
lock2 = threading.Lock()
def func1():
lock1.acquire()
lock2.acquire()
print("Func1")
lock2.release()
lock1.release()
def func2():
lock2.acquire()
lock1.acquire()
print("Func2")
lock1.release()
lock2.release()
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t1.start()
t2.start()
t1.join()
t2.join()
?? ??上述代碼中,我們定義了兩個(gè)鎖lock1
和lock2
,然后創(chuàng)建了兩個(gè)線程t1
和t2
,每個(gè)線程都需要獲取兩個(gè)鎖才能繼續(xù)執(zhí)行。由于t1
和t2
同時(shí)獲取了一個(gè)鎖,然后又試圖獲取另一個(gè)鎖,導(dǎo)致兩個(gè)線程都陷入了等待狀態(tài),無法繼續(xù)執(zhí)行,從而引發(fā)了死鎖。
多線程實(shí)戰(zhàn)
某果多線程實(shí)戰(zhàn)
????本次實(shí)戰(zhàn)目的是使用多線程獲取網(wǎng)站前十頁數(shù)據(jù)的菜·
標(biāo)題
,作者
,觀看人數(shù)
,收藏人數(shù)
。
?? ??在這里,我們使用之前學(xué)過的知識(shí),用最常用的方式先獲取網(wǎng)站前十頁的數(shù)據(jù)。如果有什么問題,可以閱讀專欄之前的文章幫助理解學(xué)習(xí)。????《Python網(wǎng)絡(luò)爬蟲》,累計(jì)閱讀6w+。
獲取數(shù)據(jù)完整代碼如下:
import requests
from lxml import etree
from tabulate import tabulate
import time
for j in range(0, 217, 24):
url = f'https://www.douguo.com/jingxuan/{j}'
res = requests.get(url)
# print(res.text)
html = etree.HTML(res.text)
for i in range(1, 25):
name = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/a[1]/text()')
author = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/a[2]/text()')[1].strip()
watch = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/div/span[1]/text()')
Collection = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/div/span[2]/text()')
result = tabulate([[name, author, watch, Collection]],
headers=['Name', 'Author', 'Watch', 'Collection'], tablefmt='orgtbl')
print(result)
time.sleep(1)
運(yùn)行結(jié)果展示部分,因?yàn)閮?nèi)容較多:
?? ??我在這里大概講解一下代碼的含義:
- 使用requests庫向指定的網(wǎng)址發(fā)送請(qǐng)求,并獲取網(wǎng)頁的源代碼。
- 使用lxml庫中的etree類將源代碼解析成HTML文檔對(duì)象
- 使用for循環(huán)遍歷每一頁上的所有菜譜,通過xpath表達(dá)式提取出菜譜的名稱、作者、瀏覽量和收藏量等信息。
- 使用tabulate庫將提取出來的信息以表格形式打印出來。
- 為了防止爬取速度過快被網(wǎng)站封禁,代碼中使用了time庫中的sleep函數(shù),每爬取一頁后暫停1秒鐘。
我們先計(jì)算一下,獲取每一頁數(shù)據(jù)需要多少時(shí)間和總體需要多少時(shí)間:
接下來我們使用多線程來操作,多線程運(yùn)行的部分需要是一個(gè)函數(shù),所以我們把主體部分封裝成一個(gè)函數(shù):
def save_data(url):
with codecs.open('douguo5.csv', 'a', 'utf-8-sig') as csvfile:
writer = csv.writer(csvfile)
res = requests.get(url)
html = etree.HTML(res.text)
for i in range(1, 25):
name = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/a[1]/text()')
author = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/a[2]/text()')[1].strip()
watch = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/div/span[1]/text()')
Collection = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/div/span[2]/text()')
writer.writerow([name, author, watch, Collection])
print([name, author, watch, Collection])
?? ??然后我們使用多線程技術(shù)加速爬取數(shù)據(jù)的速度。
- 先定義了一個(gè)名為
t_list
的列表,用于存儲(chǔ)線程對(duì)象。 - 使用一個(gè)
for循環(huán)遍歷所有需要爬取的網(wǎng)頁
,將每個(gè)網(wǎng)頁的爬取任務(wù)分配給一個(gè)線程,并將該線程對(duì)象添加到t_list列表中。 - 使用
threading.Thread方法
創(chuàng)建一個(gè)名為t1的線程對(duì)象,將save_data
函數(shù)作為target
參數(shù)傳入,并將url作為args參數(shù)傳入。 - 調(diào)用
t1.start()
方法啟動(dòng)線程,并將t1添加到t_list
列表中。 - 使用另一個(gè)for循環(huán)遍歷t_list列表中的所有線程對(duì)象,并調(diào)用
i.join()
方法等待所有線程執(zhí)行完畢。 -
join
方法會(huì)阻塞當(dāng)前線程,直到被調(diào)用的線程執(zhí)行完畢。
在這個(gè)for循環(huán)中,主線程會(huì)等待所有子線程都執(zhí)行完畢后再繼續(xù)執(zhí)行下面的代碼。
最后,代碼計(jì)算并輸出了程序的運(yùn)行時(shí)間,即
stop - start
的值。這個(gè)值表示程序從開始執(zhí)行到結(jié)束所用的時(shí)間,可以用來評(píng)估程序的效率和性能。
完整代碼如下:
import codecs
import csv
import time
import requests
from lxml import etree
def save_data(url):
with codecs.open('douguo5.csv', 'a', 'utf-8-sig') as csvfile:
writer = csv.writer(csvfile)
res = requests.get(url)
html = etree.HTML(res.text)
for i in range(1, 25):
name = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/a[1]/text()')
author = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/a[2]/text()')[1].strip()
watch = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/div/span[1]/text()')
Collection = html.xpath(f'//*[@id="jxlist"]/li[{i}]/div/div/span[2]/text()')
writer.writerow([name, author, watch, Collection])
print([name, author, watch, Collection])
import threading
start = time.time()
t_list = []
for j in range(0, 217, 24):
url = f'https://www.douguo.com/jingxuan/{j}'
# save_data(url)
t1 = threading.Thread(target=save_data, args=(url,))
t1.start()
t_list.append(t1)
for i in t_list:
i.join()
stop = time.time()
print(stop - start)
我們可以看到,運(yùn)行時(shí)間大大縮短。
提高爬取速度和效率。文章來源:http://www.zghlxwxcb.cn/news/detail-408409.html
寫在最后:
?? ??本專欄所有文章是博主學(xué)習(xí)筆記,僅供學(xué)習(xí)使用,爬蟲只是一種技術(shù),希望學(xué)習(xí)過的人能正確使用它。博主也會(huì)定時(shí)一周三更爬蟲相關(guān)技術(shù)更大家系統(tǒng)學(xué)習(xí),如有問題,可以私信我,沒有回,那我可能在上課或者睡覺,寫作不易,感謝大家的支持??!??????文章來源地址http://www.zghlxwxcb.cn/news/detail-408409.html
到了這里,關(guān)于11.網(wǎng)絡(luò)爬蟲—多線程詳講與實(shí)戰(zhàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!