Categories

Calendar

November 2018
M T W T F S S
« Jun    
 1234
567891011
12131415161718
19202122232425
2627282930  

Scrapy 轻松定制网络爬虫

Tachikoma网络爬虫(Web Crawler, Spider)就是一个在网络上乱爬的机器人。当然它通常并不是一个实体的机器人,因为网络本身也是虚拟的东西,所以这个“机器人”其实也就是一段程序,并且它也不是爬,而是有一定目的的,并且在爬行的时候会搜集一些信息。例如 Google 就有一大堆爬虫会在 Internet 上搜集网页内容以及它们之间的链接等信息;又比如一些别有用心的爬虫会在 Internet 上搜集诸如 foo@bar.com 或者 foo [at] bar [dot] com 之类的东西。除此之外,还有一些定制的爬虫,专门针对某一个网站,例如前一阵子 JavaEye 的 Robbin 就写了几篇专门对付恶意爬虫的 blog (原文链接似乎已经失效了,就不给了),还有诸如小众软件或者 LinuxToy 这样的网站也经常被整个站点 crawl 下来,换个名字挂出来。其实爬虫从基本原理上来讲很简单,只要能访问网络和分析 Web 页面即可,现在大部分语言都有方便的 Http 客户端库可以抓取 Web 页面,而 HTML 的分析最简单的可以直接用正则表达式来做,因此要做一个最简陋的网络爬虫实际上是一件很简单的事情。不过要实现一个高质量的 spider 却是非常难的。

爬虫的两部分,一是下载 Web 页面,有许多问题需要考虑,如何最大程度地利用本地带宽,如何调度针对不同站点的 Web 请求以减轻对方服务器的负担等。一个高性能的 Web Crawler 系统里,DNS 查询也会成为急需优化的瓶颈,另外,还有一些“行规”需要遵循(例如 robots.txt)。而获取了网页之后的分析过程也是非常复杂的,Internet 上的东西千奇百怪,各种错误百出的 HTML 页面都有,要想全部分析清楚几乎是不可能的事;另外,随着 AJAX 的流行,如何获取由 Javascript 动态生成的内容成了一大难题;除此之外,Internet 上还有有各种有意或无意出现的 Spider Trap ,如果盲目的跟踪超链接的话,就会陷入 Trap 中万劫不复了,例如这个网站,据说是之前 Google 宣称 Internet 上的 Unique URL 数目已经达到了 1 trillion 个,因此这个人 is proud to announce the second trillion:D

不过,其实并没有多少人需要做像 Google 那样通用的 Crawler ,通常我们做一个 Crawler 就是为了去爬特定的某个或者某一类网站,所谓知己知彼,百战不殆,我们可以事先对需要爬的网站结构做一些分析,事情就变得容易多了。通过分析,选出有价值的链接进行跟踪,就可以避免很多不必要的链接或者 Spider Trap ,如果网站的结构允许选择一个合适的路径的话,我们可以按照一定顺序把感兴趣的东西爬一遍,这样以来,连 URL 重复的判断也可以省去。

举个例子,假如我们想把 pongba 的 blog mindhacks.cn 里面的 blog 文字爬下来,通过观察,很容易发现我们对其中的两种页面感兴趣:

  1. 文章列表页面,例如首页,或者 URL 是 /page/\d+/ 这样的页面,通过 Firebug 可以看到到每篇文章的链接都是在一个 h1 下的 a 标签里的(需要注意的是,在 Firebug 的 HTML 面板里看到的 HTML 代码和 View Source 所看到的也许会有些出入,如果网页中有 Javascript 动态修改 DOM 树的话,前者是被修改过的版本,并且经过 Firebug 规则化的,例如 attribute 都有引号扩起来等,而后者通常才是你的 spider 爬到的原始内容。如果是使用正则表达式对页面进行分析或者所用的 HTML Parser 和 Firefox 的有些出入的话,需要特别注意),另外,在一个 class 为 wp-pagenavidiv 里有到不同列表页面的链接。
  2. 文章内容页面,每篇 blog 有这样一个页面,例如 /2008/09/11/machine-learning-and-ai-resources/ ,包含了完整的文章内容,这是我们感兴趣的内容。

