python实现并发爬虫

2024-09-07 17:58
文章标签 python 实现 并发 爬虫

本文主要是介绍python实现并发爬虫,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

阅读目录

一.顺序抓取
二.多线程抓取
三.gevent并发抓取
四.基于tornado的coroutine并发抓取

在进行单个爬虫抓取的时候,我们不可能按照一次抓取一个url的方式进行网页抓取,这样效率低,也浪费了cpu的资源。目前python上面进行并发抓取的实现方式主要有以下几种:进程,线程,协程。进程不在的讨论范围之内,一般来说,进程是用来开启多个spider,比如我们开启了4进程,同时派发4个spider进行网络抓取,每个spider同时抓取4个url。

所以,我们今天讨论的是,在单个爬虫的情况下,尽可能的在同一个时间并发抓取,并且抓取的效率要高。

一.顺序抓取

顺序抓取是最最常见的抓取方式,一般初学爬虫的朋友就是利用这种方式,下面是一个测试代码,顺序抓取8个url,我们可以来测试一下抓取完成需要多少时间:

HEADERS = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9',                 'Accept-Language': 'zh-CN,zh;q=0.8',                                                       'Accept-Encoding': 'gzip, deflate',}                                                       
URLS = ['http://www.cnblogs.com/moodlxs/p/3248890.html',                                      'https://www.zhihu.com/topic/19804387/newest',                                        'http://blog.csdn.net/yueguanghaidao/article/details/24281751',                       'https://my.oschina.net/visualgui823/blog/36987',                                     'http://blog.chinaunix.net/uid-9162199-id-4738168.html',                              'http://www.tuicool.com/articles/u67Bz26',                                            'http://rfyiamcool.blog.51cto.com/1030776/1538367/',                                  'http://itindex.net/detail/26512-flask-tornado-gevent']                               #url为随机获取的一批url                                                                               def func():                                                                                   """                                                                                       顺序抓取                                                                                      """                                                                                       import requests                                                                           import time                                                                               urls = URLS                                                                               headers = HEADERS                                                                         headers['user-agent'] = "Mozilla/5.0+(Windows+NT+6.2;+WOW64)+AppleWebKit/537" \           ".36+(KHTML,+like+Gecko)+Chrome/45.0.2454.101+Safari/537.36"      print(u'顺序抓取')                                                                            starttime= time.time()                                                                    for url in urls:                                                                          try:                                                                                  r = requests.get(url, allow_redirects=False, timeout=2.0, headers=headers)        except:                                                                               pass                                                                              else:                                                                                 print(r.status_code, r.url)                                                       endtime=time.time()                                                                       print(endtime-starttime)                                                                  func()

 

我们直接采用内建的time.time()来计时,较为粗略,但可以反映大概的情况。下面是顺序抓取的结果计时:

可以从图片中看到,显示的顺序与urls的顺序是一模一样的,总共耗时为7.763269901275635秒,一共8个url,平均抓取一个大概需要0.97秒。总体来看,还可以接受。

 

二.多线程抓取

线程是python内的一种较为不错的并发方式,我们也给出相应的代码,并且为每个url创建了一个线程,一共8线程并发抓取,下面的代码:

下面是我们运行8线程的测试代码:

 

HEADERS = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9',                              'Accept-Language': 'zh-CN,zh;q=0.8',                                                                    'Accept-Encoding': 'gzip, deflate',}                                                                    
URLS = ['http://www.cnblogs.com/moodlxs/p/3248890.html',                                                   'https://www.zhihu.com/topic/19804387/newest',                                                     'http://blog.csdn.net/yueguanghaidao/article/details/24281751',                                    'https://my.oschina.net/visualgui823/blog/36987',                                                  'http://blog.chinaunix.net/uid-9162199-id-4738168.html',                                           'http://www.tuicool.com/articles/u67Bz26',                                                         'http://rfyiamcool.blog.51cto.com/1030776/1538367/',                                               'http://itindex.net/detail/26512-flask-tornado-gevent']                                            def thread():                                                                                              from threading import Thread                                                                           import requests                                                                                        import time                                                                                            urls = URLS                                                                                            headers = HEADERS                                                                                      headers['user-agent'] = "Mozilla/5.0+(Windows+NT+6.2;+WOW64)+AppleWebKit/537.36+" \                    "(KHTML,+like+Gecko)+Chrome/45.0.2454.101+Safari/537.36"                       def get(url):                                                                                          try:                                                                                               r = requests.get(url, allow_redirects=False, timeout=2.0, headers=headers)                     except:                                                                                            pass                                                                                           else:                                                                                              print(r.status_code, r.url)                                                                    print(u'多线程抓取')                                                                                        ts = [Thread(target=get, args=(url,)) for url in urls]                                                 starttime= time.time()                                                                                 for t in ts:                                                                                           t.start()                                                                                          for t in ts:                                                                                           t.join()                                                                                           endtime=time.time()                                                                                    print(endtime-starttime)                                                                               
thread()

多线程抓住的时间如下:

可以看到相较于顺序抓取,8线程的抓取效率明显上升了3倍多,全部完成只消耗了2.154秒。可以看到显示的结果已经不是urls的顺序了,说明每个url各自完成的时间都是不一样的。线程就是在一个进程中不断的切换,让每个线程各自运行一会,这对于网络io来说,性能是非常高的。但是线程之间的切换是挺浪费资源的。

 

