一、提升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)