如果有多个URL等待我们爬取,我们通常是一次只能爬取一个,爬取效率低,异步爬虫可以提高爬取效率,可以一次多多个URL同时同时发起请求
异步爬虫方式:
一、多线程、多进程(不建议):可以为爬取阻塞(多个URL等待爬取)单独开启线程或进程,多个爬取URL异步执行(不能开启无限多个)
二、线程池、进程池:可以降低系统对进程或者线程创建和消除的频率,从而降低系统的开销,池中进程或线程的数量是有上限的
一、单线程串行爬取
用时间延时模拟爬取每个网址的耗时时间
单线程爬取一次只能爬取一个,以下面为例,一次爬取一个,爬取4个需要8秒
import time # 模拟爬取每个网址耗时 def get_page(url): time.sleep(2) # 开始时间 start_time = time.time() # URL url_list = ['url1', 'url2', 'url3', 'url4'] for url in url_list: get_page(url) # 结束时间 end_time = time.time() # 输出总耗时 print(end_time-start_time)
一次可以对多个URL同时进行爬取,以下面为例,开启4个进程,则可以对4个URL同时发起请求,总时间为2秒
import time from multiprocessing.dummy import Pool # 模拟爬取每个网址耗时 def get_page(url): time.sleep(2) url_list = ['url1', 'url2', 'url3', 'url4'] # 开始时间 start_time = time.time() # 实例化线程对象,4表示开启了4个进程 pool = Pool(4) # 讲列表中url_list每一个列表元素传递给get_page进行处理 pool.map(get_page, url_list) # 结束时间 end_time = time.time() print(end_time-start_time)
三、单线程+异步协程
event_loop:事件循环,相当于一个无限循环,可以把一些函数注册到这个循环上,当满足某些条件的时候,函数就会被循环执行
coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用async关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
future:代表将来执行或还没有执行的任务,实际上和 task没有本质区别。async定义一个协程。
await用来挂起阻塞方法的执行。
import asyncio async def request(url): print('模拟请求') # 调用async修饰的函数之后返回一个协程对象 c = request('url') # 创建一个事件循环对象 loop = asyncio.new_event_loop() # 将协程对象注册到loop中.然后启动loop loop.run_until_complete(c) print(c) #
task的使用
import asyncio async def request(url): print('模拟请求') # 调用async修饰的函数之后返回一个协程对象 c = request('https://www.baidu.com') # 创建一个事件循环对象 loop = asyncio.new_event_loop() # 基于loop创建一个task对象 task = loop.create_task(c) # 注册循环之前的输出 print(task) loop.run_until_complete(task) # 注册循环之后的输出 print(task) ''' 输出如下> 模拟请求 result=None> '''
future的使用
import asyncio async def request(url): print('模拟请求') # 调用async修饰的函数之后返回一个协程对象 c = request('https://www.baidu.com') # 创建一个事件循环对象 loop = asyncio.new_event_loop() # 基于loop创建一个task对象 task = asyncio.ensure_future(c, loop=loop) print(task) loop.run_until_complete(task) print(task) '''> 模拟请求 result=None> '''
import asyncio async def request(url): print('模拟请求') return url def callback_func(task): # result返回的是任务对象中封装的携程对象对应函数的返回值,即上面返回的url print(task.result()) # async修饰的函数,调用之后返回的一个协程对象 c = request('url') loop = asyncio.new_event_loop() task = asyncio.ensure_future(c, loop=loop) # 将回调函数绑定到任务对象中 task.add_done_callback(callback_func) # task loop.run_until_complete(task) ''' 模拟请求 url '''
在异步协成中,如果出现了同步模块相关的代码,那么就无法实现异步,如下面的time.sleep(2),下面代码没起到异步作用,爬取三个网站需要6秒左右
import asyncio import time async def request(url): print('模拟请求') # 在异步协成中,如果出现了同步模块相关的代码,那么就无法实现异步 time.sleep(2) return url start = time.time() urls = ['aaa', 'bbb', 'ccc'] # 任务列表:存放多个任务对象 stasks = [] # 将任务对象列表注册到事件循环当中 loop = asyncio.new_event_loop() for url in urls: c = request(url) task = asyncio.ensure_future(c, loop=loop) stasks.append(task) # 需要将任务列表封装到wait中 loop.run_until_complete(asyncio.wait(stasks)) print(time.time() - start) ''' 模拟请求 模拟请求 模拟请求 6.002672433853149 '''
这里就需要使用asyncio.sleep,当在asyncio中遇到阻塞操作必须进行手动挂起,使用await挂起,如下方法起到了异步左右,爬取三个URL只需要2秒左右
import asyncio import time async def request(url): print('模拟请求') # 当在asyncio中遇到阻塞操作必须进行手动挂起 await asyncio.sleep(2) return url start = time.time() urls = ['aaa', 'bbb', 'ccc'] # 任务列表:存放多个任务对象 stasks = [] # 将任务对象列表注册到事件循环当中 loop = asyncio.new_event_loop() for url in urls: c = request(url) task = asyncio.ensure_future(c, loop=loop) stasks.append(task) # 需要将任务列表封装到wait中 loop.run_until_complete(asyncio.wait(stasks)) print(time.time() - start) ''' 模拟请求 模拟请求 模拟请求 2.0162434577941895 '''
requests.get请求是基于同步的,那么就无法实现异步,耗时较长
async def request(url): # requests.get请求是基于同步的,那么就无法实现异步 response = requests.get(url=url)
必须使用基于异步的网络请求模块进行请求发送
aiohttp:基于异步网络请求的模块
pip install aiohttp
import aiohttp async def request(url): async with aiohttp.ClientSession() as session: # 返回session对象 # 将get改为post为post请求 # 参数:headers,params/data,proxy='http://ip:port async with await session.get(url) as response: # 返回response对象 # text()返回字符串形式的响应数据 # read()返回二进制形式的响应数据 # json()返回的是json对象 # 注意:在获取响应数据操作之前一定要使用await进行手动挂起 page_text = await response.text()