当前位置: 首页 > news >正文

gcrawler:一个基于gevent的简单爬虫框架

引子

以前用scrapy写过一些简单的爬虫程序。但是我的需求实在太简单了,用scrapy有点大材小用,而且过于强大的缺点就是用起来太复杂,加上我也不太喜欢twisted——用各种回调实现的异步框架用起来还是不太自然。

前一阵接触了一下gevent (不知道为什么这样一个纯技术网站会在墙外),且不说据说它性能很好,关键是用patch的方式隐含提供异步支持的实现用起来真是太爽了。于是自己写了一个简单的爬虫框架,主要思路是模仿scrapy的。

关于Scrapy

首先来看Scrapy的架构(源于scrapy文档 ):

用户编写的爬虫主要是需要实现Spider和Item Pipeline两部分,Scheduler和Downloader部分是由Scrapy提供,并且整个程序由Scrapy Engine所驱动。

工 作流程是:程序启动后Scrapy Engine从Scheduler取得网址,然后通过Downloader去下载页面交给Spider处理,Spider分析页面后根据情况决定是继续抓 下一级锭接的页面(返回一个Scrapy Request)还是返回数据(返回一个Item List),Spider返回的数据(Item List)将被汇总交给Item Pipeline处理,比如存成文件或存入数据库什么的。

之所以要重复一下Scrapy的工作流程,是因为gcrawler也是模仿这个流程来做的。

Scrapy的局限及gcrawler的特点

不 过在说gcrawler之前,还是先谈一下我的需求中用Scrapy实现不太方便的地方。最主要的是我的应用中虽然也是用HTTP协议去抓取内容,但是有 很多额外的要求,比如有时需要POST,有时需要BasicAuth,有时还需要处理一些特定的HTTP Header字段,这些虽然有Download Middleware也可以实现,但很不自然——一个简单的HTTP请求操作被分割得支离破碎,对于代码的日后维护会造成很大的麻烦。

比 如要用feedparser读RSS,因为feedparser是下载解析一体的实现,这时Scrapy就不太方便,虽然也可以拆开实现:用Scrapy 下载再在Spider里用feedparser,但显然这种麻烦不是必要的。再比如说需要用OAuth进行RESTAPI调用的话,用Scrapy的 Download Middleware我还真不知道要怎么做才好。

因此,gcrawler与scrapy最大的不同就是 Downloader的功能也将由用户实现(放在类似Scrapy的Spider中),用户可以自由控制下载操作的种种细节。乍一看似乎这样会增加用户的 工作量,但实际上是简化了开发。比如前面说的feedparser或是OAuth调用,都可以直接实现,不用考虑分拆到不同的地方去做。那么由用户来实现 下载工作会不会影响性能呢?Scrapy的一大优势就在于利用Twisted这个异步网络框架提供了网络访问的高性能。答案是不会,因为gevent提供 了更为简单和高效的实现方式。二者的差异在于:

因为Scrapy是基于Twisted,而它是一个基于回调的异步框架,这就意味着用它来写 Downloader固然可以得到高性能(托异步的福),但同时带来额外的复杂性(拜回调所赐)。所以Scrapy内部实现了这个高性能的 Downloader,在提代高性能的同时屏蔽了这种复杂性,但代价就是前面所说的,用户自定义操作需要通过Middleware实现,结果还是增加了一 些复杂性,并且使程序结构变得不太清晰。

而如前面所说,gevent通过patch的方式提供了隐含的异步支持,这样用户编写Downloader就成了简单的貌似“同步阻塞操作”,于是结构清晰与高性能得以兼得。个人根据gevent与twisted的性能比较 猜测,能有与scrapy相当甚至可能更好的性能(纯猜测,待求证)。这是gevent相对twisted的一个巨大的优点。

gcrawler的使用

gcrawler 的使用很简单:用户需要实现一个spider类,提供三个成员函数:scheduler, worker, pipeline。其中scheduler是一个生成器,用yield依次返回请求项(比如URL),worker相当于Scrapy里的 Downloader+Spider,用于下载页面内容并解析,pipeline与Scrapy的item pipeline类似,都是用于存储最终结果用。

scheduler和pipeline没什么好说的,重点在worker里。因为 gevent有隐含异步支持,所以这里不再需要像Scrapy里那样下载一个页面解析完后需要把请求重新发回Scheduler进行调度,而是可以直接 继续下载下一级链接的页面继续解析,整个流程会清晰很多,但又不会降低性能。当然要实现像Scrapy那样的方式也可以,只要在worker里把下一级页 面链接再传递给scheduler即可。如果worker没有什么结果需要保存(比如上述把链接交给scheduler作下一步处理的情况),只需要简单 返回一个None即可,pipeline会自动略过的。

一个简单的下载页面的例子如下:

