既然提到多線程,那么為何用多線程呢???打個比方,好比做某件事,單線程就是一個人去做,多線程呢就是有多個人去完成,那么排除掉如死鎖等特殊原因,當然多個人去完成某件事會比較快一些了。
多線程的最高境界是并行完成,當然一般是并發(fā)的,那么啥叫并發(fā)和并行呢?并行即同時去完成,并發(fā)就是有可能需要排隊有時就一起去完成。
Python中一般多線程的實現(xiàn)方式有兩種方式:1)繼承 threading.Thread 類并與 queue.Quque() 結合使用(分段完成) 2)調用函數(shù)
好了,接下來我們直接上案例:多線程下載斗圖網(wǎng)的表情包(https://www.doutula.com/article/list/?page=1)
一、導入我們所需的基本模塊以及請求源碼的函數(shù)
import requests, time, threading
from lxml import etree
import urllib.request
from multiprocessing.dummy import Pool as ThreadPool # 這里注意的是:導入dummy 表示是多線程而非多進程
# 獲取源碼
def get_html(url):
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'}
response = requests.get(url=url, headers=headers)
return response.text
二、接下來我們觀察下源碼,并使用 XPath 對源碼進行解析獲取跳轉鏈接:

4.png
這里值得提的是:最好 觀察源碼,因此在右鍵“檢查”中有些屬性是和源碼顯示的是不一樣的,觀察了源碼之后我們發(fā)現(xiàn)列表的10個選項中都有同一個 class 屬性值,因此代碼解析代碼函數(shù)如下:
# 獲取跳轉鏈接
def get_a_href(url):
html = get_html(url) # 獲取源碼
selector = etree.HTML(html) # 自動修正代碼
data = selector.xpath('//a[@class="list-group-item random_list"]/@href') # 獲取所有的跳轉鏈接
for url in data:
img_html = get_html(url) # 獲取源碼
get_img_src(img_html) # 調用解析獲取圖片鏈接函數(shù)
在以上方法的源碼中的循環(huán)里面有兩個函數(shù),第一個函數(shù)就是剛剛定義的根據(jù) url 獲取源碼內容,第二個函數(shù)就是解析跳轉后源碼中的圖片鏈接,,,這里我建議還是先別直接寫著兩個函數(shù),最好先輸出測試一下看看我們的解析跳轉鏈接正確了木有,如果沒問題再做進一步的編寫,輸出效果如下表示沒問題:

5.png
三、接下來我們先觀察下跳轉后頁面效果及源代碼

6.png
觀察了源碼之后我們開始編寫解析全部圖片鏈接的函數(shù) <<<這里注意的是:待會兒我們進行單線程和多線程的切換是在這個函數(shù)進行的>>>:
# 解析獲取圖片鏈接
def get_img_src(html):
selector = etree.HTML(html) # 自動修正代碼
img_src_list = selector.xpath('//tbody/tr[1]/td/a/img/@src')
for url in img_src_list:
print("全部圖片的鏈接為-> ", url)
# for url in img_src_list: #單線程執(zhí)行
# save_img(url) # 下載圖片
# start_thread_save_img(img_src_list) # 調用多線程的函數(shù)
我們調用相應函數(shù)之后運行如下說明我們解析沒問題:

7.png
四、測試成功后,我們去掉數(shù)據(jù)循環(huán)語句,然后先去實現(xiàn)下單線程 的 save_img(url) 函數(shù)進行圖片的下載:
# 下載圖片
def save_img(img_url):
print("正在下載鏈接 -> ", img_url)
if img_url.startswith('//'): # 防止長圖片沒有協(xié)議
img_url = "https:" + img_url
urllib.request.urlretrieve(img_url, 'C:/Users/Administrator/Desktop/doutuimage/%s' % img_url.split('/')[-1]) # 截取名字并下載到本地
五、為了方便觀察單線程與多線程下載的時間差,我們編寫一個標準化的輸出函數(shù):
def main():
url = "https://www.doutula.com/article/list/?page={}" # 點擊不同頁面在地址欄中觀察地址的變換即可找出規(guī)律
start_time = time.time()
for i in range(1, 6): # 獲取 6頁圖標
get_a_href(url.format(i))
end_time = time.time()
print("共耗時:", end_time-start_time)
if __name__ == '__main__': # 判斷文件入口
main()
六、接下來我們用單線程運行一下觀察下效果:

8.png
七、我們注釋掉 get_img_src(html) 函數(shù)中的 單線程 執(zhí)行語句,然后去掉 多線程 函數(shù)的 注釋符號,再對該函數(shù)編寫代碼:
# 多線程
def start_thread_save_img(img_url_list):
#方式一:使用線程池
pool = ThreadPool(6)
pool.map(save_img, img_url_list)
pool.close()
# pool.join() # join所完成的工作就是線程同步,即主線程任務結束之后,進入阻塞狀態(tài),一直等待其他的子線程執(zhí)行結束之后,主線程在終止
#方式二:沒有線程限制
# for url in img_url_list:
# th = threading.Thread(target=save_img, args=(url,))
# th.start()
八、經(jīng)過對比我們可以知道多線程和單線程的不一樣咯,還有多線程函數(shù)里的兩個方式最好試一下效率哦,下過圖如下:

9.png
好了,這個多線程就寫到這里,其實如果真正寫過去一遍之后感覺也挺容易的,接下來不要猶豫,直接動手吧!下一篇我們開搞多進程,希望能幫助到您哦!!!(如有疑問可留言...)