Some Experiences Of Using Scrapy

Some Experiences Of Using Scrapy

Posted@2011-05-28 10 p.m.

Categoriespython ,  scrapy

About Scrapy

Scrapy Tutorial

FAQ That Not Included In Manual

Other Tricks

About Scrapy

Scrapy是一个抓取网站的框架,用户需要做的只是定义抓取网站的spider,并在其中定义抓取的规则,获取需要抓取的数据,Scrapy管理其他复杂的工作,比如并发请求,提取之后的数据保存等。
Scrapy 声称他们“偷取”了Django的灵感,虽然两者的方向怎么都联系不到一起去,但是确实如果对Django有了解,对Scrapy的结构会感到很亲切。 Scrapy也会有项目的概念,一个项目里面可以包含多个抓取蜘蛛(spider),抓取的数据结构定义Items,以及一些配置。
Scrapy抓取的流程:通过spider中的定义需要抓取的网站,并将需要的数据提取到Items里面保存,然后通过管道(pipeline)将Items里面的数据提取,保存到文件或者数据库。

Scrapy Tutorial

首先,新建一个项目叫dmoz:

这里参考Scrapy Tutorial里面的例子做说明,抓取Open directory project(dmoz)上的数据。

scrapy startproject dmoz

将会创建一个叫dmoz的目录,结构如下:

dmoz/
   scrapy.cfg   
   dmoz/
       __init__.py
       items.py
       pipelines.py
       settings.py
       spiders/
           __init__.py
           ...
  • scrapy.cfg: 项目配置文件(基本上让它吧)
  • items.py: 需要提取的数据结构定义文件
  • pipelines.py: 管道定义,用来对items里面提取的数据做进一步处理
  • settings.py: 放一些配置
  • spiders: 放置spider的目录

然后,在items.py里面定义我们要抓取的数据:

from scrapy.item import Item, Field

class DmozItem(Item):
   title = Field()
   link = Field()
   desc = Field()

这里我们需要获取dmoz页面上的标题,链接,描述,所以定义一个对应的items结构,不像Django里面models的定义有那么多种类的Field,这里只有一种就叫Field(),再复杂就是Field可以接受一个default值。

接下来,开始写spider:

spider只是一个继承字scrapy.spider.BaseSpider的Python类,有三个必需的定义的成员

  • name: 名字,这个spider的标识
  • start_urls: 一个url列表,spider从这些网页开始抓取
  • parse(): 一个方法,当start_urls里面的网页抓取下来之后需要调用这个方法解析网页内容,同时需要返回下一个需要抓取的网页,或者返回items列表(到底返回哪个,见FAQ)

所以在spiders目录下新建一个spider,dmoz_spider.py:

class DmozSpider(BaseSpider):
   name = "dmoz.org"
   start_urls = [
       "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
       "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"
   ]

   def parse(self, response):
       filename = response.url.split("/")[-2]
       open(filename, 'wb').write(response.body)

下一步,提取数据到Items里面,这里主要用到XPath提取网页数据:

scrapy有提供两个XPath选择器,HtmlXPathSelector和XmlXPathSelector,一个用于HTML,一个用于XML,XPath选择器有三个方法

  • select(xpath): 返回一个相对于当前选中节点的选择器列表(一个XPath可能选到多个节点)
  • extract(): 返回选择器(列表)对应的节点的字符串(列表)
  • re(regex): 返回正则表达式匹配的字符串(分组匹配)列表

一种很好的方法是在Shell里面对XPath进行测试:

scrapy shell http://www.dmoz.org/Computers/Programming/Languages/Python/Books/

现在修改parse()方法看看如何提取数据到items里面去:

def parse(self, response):
      hxs = HtmlXPathSelector(response)
      sites = hxs.select('//ul/li')
      items = []
      for site in sites:
          item = DmozItem()
          item['title'] = site.select('a/text()').extract()
          item['link'] = site.select('a/@href').extract()
          item['desc'] = site.select('text()').extract()
          items.append(item)
      return items

