为什么要用异步爬虫?

 爬虫本质上就是模拟客户端与服务端的通讯过程。以浏览器端的爬虫为例,我们在爬取不同网页过程中,需要根据url构建很多HTTP请求去爬取,而如果以单个线程为参考对象,平常我们所采取的编码习惯,通常是基于同步模式的,也就是串行的方式去执行这些请求,只有当一个url爬取结束后才会进行下一个url的爬取,由于网络IO的延时存在,效率非常低。
 到这里可能会有人说,那么我们可以使用多进程+多线程来提高效率啊,为什么要使用异步编程,毕竟异步编程会大大增加编程难度。【进程、线程、协程简单梳理】在这篇整理文章中有提到,多进程/多线程虽然能提高效率,但是在进程/线程切换的时候,也会消耗很多资源。而且就IO密集型任务来说,虽然使用多线程并发可以提高CUP使用率,提升爬取效率,但是却还是没有解决IO阻塞问题。无论是多进程还是多线程,在遇到IO阻塞时都会被操作系统强行剥夺走CPU的执行权限,爬虫的执行效率因此就降低了下来。而异步编程则是我们在应用程序级别来检测IO阻塞然后主动切换到其他任务执行,以此来'降低'我们爬虫的IO,使我们的爬虫更多的处于就绪态,这样操作系统就会让CPU尽可能多地临幸我们的爬虫,从而提高爬虫的爬取效率。

补充:常见的IO模型有阻塞、非阻塞、IO多路复用,异步。下面举个小栗子来简单描述一下这四个场景。
当快乐的敲代码时光结束时,没有女朋友的单身狗只能约上好基友去召唤师峡谷傲游,当我秒选快乐风男,然后发送“亚索中单,不给就送后”,在队友一片欢声笑语中进入加载界面,奈何遇到小霸王,加载异常缓慢。。。此时!

  1. 你选择什么也不做,直直地看着辅助妹子的琴女原画,等待加载完成。这是阻塞。
  2. 你选择切换出去到某鱼看球,但是你得时不时的切换回LOL,看看是否加载完成,就这样来来回回,累得要死,还错过很多精彩画面。这是非阻塞。
  3. 你掏出自己的爱疯XL,打开某鱼APP看球,这样就不用来回切换,只是时不时地看一下电脑显示器,看是否加载完成了。这是IO多路复用。
  4. 连泡面都舍不得一次用完一包酱包的你,哪有爱疯XL,但是身为码农的尊严让你写个个小程序,在检测到游戏在加载的时候,自动切换到浏览器,打开某鱼的球星直播间。而当游戏加载完成后,自动切换回LOL游戏界面。无缝切换丝滑感受。这个叫异步。

下面开始进入正题

asyncio

在介绍aiohttp、tornado、twisted之前,先了解下python3.4版本引入的标准库asyncio。它可以帮助我们检测IO(只能是网络IO),实现应用程序级别的切换。它的编程模型是一个消息循环。我们可以从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

基本使用

import asyncio
import random
import datetimeurls=['www.baidu.com','www.qq.com','www.douyu.com']@asyncio.coroutine
def crawl(url):print("正在抓取:{}-{}".format(url,datetime.datetime.now().time()))io_time = random.random()*3 #随机模拟网络IO时间yield from asyncio.sleep(io_time) #模拟网络IOprint('{}-抓取完成,用时{}s'.format(url,io_time))loop = asyncio.get_event_loop() #获取EventLoop
loop.run_until_complete(asyncio.wait(map(crawl,urls))) #执行coroutine
loop.close()

运行结果:

正在抓取:www.baidu.com-12:45:26.517226
正在抓取:www.douyu.com-12:45:26.517226
正在抓取:www.qq.com-12:45:26.517226
www.douyu.com-抓取完成,用时0.1250027573049739s
www.baidu.com-抓取完成,用时0.450045918339271s
www.qq.com-抓取完成,用时0.6967129499714361s
[Finished in 0.9s]

运行的时候可以发现三个请求几乎是同时发出的,而返回顺序则是根据网络IO完成时间顺序返回的。

由于asyncio主要应用于TCP/UDP socket通讯,不能直接发送http请求,因此,我们需要自己定义http报头。
补充:

  • 客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行消息报头请求正文三个部分。
    例如:GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n
  • asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法asyncawait,可以让coroutine的代码更简洁易读。

概念补充

  • event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足条件发生的时候,就会调用对应的处理方法。
  • coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到时间循环中,它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
  • task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
  • future:代表将来执行或没有执行的任务的结果,实际上和 task 没有本质区别。

有了以上知识基础,就可以撸代码啦

