Python?多進(jìn)程、協(xié)程異步抓取英雄聯(lián)盟皮膚并保存在本地

傳統(tǒng)數(shù)據(jù)抓取 VS 高性能數(shù)據(jù)抓取
傳統(tǒng)的數(shù)據(jù)抓取都是運(yùn)行在單線程上的,先用獲取到目標(biāo)頁面中最大的頁數(shù),然后循環(huán)抓取每個(gè)單頁數(shù)據(jù)并進(jìn)行解析,按照這樣的思路,會有大量的時(shí)間都浪費(fèi)在等待請求傳回的數(shù)據(jù)上面,如果在等待第一個(gè)頁面返回的數(shù)據(jù)時(shí)去請求第二個(gè)頁面,就能有效地提高效率,下面我們就通過單線程、多進(jìn)程以及異步協(xié)程的方式分別來簡單的實(shí)踐一下。頁面分析
目標(biāo)網(wǎng)站:https://lol.qq.com/data/info-heros.shtml


json的數(shù)據(jù)格式來進(jìn)行傳輸?shù)?,并且存放皮膚的url也是有一定規(guī)律的,和英雄的ID相掛鉤url1 = 'https://game.gtimg.cn/images/lol/act/img/js/hero/1.js'
url2 = 'https://game.gtimg.cn/images/lol/act/img/js/hero/2.js'
url3 = 'https://game.gtimg.cn/images/lol/act/img/js/hero/3.js'
url4 = 'https://game.gtimg.cn/images/lol/act/img/js/hero/4.js'
因此我們也可以自己來構(gòu)造這個(gè)url格式'https://game.gtimg.cn/images/lol/act/img/js/hero/{}.js'.format(i)
單線程方案
我們先來看一下單線程的方案def get_page():
page_urls = []
for i in range(1, 10):
url = 'https://game.gtimg.cn/images/lol/act/img/js/hero/{}.js'.format(i)
page_urls.append(url)
return page_urls
# 獲取各英雄皮膚的鏈接
def get_img_urls():
results_list = []
page_urls = get_page()
for page_url in page_urls:
res = requests.get(page_url, headers=headers)
result = res.content.decode('utf-8')
result_dict = json.loads(result)
skins_list = result_dict["skins"]
for skin in skins_list:
hero_dict = {}
hero_dict['name'] = skin["heroName"]
hero_dict['skin_name'] = skin["name"]
if skin["mainImg"] == '':
continue
hero_dict['imgUrl'] = skin["mainImg"]
results_list.append(hero_dict)
time.sleep(2)
return results_list
# 將各種皮膚保存到本地
def save_image(index, img_url):
path = "skin/" img_url["name"]
if not os.path.exists(path):
os.makedirs(path)
response = requests.get(img_url['imgUrl'], headers = headers).content
with open('./skin/' img_url['name'] '/' img_url['skin_name'] str(index) '.jpg', 'wb') as f:
f.write(response)
上面的代碼分別代表的獲取各英雄每個(gè)皮膚的鏈接,然后再將各英雄的皮膚圖片保存到本地,通過一個(gè)主函數(shù)將上面的步驟都串聯(lián)到一起def main():
img_urls = get_img_urls()
print("總共有{}個(gè)網(wǎng)頁".format(len(img_urls)))
for index, img_url in enumerate(img_urls):
print("目前正處于第{}個(gè)".format(img_urls.index(img_url)))
save_image(index, img_url)
print("Done")
爬取這幾個(gè)網(wǎng)頁然后保存到本地的時(shí)間總共是需要43秒的時(shí)間,接下來我們來看一下多進(jìn)程的爬取所需要的時(shí)間。
多進(jìn)程的抓取方案
首先來簡單的介紹一下進(jìn)程,進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的最小單位,每一個(gè)進(jìn)程都有自己獨(dú)立的地址空間,不同進(jìn)程之間的內(nèi)存空間不共享,進(jìn)程與進(jìn)程之間的通信是由操作系統(tǒng)來傳遞的,因此通訊效率低,切換開銷大。這里我們簡單的用多進(jìn)程來抓取一下各個(gè)英雄的皮膚def main():
img_urls = get_img_urls()
print("總共有{}個(gè)網(wǎng)頁".format(len(img_urls)))
pools = multiprocessing.Pool(len(img_urls))
for index_1, img_url in enumerate(img_urls):
print("目前正處于第{}個(gè)".format(img_urls.index(img_url)))
pools.apply_async(save_image, args=(index_1, img_url, ))
pools.close() # 關(guān)閉進(jìn)程池(pool),使其不在接受新的任務(wù)。
pools.join() # 主進(jìn)程阻塞等待子進(jìn)程的退出, join方法要在close或terminate之后使用
print("Done")
整體下來需要的時(shí)間是29秒,比上面的單線程要快出許多。
異步協(xié)程的抓取方案
與異步相對立的則是同步,顧名思義,同步具體指的各個(gè)任務(wù)并不是獨(dú)立進(jìn)行的,而是按照順序交替進(jìn)行下去的,在一個(gè)任務(wù)進(jìn)行完之后得到結(jié)果才進(jìn)行下一個(gè)的任務(wù)。而異步則是各個(gè)任務(wù)可以獨(dú)立的運(yùn)行,一個(gè)任務(wù)的運(yùn)行不受另外一個(gè)任務(wù)的影響。而這里提到的協(xié)程,英文叫做Coroutine,也稱為是微線程,是一種用戶態(tài)的輕量級線程,擁有自己的寄存器上下文和棧,在進(jìn)行調(diào)度切換時(shí),將寄存器上下文和棧保存到其他地方,在切回來的時(shí)候恢復(fù)先前保存的寄存器上下文和棧。
我們可以利用協(xié)程來實(shí)現(xiàn)異步操作,比如在發(fā)出請求的時(shí)候,需要等一段時(shí)間才能得到返回的結(jié)果,但其實(shí)這個(gè)等待的時(shí)候程序完全可以干其他許多的事情,在響應(yīng)返回之后再切換回來繼續(xù)處理,這樣可以充分利用 CPU 和其他資源。我們這里用協(xié)程來抓取一下各個(gè)英雄的皮膚async def save_image(index, img_url):
path = "skin/" img_url["name"]
if not os.path.exists(path):
os.makedirs(path)
response = requests.get(img_url['imgUrl'], headers = headers).content
with open('./skin/' img_url['name'] '/' img_url['skin_name'] str(index) '.jpg', 'wb') as f:
f.write(response)
def main():
loop = asyncio.get_event_loop()
img_urls = get_img_urls()
print("總共有{}個(gè)網(wǎng)頁".format(len(img_urls)))
tasks_list = [save_image(index, img_url) for index, img_url in enumerate(img_urls)]
try:
loop.run_until_complete(asyncio.wait(tasks_list))
finally:
loop.close()
print("Done")
一整個(gè)跑下來,大概是需要33秒的時(shí)間,也是比單線程的43秒要快出很多以上便是用單線程、多進(jìn)程以及異步協(xié)程的方式來優(yōu)化爬蟲腳本的性能,感興趣的讀者可以自己照著上面的教程與步驟自己去敲一遍代碼,感謝閱讀。