scrapy 源代码阅读笔记(0)-- 背景


初探

scrapy可以服务与中小型爬虫项目,异步下载性能很出色,(50M电信,scrapy单进程,半小时,最高纪录12w页)。不过更令人惊讶的是scrapy的代码风格以及官方文档和社区,笔者受益良多。仅以此文记录阅读scrapy代码的经验与想法。

基本对象

scrapy.http.Request

  1. 基本的Request对象,描述url, proxy, User-Agent, method之类的本地发送http请求之前基本信息,
  2. 注意这只是scrapy封装的描述对象,并不显含下载功能

scrapy.http.Response

  1. 基本的Response对象,与Request对应,描述对方网站服务器的回复内容,包含url, text之类的
  2. Reponse还实现了xpath方法(似乎是基于elementtree),便于实时解析网页。这部分性能损耗可基本忽略(python下elementtree的实例化耗时,thinkpad i5-6200u,单进程,大概130/s,包含读文件)

scrapy.Spider
如果了解基本的网络爬虫,可以看将Spider作网络爬虫中的“虫”,主要负责:

发送Request
解读Response(发现新的Request,抽取目标信息)

二者循环,直到没有 新的 Request为止。
到此为止,咬文嚼字就应该发现 scrapy中的Spider只是一个独立的模块,但无法独立完成“爬虫”的工作。完成爬虫至少还需要以下模块

  • 高效下载 --> 非阻塞 or 多线程
  • 提取新的url --> 去重
  • 避免并发访问统一网站 --> 合理的调度
  • 存储目标信息 --> 数据管道(指向文件系统/数据库)
  • 功能模块之间的衔接 --> 核心引擎

数据流向


这张图来自scrapy 1.2,图虽然丑,但是标注了与代码逻辑相对应数据流的顺序,可以在不阅读核心代码的情况下理清逻辑

推荐

值得一提的是,如果需要分布式的支持,只需要对各个模块分别实现分布式,实际上就是对模块单独开一个进程,或者说挂成服务对外开放。
分布式的支持,对于scrapy来说可以通过插件的形式引入。

  1. Frontera 官方维护的分布式框架api,提供更强大的引擎和更智能的队列控制,理论上可以接入任何爬虫系统;消息队列(Kafka or ZeroMQ),数据后端(Hbase)。
  2. Scrapy Cluster 与frontera很类似(其文档中有提到), 封装程度更高,提供了后端的monitor, 其推荐的log factory可以方便和ELK结合,实现实时的log分析(但目前并没提供相关api,需要自己定制)
  3. Scrapy Redis 轻量级插件,将scrapy默认的memory queue转到redis中, 实现了消息队列的分布式
  4. Scrapyd scrapy代码部署工具,提供http api操控爬虫的进度,支持多个scrapy project。官方文档提到支持python2.6+,osx下实测其变种scrapyrt(只支持单个project)默认对于python3.5支持不够好(可能是twisted的问题),2.7.12可以正常跑

阅读文档与代码

对于初级开发者(没写过爬虫框架)在scrapy开发中遇到的问题,坚持一切以官方文档为准。刚开始你能遇到想到甚至梦到的问题,初步估计70%都能通过官方文档解决,20%通过插件可以解决,5%或许通过阅读源代码你会有更深的理解,另外5%估计scrapy就很难满足需求了。比如想通过scrapy复制一个google, 可能首先需要的是钞票而不是技术。但比如说你很讨厌scrapy 默认terminal command运行,在scrapy文档中advanced topic描述了如何在脚本中讲spider封装到crawler中(有暗坑),以及crawler的启动方式,另外也可以借助scrapyd/scrapyrt部署成service,通过http api的方式提交爬虫任务。

搜索话题

Scrapy XXX module complains XXX
How can I XXX with scrapy in order to XXX

在google,github,stackoverflow上可以搜到很多类似的话题,虽然这可能并不是最佳的答案,但一定是最快的。根据笔者印象,很多scrapy初探者甚至开发者都没有耐心在精读官方文档或者阅读scrapy源代码之后再开始开发工作(看看提的问题是什么就能猜出来),唯一能做的就是相信这个问题别人多半遇到过,即使还没有被解决,也一定有线索。通常笔者就是这样解决的,最开始遇到从脚本运行爬虫(CrawlerProcess重复运行)twisted报错到最近用splash渲染js(scrapy_splash插件)的同时实现增量爬取 (deltafetch插件) 不兼容都是这样处理。

  • 很多时候我们只是缺少线索 -- 弄明白解决问题需要参数级的处理,还是代码级的处理,然后再思考要不要造轮子,或者借别人的轮子

Powerful

关于 scrapy 究竟如何 powerful,同样的 python 环境下,scrapy 常与pyspider 一起作出对比

  1. 简书的用户 scrapy 和 pyspider 介绍
  2. 知乎上网友发问 pyspider 和 scrapy 比较起来有什么优缺点吗?
  3. scrapy 王婆 Quora 回答 How does pyspider compare to scrapy?
  4. pyspider 王婆 stackoverflow 怒顶 Can scrapy be replaced by pyspider

