一、提升requests模塊的爬取效率
1、多線程和多進程(不建議使用)
2、線程池或進程池(適當使用)
3、單線程+異步協程(爬蟲推薦使用)
二、單線程。爬取某視頻到本地
import re import time import random import requests from lxml import etree start_time = time.time() url = "https://www.pearvideo.com/category_3" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" } ex = 'srcUrl="(.*?)",vdoUrl=srcUrl' def request_video(url): """ 向視頻鏈接發送請求 """ return requests.get(url=url, headers=headers).content def save_video(content): """ 將視頻的二進制數據保存到本地 """ video_name = str(random.randint(100, 999)) + ".mp4" with open(video_name, 'wb') as f: f.write(content) # 獲取首頁源碼 page_text = requests.get(url=url, headers=headers).text tree = etree.HTML(page_text) li_list = tree.xpath('//ul[@class="listvideo-list clearfix"]/li') video_url_list = list() for li in li_list: detail_url = "https://www.pearvideo.com/" + li.xpath('./div/a/@href')[0] # 獲取該視頻頁面的源碼 detail_page_text = requests.get(url=detail_url, headers=headers).text # 正則匹配視頻的URL video_url = re.findall(ex, detail_page_text, re.S)[0] video_url_list.append(video_url) content = request_video(video_url) save_video(content) print("執行耗時: ", time.time() - start_time)
三、線程池或進程池。爬取某視頻到本地
import re import time import random import requests from lxml import etree from multiprocessing.dummy import Pool start_time = time.time() url = "https://www.pearvideo.com/category_3" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" } ex = 'srcUrl="(.*?)",vdoUrl=srcUrl' def request_video(url): """ 向視頻鏈接發送請求 """ return requests.get(url=url, headers=headers).content def save_video(content): """ 將視頻的二進制數據保存到本地 """ video_name = str(random.randint(100, 999)) + ".mp4" with open(video_name, 'wb') as f: f.write(content) # 獲取首頁源碼 page_text = requests.get(url=url, headers=headers).text tree = etree.HTML(page_text) li_list = tree.xpath('//ul[@class="listvideo-list clearfix"]/li') video_url_list = list() for li in li_list: detail_url = "https://www.pearvideo.com/" + li.xpath('./div/a/@href')[0] # 獲取該視頻頁面的源碼 detail_page_text = requests.get(url=detail_url, headers=headers).text # 正則匹配視頻的URL video_url = re.findall(ex, detail_page_text, re.S)[0] video_url_list.append(video_url) pool = Pool(4) #使用線程池將視頻的二進制數據下載下來 content_list = pool.map(request_video, video_url_list) # 使用線程池將視頻的二進制數據保存到本地 pool.map(save_video, content_list) print("執行耗時: ", time.time() - start_time)
四、單線程+異步協程。
1、單線程
import time start_time = time.time() def request(url): print("正在下載", url) time.sleep(2) print("下載完成", url) url_list = [ "https://www.baidu.com", "https://www.taobao.com", "https://www.jd.com" ] for url in url_list: request(url) print("執行耗時:", time.time() - start_time)
2、進程池或線程池
import time from multiprocessing.dummy import Pool start_time = time.time() def request(url): print("正在下載", url) time.sleep(2) print("下載完成", url) url_list = [ "https://www.baidu.com", "https://www.taobao.com", "https://www.jd.com" ] pool = Pool(3) pool.map(request, url_list) print("執行耗時:", time.time() - start_time)
3、協程
a、協程相關的概念
- event_loop:事件循環,相當於一個無限循環,我們可以把一些函數注冊到這個事件循環上,當滿足某些條件的時候,函數就會被循環執行。程序是按照設定的順序從頭執行到尾,運行的次數也是完全按照設定。當在編寫異步程序時,必然其中有部分程序的運行耗時是比較久的,需要先讓出當前程序的控制權,讓其在背后運行,讓另一部分的程序先運行起來。當背后運行的程序完成后,也需要及時通知主程序已經完成任務可以進行下一步操作,但這個過程所需的時間是不確定的,需要主程序不斷的監聽狀態,一旦收到了任務完成的消息,就開始進行下一步。loop就是這個持續不斷的監視器。
- coroutine:中文翻譯叫協程,在 Python 中常指代為協程對象類型,我們可以將協程對象注冊到事件循環中,它會被事件循環調用。我們可以使用 async 關鍵字來定義一個方法,這個方法在調用時不會立即被執行,而是返回一個協程對象。
- task:任務,它是對協程對象的進一步封裝,包含了任務的各個狀態。
- future:代表將來執行或還沒有執行的任務,實際上和 task 沒有本質區別。
- 另外我們還需要了解 async/await 關鍵字,它是從 Python 3.5 才出現的,專門用於定義協程。其中,async 定義一個協程,await 用來使掛起阻塞方法的執行。
b、創建一個協程對象,只需要在函數名前加上一個async關鍵字即可
import time async def request(): print("正在下載") time.sleep(2) print("下載完成") c = request()
c、基本使用
import time import asyncio async def request(): print("正在下載") time.sleep(2) print("下載完成") # 創建一個協程對象 c = request() # 創建一個事件循環對象 loop = asyncio.get_event_loop() # 1. 將協程對象注冊到事件循環中 # 2. 執行事件循環 loop.run_until_complete(c)
d、task的使用
import time import asyncio async def request(): print("下載成功~~") # 創建一個協程對象 c = request() # 創建一個事件循環對象 loop = asyncio.get_event_loop() # 通過事件循環對象創建一個任務, 並將協程對象封裝進這個任務里面 //作用:可以監聽它的狀態 task = loop.create_task(c) print(task) # 1. 將協程對象注冊到事件循環中 # 2. 執行事件循環 loop.run_until_complete(task) print(task)
e、future的使用
import time import asyncio async def request(): print("下載成功~~") # 創建一個協程對象 c = request() # 創建一個事件循環對象 loop = asyncio.get_event_loop() # 通過future創建任務,就不再依附於loop對象,通過asyncio模塊來創建 future = asyncio.ensure_future(c) print(future) # 1. 將協程對象注冊到事件循環中 # 2. 執行事件循環 loop.run_until_complete(future) print(future)
f、回調函數
import time import asyncio def call_back(res): # 通過res.result()就可以接收到requests發送請求返回的響應數據 print("在這里進行數據解析", res.result()) async def request(): print("下載成功~~") return "永靈大神" # 創建一個協程對象 c = request() # 創建一個事件循環對象 loop = asyncio.get_event_loop() # 通過future創建任務,就不再依附於loop對象,通過asyncio模塊來創建 future = asyncio.ensure_future(c) # 通過future定義一個回調函數,這樣的話,協程對象的返回值就可以傳遞給我們的回調函數 future.add_done_callback(call_back) # 1. 將協程對象注冊到事件循環中 # 2. 執行事件循環 loop.run_until_complete(future)
g、回調函數(真正發送請求)
import time import requests import asyncio headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" } def call_back(res): # 通過res.result()就可以接收到requests發送請求返回的響應數據 page_text = res.result() print("在這里進行數據解析", page_text) async def request(): return requests.get(url="https://www.baidu.com", headers=headers).text # 創建一個協程對象 c = request() # 創建一個事件循環對象 loop = asyncio.get_event_loop() # 通過future創建任務,就不再依附於loop對象,通過asyncio模塊來創建 future = asyncio.ensure_future(c) # 通過future定義一個回調函數,這樣的話,協程對象的返回值就可以傳遞給我們的回調函數 future.add_done_callback(call_back) # 1. 將協程對象注冊到事件循環中 # 2. 執行事件循環 loop.run_until_complete(future)
五、多任務異步協程
1、多任務異步協程基本使用
import time import asyncio start_time = time.time() # 特殊函數 async def request(url): print("正在下載: ", url) time.sleep(2) # time模塊為非異步的模塊,在整個異步的代碼中,如果出現了非異步的模塊,則會讓整個異步失去效果 print("下載完成: ", url) url_list = [ "https://www.baidu.com", "https://www.taobao.com", "https://www.jd.com" ] # 創建一個事件循環對象 loop = asyncio.get_event_loop() task_list = list() for url in url_list: c = request(url) # 創建一個任務對象 task = asyncio.ensure_future(c) task_list.append(task) # 如果傳過來的是一個列表的話,需要再加上一層封裝, 使用asyncio.wait()方法進行封裝 loop.run_until_complete(asyncio.wait(task_list)) print("執行耗時:", time.time() - start_time)
2、多任務異步協程(解決方案)
import time import asyncio start_time = time.time() # 特殊函數 async def request(url): print("正在下載: ", url) # time.sleep(2) # time模塊為非異步的模塊,在整個異步的代碼中,如果出現了非異步的模塊,則會讓整個異步失去效果 await asyncio.sleep(2) print("下載完成: ", url) url_list = [ "https://www.baidu.com", "https://www.taobao.com", "https://www.jd.com" ] # 創建一個事件循環對象 loop = asyncio.get_event_loop() task_list = list() for url in url_list: c = request(url) # 創建一個任務對象 task = asyncio.ensure_future(c) task_list.append(task) # 如果傳過來的是一個列表的話,需要再加上一層封裝, 使用asyncio.wait()方法進行封裝 loop.run_until_complete(asyncio.wait(task_list)) print("執行耗時:", time.time() - start_time)
3、將多任務異步協程應用到爬蟲中
import time import asyncio import requests start_time = time.time() headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" } # 特殊函數 async def request(url): print("正在下載: ", url) # requests模塊為非異步的請求模塊,如果使用它,將會使整個異步失去效果 page_text = requests.get(url=url, headers=headers).text print("返回的數據", page_text) print("下載完成: ", url) url_list = [ "http://127.0.0.1:5000/tiger", "http://127.0.0.1:5000/tom", "http://127.0.0.1:5000/jay" ] # 創建一個事件循環對象 loop = asyncio.get_event_loop() task_list = list() for url in url_list: c = request(url) # 創建一個任務對象 task = asyncio.ensure_future(c) task_list.append(task) # 如果傳過來的是一個列表的話,需要再加上一層封裝, 使用asyncio.wait()方法進行封裝 loop.run_until_complete(asyncio.wait(task_list)) print("執行耗時:", time.time() - start_time)
4、將多任務異步協程應用到爬蟲中(解決方案)
import time import asyncio import aiohttp import requests start_time = time.time() headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" } # 特殊函數 async def request(url): print("正在下載: ", url) # 此session非requests.Session, 這個session支持異步的網絡並發請求 async with aiohttp.ClientSession() as session: async with await session.get(url=url) as response: page_text = await response.text() # text()頁面源碼 read()二進制數據 json() print("返回的數據", page_text) print("下載完成: ", url) url_list = [ "http://127.0.0.1:5000/tiger", "http://127.0.0.1:5000/tom", "http://127.0.0.1:5000/jay" ] # 創建一個事件循環對象 loop = asyncio.get_event_loop() task_list = list() for url in url_list: c = request(url) # 創建一個任務對象 task = asyncio.ensure_future(c) task_list.append(task) # 如果傳過來的是一個列表的話,需要再加上一層封裝, 使用asyncio.wait()方法進行封裝 loop.run_until_complete(asyncio.wait(task_list)) print("執行耗時:", time.time() - start_time)
5、如何實現數據解析(任務的綁定回調機制)
import time import asyncio import aiohttp import requests start_time = time.time() headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" } def call_back(task): print("在這里進行數據解析") print("I am call_back") print(task.result()) # 特殊函數 async def request(url): # 此session非requests.Session, 這個session支持異步的網絡並發請求 async with aiohttp.ClientSession() as session: async with await session.get(url=url) as response: page_text = await response.text() # text()頁面源碼 read()二進制數據 json() print("返回的數據", page_text) return page_text url_list = [ "http://127.0.0.1:5000/tiger", "http://127.0.0.1:5000/tom", "http://127.0.0.1:5000/jay" ] # 創建一個事件循環對象 loop = asyncio.get_event_loop() task_list = list() for url in url_list: c = request(url) # 創建一個任務對象 task = asyncio.ensure_future(c) task.add_done_callback(call_back) task_list.append(task) # 如果傳過來的是一個列表的話,需要再加上一層封裝, 使用asyncio.wait()方法進行封裝 loop.run_until_complete(asyncio.wait(task_list)) print("執行耗時:", time.time() - start_time)