Python并发爬虫常用实现方法解析

在进行单个爬虫抓取的时候,我们不可能按照一次抓取一个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 requests
  import gevent
  import time

  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"
  urls = URLS
  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'基于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()

  @coroutine
  def 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

  @coroutine
  def 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的轻量级的协程的。但具体结果怎样,我没测试过。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Python抓包并解析json爬虫的完整实例代码

    Python抓包并解析json爬虫 在使用Python爬虫的时候,通过抓包url,打开url可能会遇见以下类似网址,打开后会出现类似这样的界面,无法继续进行爬虫: 例如: 需要爬取网页中第二页的数据时,点击F12➡网络(Network)➡XHR,最好点击清除键,如下图: 通过点击"第二页",会出现一个POST请求(有时会是GET请求),点击POST请求的url,(这里网址以POST请求为例), 如图: 然后复制参数代码 代码展示: import requests import json

  • Python爬虫破解登陆哔哩哔哩的方法

    写在前面 作为一名找不到工作的爬虫菜鸡人士来说,登陆这一块肯定是个比较大的难题.  从今天开始准备一点点对大型网站进行逐个登陆破解.加深自己爬虫水平. 环境搭建 Python 3.7.7环境,Mac电脑测试 Python内置库 第三方库:rsa.urllib.requests PC端登陆 全部代码: '''PC登录哔哩哔哩''' class Bilibili_For_PC(): def __init__(self, **kwargs): for key, value in kwargs.item

  • 如何基于Python爬虫爬取美团酒店信息

    一.分析网页 网站的页面是 JavaScript 渲染而成的,我们所看到的内容都是网页加载后又执行了JavaScript代码之后才呈现出来的,因此这些数据并不存在于原始 HTML 代码中,而 requests 仅仅抓取的是原始 HTML 代码.抓取这种类型网站的页面数据,解决方案如下: 分析 Ajax,很多数据可能是经过 Ajax 请求时候获取的,所以可以分析其接口. 在XHR里可以找到,Request URL有几个关键参数,uuid和cityId是城市标识,offset偏移量可以控制翻页,分析

  • Python爬虫代理池搭建的方法步骤

    一.为什么要搭建爬虫代理池 在众多的网站防爬措施中,有一种是根据ip的访问频率进行限制,即在某一时间段内,当某个ip的访问次数达到一定的阀值时,该ip就会被拉黑.在一段时间内禁止访问. 应对的方法有两种: 1. 降低爬虫的爬取频率,避免IP被限制访问,缺点显而易见:会大大降低爬取的效率. 2. 搭建一个IP代理池,使用不同的IP轮流进行爬取. 二.搭建思路 1.从代理网站(如:西刺代理.快代理.云代理.无忧代理)爬取代理IP: 2.验证代理IP的可用性(使用代理IP去请求指定URL,根据响应验证

  • Selenium结合BeautifulSoup4编写简单的python爬虫

    在学会了抓包,接口请求(如requests库)和Selenium的一些操作方法后,基本上就可以编写爬虫,爬取绝大多数网站的内容. 在爬虫领域,Selenium永远是最后一道防线.从本质上来说,访问网页实际上就是一个接口请求.请求url后,返回的是网页的源代码. 我们只需要解析html或者通过正则匹配提取出我们需要的数据即可. 有些网站我们可以使用requests.get(url),得到的响应文本中获取到所有的数据.而有些网页数据是通过JS动态加载到页面中的.使用requests获取不到或者只能获

  • python爬虫中PhantomJS加载页面的实例方法

    PhantomJS作为常用获取页面的工具之一,我们已经讲过页面测试.代码评估和捕获屏幕这几种使用的方式.当然最厉害的还是网页方面的捕捉,这里就不再讲述了.今天我们要讲的是它加载页面的新方法,这个可能很多人不知道.其实经常会用到,感兴趣的小伙伴一起进入今天的学习之中吧~ 可以利用 phantom 来实现页面的加载,下面的例子实现了页面的加载并将页面保存为一张图片. var page = require('webpage').create();page.open('http://cuiqingcai

  • python 爬虫如何实现百度翻译

    环境 python版本号 系统 游览器 python 3.7.2 win7 google chrome 关于本文 本文将会通过爬虫的方式实现简单的百度翻译.本文中的代码只供学习,不允许作为于商务作用.商务作用请前往api.fanyi.baidu.com购买付费的api.若有侵犯,立即删文! 实现思路 在网站文件中找到隐藏的免费api.传入api所需要的参数并对其发出请求.在返回的json结果里找到相应的翻译结果. 百度翻译的反爬机制 由js算法生成的sign cookie检测 token暗号 在

  • python使用selenium爬虫知乎的方法示例

    说起爬虫一般想到的情况是,使用 python 中都通过 requests 库获取网页内容,然后通过 beautifulSoup 进行筛选文档中的标签和内容.但是这样有个问题就是,容易被反扒机制所拦住. 反扒机制有很多种,例如知乎:刚开始只加载几个问题,当你往下滚动时才会继续往下面加载,而且在往下滚动一段距离时就会出来一个登陆的弹框. 这样的机制对于通过获取服务器返回内容的爬虫方式进行了限制,我们只能获得前几个回答,而没办法或许后面的回答. 所以需要使用 selenium 模拟真实浏览器进行操作.

  • 10个python爬虫入门实例(小结)

    昨天带伙伴萌学习python爬虫,准备了几个简单的入门实例 涉及主要知识点: web是如何交互的 requests库的get.post函数的应用 response对象的相关函数,属性 python文件的打开,保存 代码中给出了注释,并且可以直接运行哦 如何安装requests库(安装好python的朋友可以直接参考,没有的,建议先装一哈python环境) windows用户,Linux用户几乎一样: 打开cmd输入以下命令即可,如果python的环境在C盘的目录,会提示权限不够,只需以管理员方式

  • Python并发爬虫常用实现方法解析

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

  • python并发编程之线程实例解析

    常用用法 t.is_alive() Python中线程会在一个单独的系统级别线程中执行(比如一个POSIX线程或者一个Windows线程) 这些线程将由操作系统来全权管理.线程一旦启动,将独立执行直到目标函数返回.可以通过查询 一个线程对象的状态,看它是否还在执行t.is_alive() t.join() 可以把一个线程加入到当前线程,并等待它终止 Python解释器在所有线程都终止后才继续执行代码剩余的部分 daemon 对于需要长时间运行的线程或者需要一直运行的后台任务,可以用后台线程(也称

  • 基于Java并发容器ConcurrentHashMap#put方法解析

    jdk1.7.0_79 HashMap可以说是每个Java程序员用的最多的数据结构之一了,无处不见它的身影.关于HashMap,通常也能说出它不是线程安全的.这篇文章要提到的是在多线程并发环境下的HashMap--ConcurrentHashMap,显然它必然是线程安全的,同样我们不可避免的要讨论散列表,以及它是如何实现线程安全的,它的效率又是怎样的,因为对于映射容器还有一个Hashtable也是线程安全的但它似乎只出现在笔试.面试题里,在现实编码中它已经基本被遗弃. 关于HashMap的线程不

  • Python列表切片常用操作实例解析

    这篇文章主要介绍了Python列表切片常用操作实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 最近在爬一个网站的文档的时候,老师要求把一段文字切割开来,根据中间的文本分成两段 故学习了一段时间的切片操作,现把学习成果po上来与大家分享 1.何为切片? 列表的切片就是处理列表中的部分元素,是把整个列表切开的方法. 切片可以说是整个列表中的重点内容,相信你在以后的Python项目中会经常使用到. 它的语法是: 2.简单介绍切片的几个常见操作

  • Python文本处理简单易懂方法解析

    这篇文章主要介绍了Python文本处理简单易懂方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 自从认识了python这门语言,所有的事情好像变得容易了,作为小白,逗汁儿今天就为大家总结一下python的文本处理的一些小方法. 话不多说,代码撸起来. python大小写字符互换 在进行大小写互换时,常用到的方法有4种,upper().lower().capitalize() 和title(). str = "www.dataCASTLE.

  • python 并发下载器实现方法示例

    本文实例讲述了python 并发下载器实现方法.分享给大家供大家参考,具体如下: 并发下载器 并发下载原理 from gevent import monkey import gevent import urllib.request # 有耗时操作时需要 monkey.patch_all() def my_downLoad(url): print('GET: %s' % url) resp = urllib.request.urlopen(url) data = resp.read() print

  • Python调用C/C++的方法解析

    Python是解释性语言, 底层就是用c实现的, 所以用python调用C是很容易的, 下面就总结一下各种调用的方法, 给出例子, 所有例子都在ubuntu9.10, python2.6下试过. 1. Python 调用 C (base) 想在python中调用c函数, 如这儿的fact #include <Python.h> int fact(int n) { if (n <= 1) return 1; else return n * fact(n - 1); } PyObject*

  • python实现语音常用度量方法的代码详解

    语音信号处理一般都要进行主观评价实验和客观评价实验. 主观评价:邀请测听者对语音进行测听,给出主观意见得分 客观评价:根据算法来衡量语音质量 主观投票受多种因素影响,如个体受试者的偏好和实验的语境(其他条件).一个好的客观质量度量应该与许多不同的主观实验有很高的相关性 信噪比(SNR) 有用信号功率与噪声功率的比(此处功率为平均功率),也等于幅度比的平方 其中:$P_{signal}$为信号功率(平均功率或者实际功率):$P_{noise}$为噪声功率:$A_{signal}$为信号幅度:$A_

  • python爬虫 Pyppeteer使用方法解析

    引言 Selenium 在被使用的时候有个麻烦事,就是环境的相关配置,得安装好相关浏览器,比如 Chrome.Firefox 等等,然后还要到官方网站去下载对应的驱动,最重要的还需要安装对应的 Python Selenium 库,确实是不是很方便,另外如果要做大规模部署的话,环境配置的一些问题也是个头疼的事情.那么本节就介绍另一个类似的替代品,叫做 Pyppeteer. Pyppeteer简介 注意,本节讲解的模块叫做 Pyppeteer,不是 Puppeteer.Puppeteer 是 Goo

  • python并发爬虫实用工具tomorrow实用解析

    tomorrow是我最近在用的一个爬虫利器,该模块属于第三方的一个模块,使用起来非常的方便,只需要用其中的threads方法作为装饰器去修饰一个普通的函数,既可以达到并发的效果,本篇将用实例来展示tomorrow的强大之处.后面将对tomorrow的实现原理做进一步的分析. 1.安装第三方包 pip install requests_html #网络请求包 pip install fake-useragent #获取useragent包 pip install tomorrow 2.普通下载方式

随机推荐