引言:我们在写爬虫时常会遇到这样的问题,当需要爬取多个URL时,写一个普通的基于requests库的爬虫程序爬取时间会很长。因为是顺序请求网页的,而网页请求获得响应过程比较耗费时间,程序不得不等待获得当前网页响应后才能进行下一个URL的爬取,使得总耗时较多。对于这样的多任务,可以使用基于多进程(multiprocessing)和基于Asyncio库的异步(协程)爬虫增强并发性,加速爬虫。

本文首发于我的CSDN博客:SL_World

Talk is cheap,show me the picture!

在讲解之前,我们先来通过一幅图看清多进程协程的爬虫之间的原理及其区别。(图片来源于网络)这里,异步爬虫不同于多进程爬虫,它使用单线程(即仅创建一个事件循环,然后把所有任务添加到事件循环中)就能并发处理多任务。在轮询到某个任务后,当遇到耗时操作(如请求URL)时,挂起该任务并进行下一个任务,当之前被挂起的任务更新了状态(如获得了网页响应),则被唤醒,程序继续从上次挂起的地方运行下去。极大的减少了中间不必要的等待时间。

  1. 对于协程(Asyncio库)的原理及实现请见:《Python异步IO之协程(详解)》
  2. 对于多进程的知识讲解及实现请见:《廖雪峰-Python多进程》

在有了Asyncio异步IO库实现协程后,我们还需要实现异步网页请求。因此,aiohttp库应运而生。

一、使用aiohttp库实现异步网页请求

在我们写普通的爬虫程序时,经常会用到requests库用以请求网页获得服务器响应。而在协程中,由于requests库提供的相关方法不是可等待对象(awaitable),使得无法放在await后面,因此无法使用requests库在协程程序中实现请求。

在此,官方专门提供了一个aiohttp库,用来实现异步网页请求等功能,简直就是异步版的requests库,当然需要我们手动安装该库(如下所示)。

>>> pip3 install aiohttp

【基础实现】:在官方文档中,推荐使用ClientSession()函数来调用网页请求等相关方法。首先,我们需要引入aiohttp模块。

import aiohttp

然后,我们在协程中使用ClientSession()get()request()方法来请求网页。(其中async with是异步上下文管理器,其封装了异步实现等功能)

async with aiohttp.ClientSession() as session:    async with session.get('http://httpbin.org/get') as resp:        print(resp.status)        print(await resp.text())

ClientSession()除了有请求网页的方法,官方API还提供了其他HTTP常见方法。

session.request(method='GET', url='http://httpbin.org/request')session.post('http://httpbin.org/post', data=b'data')session.put('http://httpbin.org/put', data=b'data')session.delete('http://httpbin.org/delete')session.head('http://httpbin.org/get')session.options('http://httpbin.org/get')session.patch('http://httpbin.org/patch', data=b'data')

如欲看完整的aiohttp使用方法,请见官方文档。

【案例】

【任务】:爬取2018年AAAI顶会中10篇论文的标题【已知】:10个论文页面URL。如欲提前看所有代码请见:GitHub

urls = [    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16488',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16583',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16380',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16911',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16581',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16674',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16112',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/17343',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16659',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16449',]

一、测试普通爬虫程序

下面是一个普通的同步代码,实现顺序爬取10个URL的title