因此,我们从首页开始,通过 wp-pagenavi 里的链接来得到其他的文章列表页面,特别地,我们定义一个路径:只 follow Next Page 的链接,这样就可以从头到尾按顺序走一遍,免去了需要判断重复抓取的烦恼。另外,文章列表页面的那些到具体文章的链接所对应的页面就是我们真正要保存的数据页面了。

这样以来,其实用脚本语言写一个 ad hoc 的 Crawler 来完成这个任务也并不难,不过今天的主角是 Scrapy ,这是一个用 Python 写的 Crawler Framework ,简单轻巧,并且非常方便,并且官网上说已经在实际生产中在使用了,因此并不是一个玩具级别的东西。不过现在还没有 Release 版本,可以直接使用他们的 Mercurial 仓库里抓取源码进行安装。不过,这个东西也可以不安装直接使用,这样还方便随时更新,文档里说得很详细,我就不重复了。

Scrapy 使用 Twisted 这个异步网络库来处理网络通讯,架构清晰,并且包含了各种中间件接口,可以灵活的完成各种需求。整体架构如下图所示:

scrapy_architecture

绿线是数据流向,首先从初始 URL 开始,Scheduler 会将其交给 Downloader 进行下载,下载之后会交给 Spider 进行分析,Spider 分析出来的结果有两种:一种是需要进一步抓取的链接,例如之前分析的“下一页”的链接,这些东西会被传回 Scheduler ;另一种是需要保存的数据,它们则被送到 Item Pipeline 那里,那是对数据进行后期处理(详细分析、过滤、存储等)的地方。另外,在数据流动的通道里还可以安装各种中间件,进行必要的处理。

看起来好像很复杂,其实用起来很简单,就如同 Rails 一样,首先新建一个工程:

scrapy-admin.py startproject blog_crawl

会创建一个 blog_crawl 目录,里面有个 scrapy-ctl.py 是整个项目的控制脚本,而代码全都放在子目录 blog_crawl 里面。为了能抓取 mindhacks.cn ,我们在 spiders 目录里新建一个mindhacks_spider.py ,定义我们的 Spider 如下:

from scrapy.spider import BaseSpider
 
class MindhacksSpider(BaseSpider):
    domain_name = "mindhacks.cn"
    start_urls = ["http://mindhacks.cn/"]
 
    def parse(self, response):
        return []
 
SPIDER = MindhacksSpider()

我们的 MindhacksSpider 继承自 BaseSpider (通常直接继承自功能更丰富的 scrapy.contrib.spiders.CrawlSpider 要方便一些,不过为了展示数据是如何 parse 的,这里还是使用 BaseSpider 了),变量 domain_namestart_urls 都很容易明白是什么意思,而 parse 方法是我们需要定义的回调函数,默认的 request 得到 response 之后会调用这个回调函数,我们需要在这里对页面进行解析,返回两种结果(需要进一步 crawl 的链接和需要保存的数据),让我感觉有些奇怪的是,它的接口定义里这两种结果竟然是混杂在一个 list 里返回的,不太清楚这里为何这样设计,难道最后不还是要费力把它们分开?总之这里我们先写一个空函数,只返回一个空列表。另外,定义一个“全局”变量 SPIDER ,它会在 Scrapy 导入这个 module 的时候实例化,并自动被 Scrapy 的引擎找到。这样就可以先运行一下 crawler 试试了:

./scrapy-ctl.py crawl mindhacks.cn

会有一堆输出,可以看到抓取了 http://mindhacks.cn ,因为这是初始 URL ,但是由于我们在 parse 函数里没有返回需要进一步抓取的 URL ,因此整个 crawl 过程只抓取了主页便结束了。接下来便是要对页面进行分析,Scrapy 提供了一个很方便的 Shell (需要 IPython )可以让我们做实验,用如下命令启动 Shell :

./scrapy-ctl.py shell http://mindhacks.cn

它会启动 crawler ,把命令行指定的这个页面抓取下来,然后进入 shell ,根据提示,我们有许多现成的变量可以用,其中一个就是 hxs ,它是一个 HtmlXPathSelector ,mindhacks 的 HTML 页面比较规范,可以很方便的直接用 XPath 进行分析。通过 Firebug 可以看到,到每篇 blog 文章的链接都是在 h1 下的,因此在 Shell 中使用这样的 XPath 表达式测试:

