背景
前几天看了网上的一个视频,将异步执行的,试了一下,效果杠杠的
文章地址:https://www.cnblogs.com/bobo-zhang/p/10735140.html
对比
与上一篇非异步执行的放在一起测试,立竿见影
非异步执行:
-------非异步执行,爬取10个页面并写入本地,耗时3秒多
异步执行:
-------异步执行,爬取10个页面并写入本地,耗时仅0.4秒
异步执行背景
转载自博客园:博客网址:https://www.cnblogs.com/bobo-zhang/p/10735140.html
背景:
其实爬虫的本质就是client发请求批量获取server的响应数据,
如果我们有多个url待爬取,只用一个线程且采用串行的方式执行,那只能等待爬取一个结束后才能继续下一个,效率会非常低。
需要强调的是:对于单线程下串行N个任务,并不完全等同于低效,如果这N个任务都是纯计算的任务,
那么该线程对cpu的利用率仍然会很高,之所以单线程下串行多个爬虫任务低效,是因为爬虫任务是明显的IO密集型(阻塞)程序。
那么该如何提高爬取性能呢?
- a. 解决同步调用方案之多线程/多进程
好处:在服务器端使用多线程(或多进程)。
多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
弊端:开启多进程或都线程的方式,我们是无法无限制地开启多进程或多线程的:在遇到要同时响应成百上千路的连接请求,
则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而且线程与进程本身也更容易进入假死状态。 - b. 解决同步调用方案之线程/进程池
好处:很多程序员可能会考虑使用“线程池”或“连接池”。
“线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。可以很好的降低系统开销。
弊端:“线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。
而且,所谓“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。
所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。
以下是测试的源码,具体用法解析参见原博客文章
源码
"""
高性能异步爬虫
"""
from lxml import etree
import time
import asyncio
import aiohttp
headers = {'User-Agent': 'Mozilla/5.0'}
# 抓取一个网址是0.3秒
# url = 'https://www.qiushibaike.com/text/page/1'
# 需要用asihttp来请求,因为requests不支持异步模式
async def req(url):
async with aiohttp.ClientSession() as s:
async with await s.get(url) as response:
page_text = await response.text()
return page_text
def parse(task):
page_text = task.result()
tree = etree.HTML(page_text)
div_list = tree.xpath('//div[@class="col1 old-style-col1"]/div')
for div in div_list:
author = div.xpath('./div[@class="author clearfix"]/a[2]/h2/text()')[0]
detail_text = div.xpath('.//div[@class="content"]/span[1]//text()')
detail_text = ''.join(detail_text)
fp.write(author + detail_text + '\n\n\n\n')
print('----单线程多任务异步执行----')
fp = open('./qiubai_download_yibuzhixing.txt', 'w', encoding='utf-8')
page = input('请输入爬取页数:')
start = time.time()
url_head = 'https://www.qiushibaike.com/text/page/'
urls = []
for i in range(1, int(page) + 1):
url_each = url_head + str(i)
urls.append(url_each)
tasks = []
for url in urls:
c = req(url)
# 将异步执行进行task对象封装
task = asyncio.ensure_future(c)
# 给task任务对象添加callback
task.add_done_callback(parse)
# 将多任务task添加到列表
tasks.append(task)
# 创建循环体
loop = asyncio.get_event_loop()
# 将task放入循环体,并开始执行,需添加wait
loop.run_until_complete(asyncio.wait(tasks))
fp.close()
print(time.time() - start)