from gcrawler import GCrawler import urllib2 import traceback urls=['http://www.163.com', 'http://www.qq.com', 'http://www.sina.com.cn', 'http://www.sohu.com', 'http://www.yahoo.com', 'http://www.baidu.com', 'http://www.google.com', 'http://www.microsoft.com'] class DummySpider: def scheduler(self): for u in urls: yield u def worker(self, item): try: f = urllib2.urlopen(item) data = f.read() f.close() return (item, len(data)) except: traceback.print_exc() return None def pipeline(self, results): for r in results: print "Downloaded : %s(%s)" % r spider = DummySpider() crawler = GCrawler(spider, workers_count = 3) crawler.start()

当然,为了方便实现类似Scrapy的应用,gcrawler提供了一个默认的Downloader,所以上面的例子可以简化如下,只需要提供urls,并实现pipeline保存结果即可:
from gcrawler import GCrawler, Downloader
import urllib2
import traceback

urls=['http://www.163.com', 'http://www.qq.com', 'http://www.sina.com.cn', 'http://www.sohu.com', 'http://www.yahoo.com', 'http://www.baidu.com', 'http://www.google.com', 'http://www.microsoft.com']

class DummyDownloader(Downloader):
    def pipeline(self, results):
        for r in results:
            r['datasize'] = len(r['data'])
            print "Data fetched : %(url)s(%(datasize)s)" % r

spider = DummyDownloader(urls)
crawler = GCrawler(spider, workers_count = 3)
crawler.start()
另外还提供了一个retryOnURLError的decorator,用于自动重试,用法参见源码。

gcrawler的源码

代码在这里下载:1.5KB (好 吧,我的个人技术网站在春节前也可耻滴被墙外了,大家自己想办法去下载吧不要问我。要是连这都不会,还是不要在中国搞技术了,因为太多的先进技术被墙 外)。License还没想好用什么,先标一个copyright,反正在中国标什么License结果都一样的。等稳定一段时间发google code上去再确定用什么License吧。

附录:gevent的安装

在使用这个框架之前首先需要安装gevent,而 gevent又依赖libevent和greenlet。本来这事不复杂,gevent的文档里都有说,但可耻滴如前面所说,它的官网在中国大陆是“被不 存在的”,所以我还是在这里说一下具体安装方法,并向有关部门坚决竖中指,你们是中国技术创新进步的最大阻力!

Ubuntu 10.04:

apt-get install python-dev libevent-1.4-2 libevent-dev
easy_install greenlet
easy_install gevent

Debian 5:

#先在sources.list里增加一个sid源,比如:
#deb http://mirrors.163.com/debian/ sid main
apt-get install python-gevent
#如果还缺什么则参考ubuntu

FreeBSD 8:

cd /usr/ports/devel/py-gevent
make install clean

Mac OS X 10.6.x:

#当然需要安装XCode和HomeBrew(或其它方式安装libevent)
brew install libevent
easy_install greenlet
easy_install gevent

Windows……这个自己Google去吧,友情建议还是不要在windows下浪费生命为好。

相关文章:

  • Struts2+MySQL登录注册
  • Sql 连接串
  • java类与对象,用程序解释
  • 代码生成器修改备注
  • 常用的字符串方法 String ;
  • 在SQL Server 2008上实现资源的负载均衡
  • 【转】angular基本概念的认识与实战
  • SQL Server中未公布的扩展存储过程
  • 利用jQuery中live为动态生成Dom添加datepicker效果
  • ABAP clear,refresh,free清空内表的区别
  • 作为软件开发人员应该知道的最基本的东西
  • 让IE6、IE7、IE8、IE9、IE10、IE11支持Bootstrap的解决方法
  • 虚拟服务器负载均衡实现方法
  • 架构重构改善既有代码的设计
  • 详解SQL Server中创建数据仓库已分区表
  • (三)从jvm层面了解线程的启动和停止
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • CSS 专业技巧
  • es6要点
  • Git同步原始仓库到Fork仓库中
  • JavaScript设计模式之工厂模式
  • Java多线程(4):使用线程池执行定时任务
  • Mybatis初体验
  • React 快速上手 - 07 前端路由 react-router
  • 经典排序算法及其 Java 实现
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 通信类
  • 我有几个粽子,和一个故事
  • 摩拜创始人胡玮炜也彻底离开了,共享单车行业还有未来吗? ...
  • # 数论-逆元
  • $.ajax()
  • (7)STL算法之交换赋值
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (windows2012共享文件夹和防火墙设置
  • (编译到47%失败)to be deleted
  • (剑指Offer)面试题34:丑数
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (一)eclipse Dynamic web project 工程目录以及文件路径问题
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • (转)拼包函数及网络封包的异常处理(含代码)
  • .dat文件写入byte类型数组_用Python从Abaqus导出txt、dat数据
  • .halo勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法
  • .net6Api后台+uniapp导出Excel
  • .skip() 和 .only() 的使用
  • @CacheInvalidate(name = “xxx“, key = “#results.![a+b]“,multi = true)是什么意思
  • @data注解_SpringBoot 使用WebSocket打造在线聊天室(基于注解)
  • [ C++ ] STL---stack与queue
  • [android学习笔记]学习jni编程
  • [ARM]ldr 和 adr 伪指令的区别
  • [bzoj4010][HNOI2015]菜肴制作_贪心_拓扑排序
  • [C/C++]数据结构 循环队列