In [1]: hxs.x('//h1/a/@href').extract()
Out[1]: 
[u'http://mindhacks.cn/2009/07/06/why-you-should-do-it-yourself/',
 u'http://mindhacks.cn/2009/05/17/seven-years-in-nju/',
 u'http://mindhacks.cn/2009/03/28/effective-learning-and-memorization/',
 u'http://mindhacks.cn/2009/03/15/preconception-explained/',
 u'http://mindhacks.cn/2009/03/09/first-principles-of-programming/',
 u'http://mindhacks.cn/2009/02/15/why-you-should-start-blogging-now/',
 u'http://mindhacks.cn/2009/02/09/writing-is-better-thinking/',
 u'http://mindhacks.cn/2009/02/07/better-explained-conflicts-in-intimate-relationship/',
 u'http://mindhacks.cn/2009/02/07/independence-day/',
 u'http://mindhacks.cn/2009/01/18/escape-from-your-shawshank-part1/']

这正是我们需要的 URL ,另外,还可以找到“下一页”的链接所在,连同其他几个页面的链接一同在一个 div 里,不过“下一页”的链接没有 title 属性,因此 XPath 写作

//div[@class="wp-pagenavi"]/a[not(@title)]

不过如果向后翻一页的话,会发现其实“上一页”也是这样的,因此还需要判断该链接上的文字是那个下一页的箭头 u'\xbb' ,本来也可以写到 XPath 里面去,但是好像这个本身是 unicode escape 字符,由于编码原因理不清楚,直接放到外面判断了,最终 parse 函数如下:

def parse(self, response):
    items = []
    hxs = HtmlXPathSelector(response)
    posts = hxs.x('//h1/a/@href').extract()
    items.extend([self.make_requests_from_url(url).replace(callback=self.parse_post)
                  for url in posts])
 
    page_links = hxs.x('//div[@class="wp-pagenavi"]/a[not(@title)]')
    for link in page_links:
        if link.x('text()').extract()[0] == u'\xbb':
            url = link.x('@href').extract()[0]
            items.append(self.make_requests_from_url(url))
 
    return items

前半部分是解析需要抓取的 blog 正文的链接,后半部分则是给出“下一页”的链接。需要注意的是,这里返回的列表里并不是一个个的字符串格式的 URL 就完了,Scrapy 希望得到的是 Request 对象,这比一个字符串格式的 URL 能携带更多的东西,诸如 Cookie 或者回调函数之类的。可以看到我们在创建 blog 正文的 Request 的时候替换掉了回调函数,因为默认的这个回调函数 parse 是专门用来解析文章列表这样的页面的,而 parse_post 定义如下:

def parse_post(self, response):
    item = BlogCrawlItem()
    item.url = unicode(response.url)
    item.raw = response.body_as_unicode()
    return [item]

很简单,返回一个 BlogCrawlItem ,把抓到的数据放在里面,本来可以在这里做一点解析,例如,通过 XPath 把正文和标题等解析出来,但是我倾向于后面再来做这些事情,例如 Item Pipeline 或者更后面的 Offline 阶段。BlogCrawlItem 是 Scrapy 自动帮我们定义好的一个继承自 ScrapedItem 的空类,在 items.py 中,这里我加了一点东西:

from scrapy.item import ScrapedItem
 
class BlogCrawlItem(ScrapedItem):
    def __init__(self):
        ScrapedItem.__init__(self)
        self.url = ''
 
    def __str__(self):
        return 'BlogCrawlItem(url: %s)' % self.url

定义了 __str__ 函数,只给出 URL ,因为默认的 __str__ 函数会把所有的数据都显示出来,因此会看到 crawl 的时候控制台 log 狂输出东西,那是把抓取到的网页内容输出出来了。-.-bb

这样一来,数据就取到了,最后只剩下存储数据的功能,我们通过添加一个 Pipeline 来实现,由于 Python 在标准库里自带了 Sqlite3 的支持,所以我使用 Sqlite 数据库来存储数据。用如下代码替换 pipelines.py 的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import sqlite3
from os import path
 
from scrapy.core import signals
from scrapy.xlib.pydispatch import dispatcher
 