import asyncio
import uuiduser_agent='Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0'def parse_page(host,res):print('%s 解析结果 %s' %(host,len(res)))with open('%s.html' %(uuid.uuid1()),'wb') as f:f.write(res)async def get_page(host,port=80,url='/',callback=parse_page,ssl=False,encode_set='utf-8'):print('下载 http://%s:%s%s' %(host,port,url))if ssl:port=443#发起tcp连接, IO阻塞操作recv,send=await asyncio.open_connection(host=host,port=port,ssl=ssl) #封装http协议的报头,因为asyncio模块只能封装并发送tcp包,因此这一步需要我们自己封装http协议的包request_headers="""GET {} HTTP/1.0\r\nHost: {}\r\nUser-agent: %s\r\n\r\n""".format(url,host,user_agent) request_headers=request_headers.encode(encode_set)#发送构造好的http请求(request),IO阻塞send.write(request_headers)await send.drain()#接收响应头 IO阻塞操作while True:line=await recv.readline()if line == b'\r\n':breakprint('%s Response headers:%s' %(host,line))#接收响应体 IO阻塞操作text=await recv.read()#执行回调函数callback(host,text)#关闭套接字send.close() #没有recv.close()方法,因为是四次挥手断链接,双向链接的两端,一端发完数据后执行send.close()另外一端就被动地断开if __name__ == '__main__':tasks=[get_page('www.gov.cn',url='/',ssl=False),get_page('www.douyu.com',url='/',ssl=True),]loop=asyncio.get_event_loop()loop.run_until_complete(asyncio.wait(tasks))loop.close()

用上async/await关键字,是不是既简洁,也更便于理解了

自己动手封装HTTP(S)报头确实很麻烦,所以接下来就要请出这一小节的正主aiohttp了,它里面已经帮我们封装好了。
补充:asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大。如果把asyncio用在服务器端,例如Web服务器,由于HTTP连接就是IO操作,因此可以用单线程+coroutine实现多用户的高并发支持。asyncio实现了TCPUDPSSL等协议,aiohttp则是基于asyncio实现的HTTP框架。它分为两部分,一部分是Client(我们将要使用的部分,因为我们爬虫是模拟客户端操作嘛),一部分是 Server,详细的内容可以参考官方文档。

下面我们用aiohttp来改写上面的代码:

import asyncio
import uuid
import aiohttpuser_agent='Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0'def parse_page(url,res):print('{} 解析结果 {}'.format(url,len(res)))with open('{}.html'.format(uuid.uuid1()),'wb') as f:f.write(res)async def get_page(url,callback=parse_page):session = aiohttp.ClientSession()response = await session.get(url)if response.reason == 'OK':result = await response.read()session.close()callback(url,result)if __name__ == '__main__':tasks=[get_page('http://www.gov.cn'),get_page('https://www.douyu.com'),]loop=asyncio.get_event_loop()loop.run_until_complete(asyncio.wait(tasks))loop.close()

是不是更加简洁了呢?


Twisted

twisted是一个网络框架,哪怕刚刚接触python爬虫的萌新都知道的Scrapy爬虫框架,就是基于twisted写的。它其中一个功能就是发送异步请求,检测IO并自动切换。
基于twisted修改上面的代码如下:

from twisted.web.client import getPage,defer
from twisted.internet import reactor
import uuiddef tasks_done(arg):reactor.stop() #停止reactor#定义回调函数
def parse_page(res):print('解析结果 {}'.format(len(res)))with open('{}.html'.format(uuid.uuid1()),'wb') as f:f.write(res)defer_list=[]#初始化一个列表来存放getPage返回的defer对象urls=['http://www.gov.cn','https://www.douyu.com',
]for url in urls:obj = getPage(url.encode('utf-8'),) #getPage会返回一个defer对象obj.addCallback(parse_page) #给defer对象添加回调函数defer_list.append(obj) #将defer对象添加到列表中defer.DeferredList(defer_list).addBoth(tasks_done) #任务列表结束后停止reactor.stopreactor.run #启动监听

这只是一个简单的应用,后面会看情况可能会写一篇Twisted的整理文章。

作者:放风筝的富兰克林
链接:https://www.jianshu.com/p/aa93b7ae2a56
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