三.gevent并发抓取

gevent是一种轻量级的协程,可用它来代替线程,而且,他是在一个线程中运行,机器资源的损耗比线程低很多。如果遇到了网络io阻塞,会马上切换到另一个程序中去运行,不断的轮询,来降低抓取的时间 
下面是测试代码:

HEADERS = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9','Accept-Language': 'zh-CN,zh;q=0.8','Accept-Encoding': 'gzip, deflate',}URLS = ['http://www.cnblogs.com/moodlxs/p/3248890.html','https://www.zhihu.com/topic/19804387/newest','http://blog.csdn.net/yueguanghaidao/article/details/24281751','https://my.oschina.net/visualgui823/blog/36987','http://blog.chinaunix.net/uid-9162199-id-4738168.html','http://www.tuicool.com/articles/u67Bz26','http://rfyiamcool.blog.51cto.com/1030776/1538367/','http://itindex.net/detail/26512-flask-tornado-gevent']def main():"""gevent并发抓取"""import requestsimport geventimport timeheaders = HEADERSheaders['user-agent'] = "Mozilla/5.0+(Windows+NT+6.2;+WOW64)+AppleWebKit/537.36+" \"(KHTML,+like+Gecko)+Chrome/45.0.2454.101+Safari/537.36"urls = URLSdef get(url):try:r = requests.get(url, allow_redirects=False, timeout=2.0, headers=headers)except:passelse:print(r.status_code, r.url)print(u'基于gevent的并发抓取')starttime= time.time()g = [gevent.spawn(get, url) for url in urls]gevent.joinall(g)endtime=time.time()print(endtime - starttime)
main()

协程的抓取时间如下:

正常情况下,gevent的并发抓取与多线程的消耗时间差不了多少,但是可能是我网络的原因,或者机器的性能的原因,时间有点长......,请各位小主在自己电脑进行跑一下看运行时间

 

四.基于tornado的coroutine并发抓取

tornado中的coroutine是python中真正意义上的协程,与python3中的asyncio几乎是完全一样的,而且两者之间的future是可以相互转换的,tornado中有与asyncio相兼容的接口。 
下面是利用tornado中的coroutine进行并发抓取的代码:

 

利用coroutine编写并发略显复杂,但这是推荐的写法,如果你使用的是python3,强烈建议你使用coroutine来编写并发抓取。

下面是测试代码:

 

HEADERS = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9','Accept-Language': 'zh-CN,zh;q=0.8','Accept-Encoding': 'gzip, deflate',}URLS = ['http://www.cnblogs.com/moodlxs/p/3248890.html','https://www.zhihu.com/topic/19804387/newest','http://blog.csdn.net/yueguanghaidao/article/details/24281751','https://my.oschina.net/visualgui823/blog/36987','http://blog.chinaunix.net/uid-9162199-id-4738168.html','http://www.tuicool.com/articles/u67Bz26','http://rfyiamcool.blog.51cto.com/1030776/1538367/','http://itindex.net/detail/26512-flask-tornado-gevent']
import time
from tornado.gen import coroutine
from tornado.ioloop import IOLoop
from tornado.httpclient import AsyncHTTPClient, HTTPError
from tornado.httpclient import HTTPRequest#urls与前面相同
class MyClass(object):def __init__(self):#AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")self.http = AsyncHTTPClient()@coroutinedef get(self, url):#tornado会自动在请求首部带上host首部request = HTTPRequest(url=url,method='GET',headers=HEADERS,connect_timeout=2.0,request_timeout=2.0,follow_redirects=False,max_redirects=False,user_agent="Mozilla/5.0+(Windows+NT+6.2;+WOW64)+AppleWebKit/537.36+\(KHTML,+like+Gecko)+Chrome/45.0.2454.101+Safari/537.36",)yield self.http.fetch(request, callback=self.find, raise_error=False)def find(self, response):if response.error:print(response.error)print(response.code, response.effective_url, response.request_time)class Download(object):def __init__(self):self.a = MyClass()self.urls = URLS@coroutinedef d(self):print(u'基于tornado的并发抓取')starttime = time.time()yield [self.a.get(url) for url in self.urls]endtime=time.time()print(endtime-starttime)if __name__ == '__main__':dd = Download()loop = IOLoop.current()loop.run_sync(dd.d)

 

抓取的时间如下:

可以看到总共花费了128087秒,而这所花费的时间恰恰就是最后一个url抓取所需要的时间,tornado中自带了查看每个请求的相应时间。我们可以从图中看到,最后一个url抓取总共花了1.28087秒,相较于其他时间大大的增加,这也是导致我们消耗时间过长的原因。那可以推断出,前面的并发抓取,也在这个url上花费了较多的时间。

总结: 
以上测试其实非常的不严谨,因为我们选取的url的数量太少了,完全不能反映每一种抓取方式的优劣。如果有一万个不同的url同时抓取,那么记下总抓取时间,是可以得出一个较为客观的结果的。 
并且,已经有人测试过,多线程抓取的效率是远不如gevent的。所以,如果你使用的是python2,那么我推荐你使用gevent进行并发抓取;如果你使用的是python3,我推荐你使用tornado的http客户端结合coroutine进行并发抓取。从上面的结果来看,tornado的coroutine是高于gevent的轻量级的协程的。但具体结果怎样,我没测试过。

这篇关于python实现并发爬虫的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145757

相关文章

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P