class SQLiteStorePipeline(object):
    filename = 'data.sqlite'
 
    def __init__(self):
        self.conn = None
        dispatcher.connect(self.initialize, signals.engine_started)
        dispatcher.connect(self.finalize, signals.engine_stopped)
 
    def process_item(self, domain, item):
        self.conn.execute('insert into blog values(?,?,?)', 
                          (item.url, item.raw, unicode(domain)))
        return item
 
    def initialize(self):
        if path.exists(self.filename):
            self.conn = sqlite3.connect(self.filename)
        else:
            self.conn = self.create_table(self.filename)
 
    def finalize(self):
        if self.conn is not None:
            self.conn.commit()
            self.conn.close()
            self.conn = None
 
    def create_table(self, filename):
        conn = sqlite3.connect(filename)
        conn.execute("""create table blog
                     (url text primary key, raw text, domain text)""")
        conn.commit()
        return conn

__init__ 函数中,使用 dispatcher 将两个信号连接到指定的函数上,分别用于初始化和关闭数据库连接(在 close 之前记得 commit ,似乎是不会自动 commit 的,直接 close 的话好像所有的数据都丢失了 dd-.-)。当有数据经过 pipeline 的时候,process_item 函数会被调用,在这里我们直接讲原始数据存储到数据库中,不作任何处理。如果需要的话,可以添加额外的 pipeline ,对数据进行提取、过滤等,这里就不细说了。

最后,在 settings.py 里列出我们的 pipeline :

ITEM_PIPELINES = ['blog_crawl.pipelines.SQLiteStorePipeline']

再跑一下 crawler ,就 OK 啦! :D 最后,总结一下:一个高质量的 crawler 是极其复杂的工程,但是如果有好的工具的话,做一个专用的 crawler 还是比较容易的。Scrapy 是一个很轻便的爬虫框架,极大地简化了 crawler 开发的过程。另外,Scrapy 的文档也是十分详细的,如果觉得我的介绍省略了一些东西不太清楚的话,推荐看他的 Tutorial

注:本文开始的那幅图并不是一个 spider (当然啦!-,-bb),那是出自动画片《攻壳机动队》里的思考战车 Tachikoma (很可爱的!)。