异步爬虫-aiohttp库、Twisted库简介相关推荐

  1. 简谈异步爬虫aiohttp

    之前涉及到的爬虫,都基本上使用的requests库进行爬取.但是request获取网站内容的话,是有相应时间的,要不然也不会设置timeout了.         但是,响应时间内,程序也在等待,响应 ...

  2. Python|http|Chrome Developer Tools|Postman|HTTPie|builtwith库|python-whois库|爬虫及解析|语言基础50课:学习(10)

    文章目录 系列目录 原项目地址 第31课:网络数据采集概述 爬虫的应用领域 爬虫合法性探讨 Robots协议 超文本传输协议(HTTP) 相关工具(Chrome Developer Tools.Pos ...

  3. 第17讲:aiohttp 异步爬虫实战

    在上一课时我们介绍了异步爬虫的基本原理和 asyncio 的基本用法,另外在最后简单提及了 aiohttp 实现网页爬取的过程,这一可是我们来介绍一下 aiohttp 的常见用法,以及通过一个实战案例 ...

  4. 异步爬虫模块aiohttp实战之infoq

    点击上方蓝字关注 异步爬虫模块aiohttp实战之infoq 之前写过很多的关于异步的文章,介绍了asyncio的使用,大多的只是通过简单的例子说明了asyncio的使用,但是没有拿出具体的使用例子, ...

  5. asyncio+aiohttp异步爬虫

    概念 进程:进程是一个具有独立功能的程序关于某个数据集合的一次运行活动.进程是操作系统动态执行的基本单元. 线程:一个进程中包含若干线程,当然至少有一个线程,线程可以利用进程所拥有的资源.线程是独立运 ...

  6. Py之cx_Freeze:Python库之cx_Freeze库(程序打包)简介、安装、使用方法详细攻略—案例之实现机器人在线24小时智能翻译

    Py之cx_Freeze:Python库之cx_Freeze库(程序打包)简介.安装.使用方法详细攻略-案例之实现机器人在线24小时智能翻译 导读      将Python程序生成exe程序目前流行这 ...

  7. 【OpenCV图像处理入门学习教程六】基于Python的网络爬虫与OpenCV扩展库中的人脸识别算法比较

    OpenCV图像处理入门学习教程系列,上一篇第五篇:基于背景差分法的视频目标运动侦测 一.网络爬虫简介(Python3) 网络爬虫,大家应该不陌生了.接下来援引一些Jack-Cui在专栏<Pyt ...

  8. python爬虫之Beautiful Soup库,基本使用以及提取页面信息

    一.Beautiful Soup简介 爬虫正则表达式参考:Python 爬虫正则表达式和re库 在爬虫过程中,可以利用正则表达式去提取信息,但是有些人觉得比较麻烦.因为花大量时间分析正则表达式.这时候 ...

  9. muduo网络库:18---muduo简介之(muduo库的由来、编译安装、目录结构、代码结构、线程模型)

    一.由来 2010年3月陈硕先生写了一篇<学之者生,用之者死--ACE历史与简评>(文章参阅:https://blog.csdn.net/Solstice/article/details/ ...

最新文章

  1. JavaScript原始类型转换和进制转换
  2. Oracle AWR 介绍
  3. 若川知乎高赞:有哪些必看的 JS 库?
  4. FFmpeg源代码简单分析:av_write_frame()
  5. python json提取器_入门python爬虫,10分钟就够了,这可能是我见过最简单的基础教学...
  6. Win10自带录屏如何实现录制系统声音
  7. PM Interview 60quiz
  8. cbv本质,前后端交互编码方式,django模板使用的2种方式,模板语法
  9. 捷径|皮皮虾去水印教程
  10. 【年度总结】回顾2021,展望2022,老杨来了
  11. 基于asp.net706酒店客户关系管理系统
  12. 具备对接云平台的4G车牌识别摄像机硬件模组方案
  13. Vue.config.js开发环境与生产环境配置
  14. html语言对ui设计,学ui设计还需要学html5代码?
  15. 集五福开始了 | 如何用5分钟集满五福
  16. 自定义搜狗输入法皮肤(DIY),挺好玩的。。制作全流程
  17. ajax分页类 php,thinkphp之ajax分页类
  18. 腾讯云直播答题方案解析
  19. 低端与高端直流电流采样电流补充说明
  20. XMAN【x天】main

热门文章

  1. scikit_learn 官方文档翻译(集成学习)
  2. 《大道至简》第二篇读后感
  3. python下载文件保存_从URL下载文件并将其保存在Python文件夹中
  4. h标签对html网页的作用,网页H标签SEO价值的说明与举例
  5. designer pyqt5 界面切换_PyQt5快速上手基础篇3-设置窗口标题和图标
  6. 智能机器人路径规划及代码_AI割草机器人用ML+传感器自动规划路径
  7. 计算机控制常用数据通信标准,计算机控制数据通信基础要点.ppt
  8. android指纹java_Android
  9. 如何保证交叉表编译器和目标系统版本一致_嵌入式系统词汇表
  10. oracle排序函数性能,oracle排序函数