import timefrom lxml import etreeimport requestsurls = [    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16488',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16583',    # 省略后面8个url...]'''提交请求获取AAAI网页,并解析HTML获取title'''def get_title(url,cnt):    response = requests.get(url)  # 提交请求,获取响应内容    html = response.content       # 获取网页内容(content返回的是bytes型数据,text()获取的是Unicode型数据)    title = etree.HTML(html).xpath('//*[@id="title"]/text()') # 由xpath解析HTML    print('第%d个title:%s' % (cnt,''.join(title)))

if __name__ == '__main__':    start1 = time.time()    i = 0    for url in urls:        i = i + 1        start = time.time()        get_title(url,i)        print('第%d个title爬取耗时:%.5f秒' % (i,float(time.time() - start)))    print('爬取总耗时:%.5f秒' % float(time.time()-start1))

执行结果如下:

第1个title:Norm Conflict Resolution in Stochastic Domains第1个title爬取耗时:1.41810秒第2个title:Algorithms for Trip-Vehicle Assignment in Ride-Sharing第2个title爬取耗时:1.31734秒第3个title:Tensorized Projection for High-Dimensional Binary Embedding第3个title爬取耗时:1.31826秒第4个title:Synthesis of Programs from Multimodal Datasets第4个title爬取耗时:1.28625秒第5个title:Video Summarization via Semantic Attended Networks第5个title爬取耗时:1.33226秒第6个title:TIMERS: Error-Bounded SVD Restart on Dynamic Networks第6个title爬取耗时:1.52718秒第7个title:Memory Management With Explicit Time in Resource-Bounded Agents第7个title爬取耗时:1.35522秒第8个title:Mitigating Overexposure in Viral Marketing第8个title爬取耗时:1.35722秒第9个title:Neural Link Prediction over Aligned Networks第9个title爬取耗时:1.51317秒第10个title:Dual Deep Neural Networks Cross-Modal Hashing第10个title爬取耗时:1.30624秒爬取总耗时:13.73324秒

可见,平均每请求完一个URL并解析该HTML耗时1.4秒左右。本次程序运行总耗时13.7秒。

二、测试基于协程的异步爬虫程序

下面,是使用了协程的异步爬虫程序。etree模块用于解析HTML,aiohttp是一个利用asyncio的库,它的API看起来很像请求的API,可以暂时看成协程版的requests

import timefrom lxml import etreeimport aiohttpimport asynciourls = [    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16488',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16583',    # 省略后面8个url...]titles = []sem = asyncio.Semaphore(10) # 信号量,控制协程数,防止爬的过快'''提交请求获取AAAI网页,并解析HTML获取title'''async def get_title(url):    with(await sem):        # async with是异步上下文管理器        async with aiohttp.ClientSession() as session:  # 获取session            async with session.request('GET', url) as resp:  # 提出请求                # html_unicode = await resp.text()                 # html = bytes(bytearray(html_unicode, encoding='utf-8'))                html = await resp.read() # 可直接获取bytes                 title = etree.HTML(html).xpath('//*[@id="title"]/text()')                print(''.join(title))'''调用方'''def main():    loop = asyncio.get_event_loop()           # 获取事件循环    tasks = [get_title(url) for url in urls]  # 把所有任务放到一个列表中    loop.run_until_complete(asyncio.wait(tasks)) # 激活协程    loop.close()  # 关闭事件循环

if __name__ == '__main__':    start = time.time()    main()  # 调用方    print('总耗时:%.5f秒' % float(time.time()-start))

执行结果如下:

Memory Management With Explicit Time in Resource-Bounded AgentsNorm Conflict Resolution in Stochastic DomainsVideo Summarization via Semantic Attended NetworksTensorized Projection for High-Dimensional Binary EmbeddingAlgorithms for Trip-Vehicle Assignment in Ride-SharingDual Deep Neural Networks Cross-Modal HashingNeural Link Prediction over Aligned NetworksMitigating Overexposure in Viral MarketingTIMERS: Error-Bounded SVD Restart on Dynamic NetworksSynthesis of Programs from Multimodal Datasets总耗时:2.43371秒

可见,本次我们使用协程爬取10个URL只耗费了2.4秒,效率是普通同步程序的8~12倍。【解释】:

  1. request获取的text()返回的是网页的Unicode型数据contentread()返回的是bytes型数据。而etree.HTML(html)接收的参数需是bytes类型,所以①可以通过resp.read()直接获取bytes;②若使用text()则需要通过先把Unicode类型数据转换成比特数组对象,再转换成比特对象, 即bytes(bytearray(html_unicode, encoding='utf-8'))
  2. 发起请求除了可以用上述session.request('GET', url)也可以用session.get(url),功能相同。
  3. 如果同时做太多的请求,链接有可能会断掉。所以需要使用sem = asyncio.Semaphore(10)Semaphore限制同时工作的协同程序数量同步工具
  4. async with是异步上下文管理器,不解的请看Python中的async with用法。

三、测试基于多进程的分布式爬虫程序

下面,我们测试多进程爬虫程序,由于我的电脑CPU是4核,所以这里进程池我就设的4。

import multiprocessingfrom multiprocessing import Poolimport timeimport requestsfrom lxml import etreeurls = [    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16488',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16583',    # 省略后面8个url...]'''提交请求获取AAAI网页,并解析HTML获取title'''def get_title(url,cnt):    response = requests.get(url)  # 提交请求    html = response.content       # 获取网页内容    title = etree.HTML(html).xpath('//*[@id="title"]/text()') # 由xpath解析HTML    print('第%d个title:%s' % (cnt,''.join(title)))'''调用方'''def main():    print('当前环境CPU核数是:%d核' % multiprocessing.cpu_count())    p = Pool(4)  # 进程池    i = 0    for url in urls:        i += 1        p.apply_async(get_title, args=(url, i))    p.close()    p.join()   # 运行完所有子进程才能顺序运行后续程序

if __name__ == '__main__':    start = time.time()    main()  # 调用方    print('总耗时:%.5f秒' % float(time.time()-start))

执行结果:

当前环境CPU核数是:4核第2个title:Algorithms for Trip-Vehicle Assignment in Ride-Sharing第1个title:Norm Conflict Resolution in Stochastic Domains第4个title:Synthesis of Programs from Multimodal Datasets第3个title:Tensorized Projection for High-Dimensional Binary Embedding第5个title:Video Summarization via Semantic Attended Networks第6个title:TIMERS: Error-Bounded SVD Restart on Dynamic Networks第7个title:Memory Management With Explicit Time in Resource-Bounded Agents第8个title:Mitigating Overexposure in Viral Marketing第9个title:Neural Link Prediction over Aligned Networks第10个title:Dual Deep Neural Networks Cross-Modal Hashing总耗时:5.01228秒

可见,多进程分布式爬虫也比普通同步程序要快很多,本次运行时间5秒。但比协程略慢。

【时间对比】:对于上例中10个URL的爬取时间,下面整理成了表格。

CPU核数\实现方式 普通同步爬虫 多进程爬虫 异步爬虫
4核 13.7秒 5.0秒 2.4秒

其中增加多进程中进程池Pool(n)n可加速爬虫,下图显示了消耗的时间(单位.秒)和Pool()参数的关系。如果你以为到这里就结束了,那你就要错过最精彩的东西了:)

四、测试-异步结合多进程-爬虫程序

由于解析HTML也需要消耗一定的时间,而aiohttpasyncio均未提供相关解析方法。所以可以在请求网页的时使用异步程序,在解析HTML使用多进程,两者配合使用,效率更高哦~!【请求网页】:使用协程。【解析HTML】:使用多进程

from multiprocessing import Poolimport timefrom lxml import etreeimport aiohttpimport asynciourls = [    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16488',    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16583',    # 省略后面8个url...]htmls = []titles = []sem = asyncio.Semaphore(10) # 信号量,控制协程数,防止爬的过快'''提交请求获取AAAI网页html'''async def get_html(url):    with(await sem):        # async with是异步上下文管理器        async with aiohttp.ClientSession() as session:  # 获取session            async with session.request('GET', url) as resp:  # 提出请求                html = await resp.read() # 直接获取到bytes                htmls.append(html)                print('异步获取%s下的html.' % url)

'''协程调用方,请求网页'''def main_get_html():    loop = asyncio.get_event_loop()           # 获取事件循环    tasks = [get_html(url) for url in urls]  # 把所有任务放到一个列表中    loop.run_until_complete(asyncio.wait(tasks)) # 激活协程    loop.close()  # 关闭事件循环'''使用多进程解析html'''def multi_parse_html(html,cnt):    title = etree.HTML(html).xpath('//*[@id="title"]/text()')    titles.append(''.join(title))    print('第%d个html完成解析-title:%s' % (cnt,''.join(title)))'''多进程调用总函数,解析html'''def main_parse_html():    p = Pool(4)    i = 0    for html in htmls:        i += 1        p.apply_async(multi_parse_html,args=(html,i))    p.close()    p.join()

if __name__ == '__main__':    start = time.time()    main_get_html()   # 调用方    main_parse_html() # 解析html    print('总耗时:%.5f秒' % float(time.time()-start))

执行结果如下:

异步获取https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16380下的html.异步获取https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16674下的html.异步获取https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16583下的html.异步获取https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16911下的html.异步获取https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/17343下的html.异步获取https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16449下的html.异步获取https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16488下的html.异步获取https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16659下的html.异步获取https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16581下的html.异步获取https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16112下的html.第3个html完成解析-title:Algorithms for Trip-Vehicle Assignment in Ride-Sharing第1个html完成解析-title:Tensorized Projection for High-Dimensional Binary Embedding第2个html完成解析-title:TIMERS: Error-Bounded SVD Restart on Dynamic Networks第4个html完成解析-title:Synthesis of Programs from Multimodal Datasets第6个html完成解析-title:Dual Deep Neural Networks Cross-Modal Hashing第7个html完成解析-title:Norm Conflict Resolution in Stochastic Domains第8个html完成解析-title:Neural Link Prediction over Aligned Networks第5个html完成解析-title:Mitigating Overexposure in Viral Marketing第9个html完成解析-title:Video Summarization via Semantic Attended Networks第10个html完成解析-title:Memory Management With Explicit Time in Resource-Bounded Agents

【参考文献】:

[1] aiohttp官方API文档

[2] 加速爬虫: 异步加载 Asyncio

[3] python:利用asyncio进行快速抓取

[4] 使用 aiohttp 和 asyncio 进行异步请求

[5] requests的content与text导致lxml的解析问题

python 协程可以嵌套协程吗_Python实战异步爬虫(协程)+分布式爬虫(多进程)相关推荐

  1. python爬虫:scrapy-redis分布式爬虫(详细版)

    本文是将现有的scrapy爬虫改造为分布式爬虫,为详细版,简略版请看https://blog.csdn.net/Aacheng123/article/details/114265960 使用scrap ...

  2. python微博爬虫教程_Python爬虫教程-新浪微博分布式爬虫分享

    爬虫功能: 此项目实现将单机的新浪微博爬虫重构成分布式爬虫. Master机只管任务调度,不管爬数据:Slaver机只管将Request抛给Master机,需要Request的时候再从Master机拿 ...

  3. python异步爬虫_Python实战异步爬虫(协程)+分布式爬虫(多进程)

    转自:https://blog.csdn.net/SL_World/article/details/86633611 在讲解之前,我们先来通过一幅图看清多进程和协程的爬虫之间的原理及其区别.(图片来源 ...

  4. python中123+5.0的执行结果_python实战笔记(一)

    [Python注释] [Python变量] [Python运算符] [Python输入输出] *   [输入函数] *   [输出函数(3.x)] *   [格式化输出] [分支] [循环] ### ...

  5. python分布式爬虫系统_如何构建一个分布式爬虫:理论篇

    前言 本系列文章计划分三个章节进行讲述,分别是理论篇.基础篇和实战篇.理论篇主要为构建分布式爬虫而储备的理论知识,基础篇会基于理论篇的知识写一个简易的分布式爬虫,实战篇则会以微博为例,教大家做一个比较 ...

  6. python分布式爬虫系统_三种分布式爬虫系统的架构方式

    分布式爬虫系统广泛应用于大型爬虫项目中,力求以最高的效率完成任务,这也是分布式爬虫系统的意义所在. 分布式系统的核心在于通信,介绍三种分布式爬虫系统的架构思路,都是围绕通信开始,也就是说有多少分布式系 ...

  7. python实习目的_python爬虫系列---为什么要学习爬虫

    (0)为什么要学习爬虫 最近刷抖音看到一个话题是"为什么要找程序员老公?",其中一条理由是:写个python网络投票爬虫,稳稳让自家孩子成为幼儿园最美宝宝.当然这算是爬虫的其中一个 ...

  8. Python进阶之Scrapy-redis分布式爬虫抓取当当图书

    Python进阶之Scrapy-redis分布式爬虫抓取当当图书 1. 准备工作 1.1 安装scrapy-redis 1.2 在windows安装redis程序 1.3 打开redis服务 2. 需 ...

  9. 【视频教程免费领取】聚焦Python分布式爬虫必学框架Scrapy 打造搜索引擎

    领取方式 关注公众号,发送Python0407获取下载链接. 扫码关注公众号,公众号回复 Python0407 获取下载地址 目录结构 目录:/读书ReadBook [57.6G] ┣━━48G全套J ...

最新文章

  1. C++标准库中各种排序归纳
  2. 剑指offer:剪绳子
  3. 一文详解计算机视觉的广泛应用:网络压缩、视觉问答、可视化、风格迁移等
  4. java中jar打包的方法
  5. Jquery背景图片的预加载
  6. 百度富文本编辑器UEditor安装配置全过程
  7. alphac测试和bata测试区别_电缆识别仪与电缆故障测试仪的区别
  8. 转:GridView 模板列中的数据绑定
  9. vim 配置_模块化你的vim配置文件
  10. 算法进阶之Leetcode刷题记录
  11. php 批量删除挂马文件夹,PHP批量挂马脚本
  12. java treetable_00035-layui+java 树形表格treeTable(异步请求)
  13. 2018-2019-1 20165318 20165322 20165326 实验二 固件程序设计
  14. orc识别 语音识别 云真机 内网穿透快速调研
  15. c语言中合法整型常量负号,C语言中整型常量的表示方法
  16. 计算机上安装了更新ie版本,电脑XP系统安装不了ie提示“安装了更新的Internet Explorer版本”的解决方法...
  17. Python爬虫学习笔记-第二课(网络请求模块上)
  18. QTabWidget的样式
  19. 致我们终将逝去的大学生活
  20. 深圳信息通信研究院与深圳市广和通无线股份有限公司签署战略合作协议

热门文章

  1. SOLIDWORKS自定义材质库
  2. 金士顿U盘在我的电脑中无法显示的方法
  3. 信息、消息与信号及通信系统的组成
  4. 【sql随笔】sql题目:查询每班成绩前三名
  5. SrpingBoot+Vue实现学生信息管理
  6. 测试颜色度的软件是什么情况,颜色的秘密:为何需要色彩分析仪测量颜色
  7. html5独立钻石棋,独立钻石棋初级入门玩法讲解
  8. 新手对集成开发环境的理解
  9. listbox java_如何将所选项从一个listBox添加到另一个listBox
  10. 童星养成系统的文推荐_几部养成系列的现言宠文推荐啦,都是不错的大叔文,十分治愈哦~...