Some Experiences Of Using Scrapy

转载 2013年12月02日 11:37:58

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

About Scrapy Scrapy是一个抓取网站的框架,用户需要做的只是定义抓取网站的spider,并在其中定义抓取的规则,获取需要抓取的数据,Scrapy管理其他复杂的工作,比如并发请求,提取...
  • zhaixh_89
  • zhaixh_89
  • 2013年11月26日 15:51
  • 503

MyEclipse实操错误2:Some characters cannot be mapped using "GBK" character encoding

1:Some characters cannot be mapped using "GBK" character encoding  Window->Profermance->General->Co...
  • cafuc46wingw
  • cafuc46wingw
  • 2014年05月29日 15:51
  • 2082

Some experiences in axis.

 There is a perfect tool called "soapUI",it can help you to generate code easily. 1.Add SOAPHeader i...
  • stefli
  • stefli
  • 2008年09月17日 17:26
  • 589

Some of my experiences for cocos2d-x+lua

I recently submitted an IOS application(IQ Pyramid), which is based on cocos2d-x(http://www.cocos2d-...
  • dragoncheng
  • dragoncheng
  • 2011年10月04日 11:33
  • 19142

关于“Some characters cannot be mapped using "GB2312" character encoding. ”的问题

今天在做项目时,遇到了一个问题。因为是在别人做好的基础上改东西,所以,一些东西不能按照自己的变成习惯来。就比如,我创建项目习惯刚建完就设置字符的编码集为UTF-8,而人家的项目就未必是那样了。好了,废...
  • machunmei2
  • machunmei2
  • 2014年06月10日 11:08
  • 1246

解决问题Some characters cannot be mapped using 'ISO-8859-1' character encoding

突然好好的jsp文件没法正常保存了, Save could not be completed.  Reason: Some characters cannot be mapped using "I...
  • XinTeng2012
  • XinTeng2012
  • 2014年09月25日 15:57
  • 2537

MyEclipse编辑js的时候出现some characters cannot be mapped using "ISO-8859-1" charater encoding

IDE: Myeclipse Problem: somecharacters cannot be mapped using "ISO-8859-1" charater encoding I...
  • zhang6622056
  • zhang6622056
  • 2011年12月29日 12:26
  • 3951

some characters cannot be mapped using “Cp1252″ character encoding.

This is a problem you can face while developing applications in Eclipse here is a solution for this ...
  • hellobinfeng
  • hellobinfeng
  • 2013年03月13日 16:12
  • 1540

用myeclipse修改文件是出现“some characters cannot be mapped using "iso-8859-1"character encoding

解决: window      -->      General     -->        Content Types      -->   Text    ----》根据你修改的文件类型,改编...
  • KILANG
  • KILANG
  • 2017年01月05日 16:55
  • 359

Save could not be completed. Reason: some characters cannot be mapped using “ISO-8859-1“ character encoding.的解决方案分析

刚才在套用模板时 出现了以下的错误: Save could not be completed. Reason: some characters cannot be mapped ...
  • eryueban213
  • eryueban213
  • 2010年11月28日 20:08
  • 4683
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Some Experiences Of Using Scrapy
举报原因:
原因补充:

(最多只允许输入30个字)