網(wǎng)站開發(fā)網(wǎng)站設(shè)計制作學(xué)電腦培訓(xùn)班
前言
在使用爬蟲爬取數(shù)據(jù)的時候,當(dāng)需要爬取的數(shù)據(jù)量比較大,且急需很快獲取到數(shù)據(jù)的時候,可以考慮將單線程的爬蟲寫成多線程的爬蟲。下面來學(xué)習(xí)一些它的基礎(chǔ)知識和代碼編寫方法。
一、進程和線程
進程可以理解為是正在運行的程序的實例。進程是擁有資源的獨立單位,而線程不是獨立的單位。由于每一次調(diào)度進程的開銷比較大,為此才引入的線程。一個進程可以擁有多個線程,一個進程中可以同時存在多個線程,這些線程共享該進程的資源,線程的切換消耗是很小的。因此在操作系統(tǒng)中引入進程的目的是更好地使多道程序并發(fā)執(zhí)行,提高資源利用率和系統(tǒng)吞吐量;而引入線程的目的則是減小程序在并發(fā)執(zhí)行時所付出的時空開銷,提高操作系統(tǒng)的并發(fā)性能。
下面用簡單的例子進行描述,打開本地計算機的”任務(wù)管理器”如圖1所示,這些正在運行的程序叫作進程。如果將一個進程比喻成一個工作,指定10個人來做這份工作,這10個人就是10個線程。因此,在一定的范圍內(nèi),多線程效率比單線程效率更高。
圖1.任務(wù)管理器
二、Python中的多線程與單線程
在我們平時學(xué)習(xí)的過程中,使用的主要是單線程爬蟲。一般來說,如果爬取的資源不是特別大,使用單線程即可。在Python中,默認情況下是單線程的,簡單理解為:代碼是按順序依次運行的,比如先運行第一行代碼,再運行第二行,依次類推。在前面章節(jié)所學(xué)習(xí)知識中,都是以單線程的形式實踐的。
舉個例子,批量下載某網(wǎng)站的圖片,由于下載圖片是一個耗時的操作,如果依然采用單線程的方式下載,那么效率就會特別低,意味著需要消耗更多的時間等待下載。為了節(jié)約時間,這時候我們就可以考慮使用多線程的方式來下載圖片。
threading模塊是Python中專門用來做多線程編程的模塊,它對thread進行了封裝,使用更加方便。例如需要對寫代碼和玩游戲兩個事件使用多線程進行,案例代碼如下。
import threading
import time
# 定義第一個
def coding():for x in range(3):print('%s正在寫代碼\n' % x)time.sleep(1)
# 定義第二個
def playing():for x in range(3):print('%s正在玩游戲\n' % x)time.sleep(1)
# 如果使用多線程執(zhí)行
def multi_thread():start = time.time()# Thread創(chuàng)建第一個線程,target參數(shù)為函數(shù)命t1 = threading.Thread(target=coding)t1.start() # 啟動線程# 創(chuàng)建第二個線程t2 = threading.Thread(target=playing)t2.start()# join是確保thread子線程執(zhí)行完畢后才能執(zhí)行下一個線程t1.join()t2.join()end = time.time()running_time = end - start print('總共運行時間 : %.5f 秒' % running_time)
# 執(zhí)行
if __name__ == '__main__':multi_thread() # 執(zhí)行單線程
運行結(jié)果如圖2所示:
圖2.多線程運行結(jié)果
那么執(zhí)行單線程會消耗多少時間,案例代碼如下所示。
import time
# 定義第一個
def coding():for x in range(3):print('%s正在寫代碼\n' % x)time.sleep(1)
# 定義第二個
def playing():start = time.time()for x in range(3):print('%s正在玩游戲\n' % x)time.sleep(1)end = time.time()running_time = end - startprint('總共運行時間 : %.5f 秒' % running_time)
def single_thread():coding()playing()
# 執(zhí)行
if __name__ == '__main__':single_thread() # 執(zhí)行單線程
運行結(jié)果如圖3所示:
圖3.單線程運行結(jié)果
經(jīng)過以上多線程和單線程的運行結(jié)果,可以看出多線程中寫代碼和玩游戲是一起執(zhí)行的,單線程中則是先寫代碼再玩游戲。從時間上來說,可能只有細微的差距,當(dāng)執(zhí)行工作量很大的時候,便會發(fā)現(xiàn)多線程消耗的時間會更少,從這個案例中我們也可以知道,當(dāng)所需要執(zhí)行的任務(wù)并不多的時候,只需要編寫單線程即可。
三、單線程改為多線程
以某直播的圖片爬取為例,案例代碼如下:
import requests
from lxml import etree
import time
import osdirpath = '圖片/'
if not os.path.exists(dirpath):os.mkdir(dirpath) # 創(chuàng)建文件夾header = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36'
}
def get_photo():url = 'https://www.huya.com/g/4079/' # 目標(biāo)網(wǎng)站response = requests.get(url=url, headers=header) # 發(fā)送請求data = etree.HTML(response.text) # 轉(zhuǎn)化為html格式return datadef jiexi():data = get_photo()image_url = data.xpath('//a//img//@data-original')image_name = data.xpath('//a//img[@class="pic"]//@alt')for ur, name in zip(image_url, image_name):url = ur.replace('?imageview/4/0/w/338/h/190/blur/1', '')title = name + '.jpg'response = requests.get(url=url, headers=header) # 在此發(fā)送新的請求with open(dirpath + title, 'wb') as f:f.write(response.content)print("下載成功" + name)time.sleep(2)if __name__ == '__main__':jiexi()
如果需要修改為多線程爬蟲,只需要修改主函數(shù)即可,例如創(chuàng)建4個線程進行爬取,案例代碼如下所示:
if __name__ == "__main__":threads = []start = time.time()# 創(chuàng)建四個進程for i in range(1, 5):thread = threading.Thread(target=jiexi(), args=(i,))threads.append(thread)thread.start()for thread in threads:thread.join()end = time.time()running_time = end - startprint('總共消耗時間 : %.5f 秒' % running_time)print("全部完成!") # 主程序
四、圖書推薦
本書介紹了Python3網(wǎng)絡(luò)爬蟲的常見技術(shù)。首先介紹了網(wǎng)頁的基礎(chǔ)知識,然后介紹了urllib、Requests請求庫以及XPath、Beautiful Soup等解析庫,接著介紹了selenium對動態(tài)網(wǎng)站的爬取和Scrapy爬蟲框架,最后介紹了Linux基礎(chǔ),便于讀者自主部署編寫好的爬蟲腳本。
本書主要面向?qū)W(wǎng)絡(luò)爬蟲感興趣的初學(xué)者。