74 comments to Scrapy 轻松定制网络爬虫

  • 不错的东西

    但我没搞明白,他除了定制型更强一点,是老外开发的之外,还有哪点比火车头强呢?

  • @大麦壳
    火车头是什么呀?我没有用过这个东西。

  • nicoljiang

    火车头就是个幼稚的软件…
    分析url的速度高达1条/秒
    如果直接给出最终内容地址去采集,例如:
    http://www.contnt.com/{id}.html (id变量为1-N)
    它居然不直接采集并入库,还是要先把这些已经直接给出的url做一番“处理”,速度高达0.6-0.8秒/条。

  • lxneng

    写得很好,最近在看Scrapy,怎么倒腾下Scrapy,期待pluskid写更多的scrapy教程。

  • luiqt

    流程框架和我做的spider一样。 抽空参考Scrapy的框架 优化一下我的结构。
    谢谢pluskid的劳动,你是光荣的。

  • 雨忆

    火车头采集器。。。分付费和免费呢

  • hu

    scrapy-admin.py startproject blog_crawl是在哪运行?

  • @hu
    在任意的地方都可以运行的,它会在当前目录生成项目文件。

  • hu

    @pluskid
    谢谢,明白了,看了下Tutorial,运行scrapy-ctl.py startproject blog_crawl可以了,用scrapy-admin.py好像不行。

  • @hu
    唔?难道我写错了?或者也许是新版本不同了吧?

  • hu

    我在blog_crawl下运行scrapy-ctl.py shell http://mindhacks.cn
    出现[Uninitialized] Unhandled Error,是什么原因呢?

  • @hu
    从这些信息看不出哪里出错了额,你检查一下代码吧。另外,scrapy 可能在新版本里有一些改变的话,要参照他的文档的。

  • hu

    ScrapyHTTPClientFactory instance has no attribute ‘followRedirect’?

  • @hu
    看起来像是它的内部错误啊,你用的是稳定版本的吗?如果是从代码仓库里 check out 出来的话,更新到最新或者下载稳定的 release 版试试吧。

  • woodland33

    关于ScrapyHTTPClientFactory instance has no attribute ‘followRedirect’
    可能是twisted 9.0 兼容问题,可以修改scrapy/core/downloader/webclient.py 来解决。
    具体参照官方方法
    http://dev.scrapy.org/ticket/128
    http://dev.scrapy.org/changeset/1875

  • cx

    我想学习这种方法做网络爬虫,但上面写的有点儿看不明白,请问我需要学习一些什么作为基础?

  • @cx
    会 Python 并且有一定网络编程的基础的话应该就可以了。

  • 抓取和解析现在已经不是什么难的东西了。
    要显出优势,需要智能程度更高一点的。
    包括:自动分析文章页url规则;自动抓取文章页正文。 这两条可以大大减轻规则定义的工作量,而适合于大规模网站的爬行。
    还有就是文章自动分类,需要对下载的问题进行归类、过滤。这样可以保证抓到的内容是我们所关心的。
    下一步就是“发现”了,通过分析归类,发现新的东西

  • 最近也在用Scrapy,发现抓取中文页面的时候,在用extract()抽取数据时有一部分会乱码,另外一部分是好的,而页面编码都是gb2312,不知道大家有没有碰到这种情况。

  • superb

    楼主有没有考虑过如何用scrapy实现一个能持续更新的爬虫呢?

    我看了下源代码,似乎应该在scheduler里面改,但是我发现它是把所有的url按(request, defer)的形式放在内存里的。这样当url太多的时候就没办法了。。。

    不知道博主有没有想过如何能从一个url的数据库中去获取新的url呢?当前的middleware似乎只能做一些过滤的功能。

  • @superb
    他存储在内存中的是将要抓取的 URL 吧,抓完之后会清理掉,我想可以在分析页面得到新的 URL 之后存入数据库,定期地从数据库中取出放到 scheduler 里面去。

  • superb

    刚刚在它的user group里搜了一下。发现在现在的scrapy框架中还是不好解决,原因是request是带有很多的callback的(所谓的在各个模块中传递,就是engine来设置好这些callbak),就不好persistent了。。

    他们现在的开发计划是:
    1. 实现Crawler Spider V2, 是一个callback-less的spider (好象已经实现的差不多了)
    2. 相应的,也要修改scrapy的core部分。
    3. 在1,2的基础上实现persistent scheduler queue….

    看来要等上一阵了。。。

  • @superb
    唔,确实是,感觉他们用那个 twisted 的 callback 事件库之后内部相当复杂,有时候出了问题非常难以调试。

  • 上次乱码的问题,竟然是scrapy0.8的bug,换了0.9有了response编码自动探测就好了

  • ct

    我想问一下楼主,碰到Javascript动态修改dom树的情况怎么爬取html中没有的数据。

  • Leo

    Hi pluskid,

    能帮我看看这个链接的职位信息如何采集吗?
    http://www.intel.com/jobs/jobsearch/index_ne.htm?Location=200000008

    为什么它的post request data有很多?

    谢谢!

    • 你说 post request ?我没有仔细看,莫非这个页面的内容是通过 javascript 加载的?这样的话你可以装一个 Firefox 的 Firebug 插件看到它生成的动态 AJAX 请求的详细信息,如果可行的话,直接用那里同样的内容应该就可以抓取到想要的内容。

  • 之前用过BeautifulSoup,再用Scrapy,然后又切回BeautifuSoup,自己做了个简单的框架,已经做了两版了。因为只是要解析网页抓取资源而已。现在感觉还是XPath比较理想。换来换去的。可能要在Scapy基础上在修改。

    折腾自己。

    不过现在用Python做爬虫,还有一些其他问题。比如如果解决AJAX问题?好像需要Java的DOM来做。此外,认证码有时候也是一个比较麻烦的事情。

  • 此外,我看到网络爬虫中所谓的ICDL/WPT,所谓XML定义的Website Parser Template,网站接卸模板是否有啥推荐的。我现在有个难题,爬许多同一行业的网站的内容差不多,但是采用的技术有些差别。很难统一在数据库中。

    我想如果Scrapy配合WPT的,可以减少不少编程工作量吧?

  • fragesteller

    博主你好,
    我最近想用scrapy,看到你文章好开心,可是到了新建项目这个步骤总是不成功。我用了命令scrapy-admin.py startproject blog_crawl,可总说是invalid syntax。scrapy的原文网站我也看了,还不如你博客详细。麻烦你能再写的详细点吗,比如打开什么软件,然后打开哪个窗口,在哪里输入命令等等,最好是能把在windows下和ubuntu下两种情况都写一下。我在这里谢谢了!!
    急盼回复!

  • […] 如果想了解scrapy的话,推荐pluskid的scrapy轻松定制网络爬虫,写的很清晰。 […]

  • […] Scrapy 轻松定制网络爬虫,http://blog.pluskid.org/?p=366 └ Tags: python, […]

  • gstarweb

    最近在研究scrapy,研究有结果了,我也分享心得

  • […] 使用scrapy进行大规模抓取 March 14th, 2011 发表评论2011/03/14/scrapy_intro.html 阅读评论 使用scrapy有大概半年了,算是有些经验吧,在这里跟大家讨论一下使用scrapy作为爬虫进行大规模抓取可能遇到的问题。我们抓取的目标是教育网上的网站(目前主要针对.edu.cn和.cas.cn/.cass.cn域名),这半年里抓取了百万以上的url,其实百万url的规模不算大,我们一直在断断续续的修改,还没有开始全面的抓取。如果想了解scrapy的话,推荐pluskid的scrapy轻松定制网络爬虫,写的很清晰。我们对scrapy的修改是基于0.9 stable的(写这篇文章时最新的release是0.10)。我们为了实现定制需求而进行的修改经常破坏了scrapy的代码结构,所以这里就不贴代码了,有兴趣的朋友可以留言或者邮件讨论。首先说下scrapy的定位吧,我们看来它是为抓取单一网站上固定格式的内容而设计的,比如抓取一个小说网站上的小说或者抓取一个电子商务网站上的商品。好在scrapy结构清晰,可以很方便得修改它来实现更复杂的需求。要进行大规模的抓取,可能要修改scrapy以处理如下问题。快速的link extractor。python的SGMLParser实在是太慢了,使用SgmlLinkExtractor会让爬虫把大部分的时间都浪费在解析网页上,最好自己写一个link extractor(我们基于lxml写了一个)。也可以用正则表达式来写link extractor,速度快,问题是不理解html语义,会把注释里的链接也包含进来。另外基于javascript重定向url也要在这里提取出来。Spider Trap(?)。我们解决这个问题的方法比较暴力。因为spider trap一般是由动态网页实现的,所以我们最开始的方案就是先通过url是否包含”?”来判断一个网页是否是动态网页,然后取得不包含参数的url地址,对这个地址进行计数,设置一个阈值,超过阈值之后不再抓取。这个方案遇到的困扰在于很多网站开启了url rewrite,使得判别一个页面是否是动态页面很困难。现在的方法是把网页按照所引用的css文件进行聚类,通过控制类里最大能包含的网页数量防止爬虫进入trap后出不来,对不不含css的网页会给一个penalty,限制它能产生的链接数量。这个办法理论上不保证能避免爬虫陷入死循环,但是实际上这个方案工作得挺好,因为绝大多数网页都使用了css,动态网页更是如此。之所以说着方法比较暴力是因为这两种方法阈值都是写死的,不能通过抓取的结果,网页的相似度,网页的重要性进行修改。增量抓取。一个针对多个网站的爬虫很难一次性把所有网页爬取下来,并且网页也处于不断更新的状态中,爬取是一个动态的过程,爬虫支持增量的抓取是很必要的。大概的流程就是关闭爬虫时保存duplicate filter的数据,保存当前的request队列,爬虫启动时导入duplicate filter,并且用上次request队列的数据作为start url。这里还涉及scrapy一个称得上bug的问题,一旦抓取队列里url过多,关闭scrapy需要很久,有时候要花费几天的时间。我们hack了scrapy的代码,在接收到关闭命令后,保存duplicate filter数据和当前的request队列和已抓取的url列表,然后调用twisted的reactor.stop()强制退出。当前的request队列可以通过scrapy.core.scheduler的pending_requests成员得到。高效数据存储。抓取的页面多了之后如何存储就成了一个问题,按我们的统计纯html页面的平均大小大概在20~30k之间,百万的页面抓下来之后占用的硬盘空间大概是几十G。ntfs和ext3这些文件系统在小文件过多之后效率都会比较低,需要优化存储效率。我们目前将页面的url进行hash(sha1),以hash值作为文件名,hash值的前几位作为目录名存储页面,以后准备使用HDFS来存储数据。scrapy的开发者还是挺活跃的,这两天就看到scrapy的blog上有专家写关于ajax抓取的文章,ajax抓取是个挺常出现的问题。目前新版本的scrapy(0.10)支持持久化队列支持增量抓取,爬虫关闭过慢的问题也得到了一定的解决,相信更多特性会逐步开发出来。我们目前的数据量其实也谈不上大规模,针对这些问题的解决方式也不够完美。这里只是列举一部分可能遇到的问题,实际的问题还有很多,比如编码检测/确定网页的重要性/定期更新。还有一个大问题就是内存泄露,在python里检查内存泄露很难,尤其是对于工作在twisted上的这种持续工作的网络程序,这个以后再谈吧。下面还有一些小建议。1.作为通用的爬虫的话尽量使用宽度优先的策略,在配置里设置 SCHEDULER_ORDER = ‘BFO’2.修改单爬虫的最大并行请求数 CONCURRENT_REQUESTS_PER_SPIDER3.如果自己实现duplicate filter的话注意要保证它是一直可用的,dupfilter里的异常是不会出现在日志文件中的,好像外面做了try-expect处理,我也没仔细看这部分 分类: Python 标签: search  相关文章 […]

  • 我们用一样的主题哦

    开始学习python,你的文章很有帮助。

  • 龙之白云

    您好,请问我抓取的网站一直显示(referer: None)是怎么回事?还有items总是ImportError,不知道怎么办。望您回答

  • […] – 入门文章:Scrapy 轻松定制网络爬虫 […]

  • 杨晓伟

    写得不错,赞一个!
    最近用scrapy写了满足自己需求的小东西。但有一点,就是我需要这它一直守护(记录一些状态,这样做最方便),每隔2个小时爬一次,不爬取时处于暂停或休眠状态。但问题来了,要爬取的页面在不到一分钟内就分析完毕了,这样队列为空,scrapy就退出了。
    想用sleep,没法用,因为事先不知道那个URL是一次爬取过程中的最后一次解析。

    其实,简单点说就是给spider加一个暂停功能。

  • David Wong

    楼上的,可以在pipeline收到spider_closed信号后重新启动这个进程。
    我现在是这样做的

  • shaoping

    from scrapy.core import signals
    from scrapy.item import ScrapedItem
    这两个在我的版本中都编译不过去,我的scrapy版本是0.14.2,你用的是什么版本呢?

  • 分析一下页面数据的来源,如果是写在 JS 里,那就正则拿出来;如果是 JSON 传递,把接口搞出来就更容易了。

  • redir

    请教下pluskid前辈,scrapy必须以scrapy crawal test这样来运行吗?url地址能否通过命令行参数的形式来输入?谢谢

  • Lin

    我最近也在用java自己写一个网络爬虫,有一个问题请教一下:关于如何获得javascript动态生成的页面的完整能容博主有研究吗?像firebug这样的工具是怎么得到完整的源代码的呢?以及可以用像firefox这样的开元浏览器中的部分代码,直接得到浏览器输出的html代码吗? thanks :)

    • Firebug 是基于浏览器的基础上,浏览器要渲染网页,把 html 结构都已经动态处理好了,Firebug 能够拿到文档树所以能够得到动态的代码。你可以试试用 SpiderMonkey 等 javascript 引擎来自己做,或者直接在 Firebug 的基础上做爬虫,总之都不是一件容易的事情。

  • 看起来还是很简单,不知道真实用的话好不好使,现在在用Beautiful soup自己写爬虫。

  • fivefog

    的确你写的scrapy的版本有些低

  • Hi

    如果我想访问 a.html,结果服务器会重定向到 redirect.html?url=a.html,而redirect.html中是一些javascript代码,用来写cookie,那么我该怎么办才能访问到原网页呢?

  • 当然还有一个比较‘傻’的方法,就是自己做个浏览器的插件bot,让这个bot来模拟人的点击行为,这样就可以在webview里自动执行所有js了。

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>