以上几篇是google搜索中英文“scrapy和pyspider比较” 排名较高的链接(赞叹google确实强大,两篇来自网友,两篇来自开发者)。先别去在意评论者的情感色彩,挖掘下我们能从中了解到什么干货。

  1. 初探 两种框架的架构(数据流)和主要组成(功能模块)
  2. 开发效率,网友的体会,萝卜青菜,各有所爱。
  3. 功能模块比较,主要从功能以及实现方式介绍(广告)。
  4. pyspider开发者逻辑

虽然我目前用的是scrapy,但以上文章令我映象最深的来自于4,pyspider的开发者binux的回答

spider should never stop till WWW dead

仔细想想,web crawler确实应该服务化,爬虫的巨头谷歌、百度不都是这样做的么,你能想象几十万台的机器每天一台一台去开启和关闭? binux将自己的逻辑实现在代码中,开发了webui, 一开始就实现了在线编辑、调试、控制爬虫,令人不得不爱。不过,我对pyspider的尝试也就到此为止了,原因很简单,正如scrapy开发者在Quora里回答的那样

  • Scrapy is a mature framework
  • Scrapy has an active community
  • Documentation is one area where Scrapy really shines.

开发者躲不过 效率优先,pyspider可以很容易的实现基本功能,但更复杂的需求,如果没有完善的文档以及社区支持,单枪匹马大概很快就挂了吧。然而,如果有足够的实力单独写完所有插件,可能也不需要最开始的轮子了。

github参数对比

项目 commits branches releases contributors open closed
scrapy 5896 26 70 219 299 766
pyspider 851 4 11 32 91 385

截止2016/10/23

搜索经验

关于如何搜索scrapy相关话题,一些个人经验

  1. 知乎,quora,或者博客的文章可以解决背景问题,或者说提出一个技术问题,或者指向文档/轮子的具体位置
  2. stackoverflow至少可以找到解决问题的线索,或者轮子的线索,不过通常会引入更多的问题,比如twisted, celery, message bus之类在scrapy文档中没有详细描述的。另外,很多技术宅喜欢针对问题的贴自己的代码解决方案(逻辑)
  3. 在其他网站找到的解决方案,要去文档核实(小问题一般都能对应),代码可能已经更新了,注意scrapy/python版本

代码风格提示

终于到了正题了,scrapy的的代码模块化程度很高(对于本渣来说是这样),刚开始阅读的时候,经常读不懂;后来渐渐发现是由于代码风格的差异。以功能模块实例化为例,经常我们设计类

class A:

    def __init__(self,x,y,z):
        pass
    
    def method1(self):
        pass

a = A(x,y,z) 做实例化,然后调用方法。然而,scrapy的做法

class A:

    def __init__(self,x,y,z):
        pass
    
    @classmethod
    def from_crawler(cls, cralwer):
        # do something with crawler
        x, y, z = foo(crawler) 
        return cls(x,y,z)

    def method1(self):
        pass

scrapy喜欢将参数配置在settings.py内,然后将settings参数倒入crawler中,然后通过引擎的初始化,spider与crawler绑定(姑且认为是“爬虫”中的“爬”的开关, 可以启动多个“虫”),然后通过插件中的from_crawler方法实例化插件,并导入参数。(完整代码参考文章末尾scrapy.extensions.spiderstate)

  1. 统一定义@classmethod from_crawler,作为实例化的接口(返回一个注入了settings的实例)
  2. 可以调用异步方法,以实现针对各个spider的处理的方法
    spider_opened, spider_closed(optional)
  3. 不用怀疑,几乎每个插件都包含1,2的特征;实际上,如果不包含 1 所推荐的实例化方法,这个插件不能通过官方推荐的方案集成到scrapy爬虫中去。

笔者猜测,插件的实例化方案是 MWs = SpiderState.from_crawler(crawler), 这样在生成实例的同时,注入了在spider(已经喝crawler绑定)开始/结束时的处理方案。这对于异常退出保存数据很重要。

To be continued

class SpiderState(object):
   """Store and load spider state during a scraping job"""

   def __init__(self, jobdir=None):
       self.jobdir = jobdir

   @classmethod
   def from_crawler(cls, crawler):
       jobdir = job_dir(crawler.settings)
       if not jobdir:
           raise NotConfigured

       obj = cls(jobdir)
       crawler.signals.connect(obj.spider_closed, signal=signals.spider_closed)
       crawler.signals.connect(obj.spider_opened, signal=signals.spider_opened)
       return obj

   def spider_closed(self, spider):
       if self.jobdir:
           with open(self.statefn, 'wb') as f:
               pickle.dump(spider.state, f, protocol=2)

   def spider_opened(self, spider):
       if self.jobdir and os.path.exists(self.statefn):
           with open(self.statefn, 'rb') as f:
               spider.state = pickle.load(f)
       else:
           spider.state = {}

   @property
   def statefn(self):
       return os.path.join(self.jobdir, 'spider.state')
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容