多線程爬取數(shù)據(jù),主要是用Python的線程池ThreadPoolExecutor。
主要代碼如下:
# -*- coding:utf-8 -*-
import json
import requests
import os
from concurrent.futures import ThreadPoolExecutor
class Wzry_Cosplay_Spider(object):
def __init__(self):
"""定義存儲(chǔ)目錄"""
self.base_dir = './wzry_cosplay_pics/'
"""定義請(qǐng)求Header頭"""
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'}
def get_pics(self):
n = 1
while True:
"""獲取圖片資源api地址"""
url = f'http://gamehelper.gm825.com/wzry/gallery/list?pn={n}'
try:
r = requests.get(url, headers=self.headers)
json_data = json.loads(r.text)
pics_list = json_data.get('list')
if len(pics_list) != 0:
for pic in pics_list:
pic_title = pic.get('title')
pic_imgs = pic.get('thumb_img')
"""將標(biāo)題和圖片地址列表返回"""
yield pic_title, pic_imgs
else:
break
n += 1
except:
pass
def download_pics(self, dirname, imgs):
path = self.base_dir + dirname
try:
"""遞歸創(chuàng)建文件夾"""
os.makedirs(path)
except:
pass
"""循環(huán)下載圖片"""
for img in imgs:
filename = img.split('/')[-1]
r = requests.get(img, headers=self.headers)
with open(path + '/' + filename, 'wb') as f:
f.write(r.content)
print(f'----------------------------下載{filename}成功----------------------------')
print(f"----------------------------{dirname}]下載完成----------------------------")
def main():
"""使用線程池,創(chuàng)建四個(gè)線程"""
pool = ThreadPoolExecutor(max_workers=4)
"""獲取類對(duì)象"""
wzry_cosplay_spider = Wzry_Cosplay_Spider()
"""調(diào)用對(duì)象方法獲取標(biāo)題和圖片地址列表"""
for pic_title, pic_imgs in wzry_cosplay_spider.get_pics():
"""使用線程下載"""
pool.submit(wzry_cosplay_spider.download_pics, pic_title, pic_imgs)
"""關(guān)閉線程池"""
pool.shutdown()
if __name__ == '__main__':
main()
爬取到的資源如下(小姐姐還是蠻養(yǎng)眼的):

ThreadPoolExecutor構(gòu)造實(shí)例的時(shí)候,傳入max_workers參數(shù)來設(shè)置線程池中最多能同時(shí)運(yùn)行的線程數(shù)目。使用submit函數(shù)來提交線程需要執(zhí)行的任務(wù)(函數(shù)名[wzry_cosplay_spider對(duì)象的get_pics函數(shù)]和參數(shù)[圖片標(biāo)題和下載地址列表])到線程池中,并返回該任務(wù)的句柄(類似于文件、畫圖),注意submit()不是阻塞的,而是立即返回。通過submit函數(shù)返回的任務(wù)句柄,還可以能夠使用done()方法判斷該任務(wù)是否結(jié)束。最后通過shutdown()函數(shù)來關(guān)閉線程池。
線程池介紹
線程池的基類是 concurrent.futures 模塊中的 Executor,Executor 提供了兩個(gè)子類,即 ThreadPoolExecutor和 ProcessPoolExecutor,其中 ThreadPoolExecutor 用于創(chuàng)建線程池,而 ProcessPoolExecutor 用于創(chuàng)建進(jìn)程池。
如果使用線程池/進(jìn)程池來管理并發(fā)編程,那么只要將相應(yīng)的 task 函數(shù)提交給線程池/進(jìn)程池,剩下的事情就由線程池/進(jìn)程池來搞定。
Exectuor 提供了如下常用方法:
submit(fn, *args, **kwargs):將 fn函數(shù)提交給線程池。*args 代表傳給 fn 函數(shù)的參數(shù),*kwargs 代表以關(guān)鍵字參數(shù)的形式為 fn 函數(shù)傳入?yún)?shù)。
map(func, *iterables, timeout=None, chunksize=1):該函數(shù)類似于全局函數(shù) map(func, *iterables),只是該函數(shù)將會(huì)啟動(dòng)多個(gè)線程,以異步方式立即對(duì) iterables 執(zhí)行 map 處理。
shutdown(wait=True):關(guān)閉線程池。
程序?qū)?task 函數(shù)提交(submit)給線程池后,submit方法會(huì)返回一個(gè)Future 對(duì)象,Future類主要用于獲取線程任務(wù)函數(shù)的返回值。由于線程任務(wù)會(huì)在新線程中以異步方式執(zhí)行,因此,線程執(zhí)行的函數(shù)相當(dāng)于一個(gè)“將來完成”的任務(wù),所以Python使用 Future 來代表。
使用線程池來執(zhí)行線程任務(wù)的步驟如下:
1.調(diào)用 ThreadPoolExecutor 類的構(gòu)造器創(chuàng)建一個(gè)線程池。
2.定義一個(gè)普通函數(shù)作為線程任務(wù)。
3.調(diào)用 ThreadPoolExecutor 對(duì)象的 submit() 方法來提交線程任務(wù)。
4.當(dāng)不想提交任何任務(wù)時(shí),調(diào)用 ThreadPoolExecutor 對(duì)象的 shutdown() 方法來關(guān)閉線程池。