最后,保存抓取的数据:

scrapy提供了几个选项,可以将数据保存为json,csv或者xml文件,下面开始放出定义的dmoz_spider(注意他的name是dmoz.org),并将抓取的数据保存为json,在dmoz目录下执行命令

scrapy crawl dmoz.org --set FEED_URI=items.json --set FEED_FORMAT=json

如果需要对items数据进一步处理,比如直接保存到数据库,就要用到pipelines

FAQ That Not Included In Manual

不断的抓取下一个链接如何实现,items如何保存?

这里需要解释一下parse()方法,parse可以返回Request列表,或者items列表,如果返回的是Request,则这个Request会放到下一次需要抓取的队列,如果返回items,则对应的items才能传到pipelines处理(或者直接保存,如果使用默认FEED exporter)。那么如果由parse()方法返回下一个链接,那么items怎么返回保存? Request对象接受一个参数callback指定这个Request返回的网页内容的解析函数(实际上start_urls对应的callback默认是parse方法),所以可以指定parse返回Request,然后指定另一个parse_item方法返回items:

def parse(self, response):
    # doSomething
    return [Request(url, callback=self.parse_item)]
def parse_item(self, response):
    # item['key'] = value
    return [item]

关于解析函数的返回值,除了返回列表,其实还可以使用生成器,是等价的:

def parse(self, response):
    # doSomething
    yield Request(url, callback=self.parse_item)
def parse_item(self, response):
    yield item

如何在解析函数之间传递值?

一种常见的情况:在parse中给item某些字段提取了值,但是另外一些值需要在parse_item中提取,这时候需要将parse中的item传到parse_item方法中处理,显然无法直接给parse_item设置而外参数。 Request对象接受一个meta参数,一个字典对象,同时Response对象有一个meta属性可以取到相应request传过来的meta。所以解决上述问题可以这样做:

def parse(self, response):
    # item = ItemClass()
    yield Request(url, meta={'item': item}, callback=self.parse_item)
def parse(self, response):
    item = response.meta['item']
    item['field'] = value
    yield item

pipelines.py如何使用?

具体参考:http://doc.scrapy.org/topics/item-pipeline.html,只需要在settings.py中启用定义的pipelines组件即可,可能困惑的地方在于如果指定了默认的feed exporter,piplelines会对item处理的流程会有什么影响,答案是pipelines会取代默认的feed exporter,项目中所有spider返回的item(比如parse_item)最后都会传入pipelines中定义的proccess_item()方法进一步处理。

Other Tricks

如何处理extract()返回为空列表的情况?

因为extract()方法返回的是字符串列表,如果选择器没有获取到某个节点的内容,则是一个空列表,所以经常会遇到这种处理:

item['field'] = ex_data[0].strip() if len(ex_data) > 0 else ''

一种更好的处理方式:

item['field'] = ''.join(ex_data).strip()

如何给XPath选取内容设置默认值?

XPath选取节点内的文本时,如果节点内容为空,XPath不会返回一个空字符串,而是什么都不返回,对应到列表就是对应的列表项少一项,有时候需要这样的空字符串当默认值。XPath中有一个concat函数可以实现这种效果:

text = hxs.select(‘concat(//span/text(), “”)’).extract()

对于空span会返回一个空字符串

scrapy.log是很好用的调试工具

需要先在settings.py中指定LOG_LEVEL,默认为‘DEBUG’,所以抓取的时候每个item获取的内容都会输出到屏幕,如果抓取的内容太多,有时候会把一些异常信息淹没。所以有时候需要设置高一点的级别,比如‘WARNING’,这样在spider中可以在需要的地方使用log.msg('info', log.WARNING)输出一些有用的信息。

另一种方便的调试方法,在spider中调用交互shell环境

在需要中断调试的地方插入:

from scrapy.shell import inspect_response
inspect_response(response)

这时候会打断抓取,进入一个shell,response为当前抓取的url内容。

你可能感兴趣的:(Some Experiences Of Using Scrapy)