Scrapy 教程

Scrapy 教程

本文翻译自scrapy的最新官方教程,觉得有帮助的朋友可以小小打赏一下,谢谢。

首先,用户需要安装Scrapy,可以参见安装指导。在本教程中,我们将爬取网站dmoz,并包含以下这些任务:

  1. 创建一个全新的Scrapy项目
  2. 定义用户想爬取的数据类别
  3. 编写一个爬虫分析一个网页并提取所需数据
  4. 编写流程来存储所提取的数据

创建一个项目

首先,我们在目标路径下输入并执行以下代码

scrapy startproject tutorial

这会创建一个tutorial目录,其中含有以下内容

tutorial/
    scrapy.cfg              # 配置文件
    
    tutorial/               # 项目的Python模组
        __init__.py
        
        items.py            # items文件,数据格式定义
        
        pipelines.py        # pipelines文件,定义流程处理
        
        settings.py         # settings文件,项目设置
        
        spiders/            # 爬虫路径,用户根据需要定义自己的爬虫
            __init__.py
            ...

定义我们的数据(Item)

Items是存储的容器,随着爬虫工作而加载,调用格式上和Python字典类似。Scrapy中也可以使用单纯的Python字典,但是Items提供了额外的对于填充未声明字段的保护机制,避免用户输入错误引起的错误。

我们通过创建一个scrapy.Item类,并定义其属性为scrapy.Field来声明Scapy中的数据类型。这和对应关系映射(ORM)机制类似。

我们首先对我们希望从dmoz.org获取到的数据进行建模刻画。具体而言,我们希望获取网页的名称,连接以及描述信息,因此我们对这三个属性进行字段定义。因此我们如下编写tutorial目录下的items.py

import scrapy

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

这种定义初看比较复杂,但是定义这样一个类会让用户可以进一步使用其他Scrapy中便利的部件和助手。

第一个爬虫

爬虫(spiders)是用户定义的一系列类,Scrapy根据这些类在某个定义域内抓取用户感兴趣的信息。在爬虫中,用户定义初始的需要下载的连接,怎么进一步扩展爬虫,如何解析当前页面并提取之间定义的Items

为了创建一个爬虫,用户首先需要定义一个scrapy.Spider的子类,并且定义某些属性:

  • name:是爬虫的识别符,在当前项目中必须是唯一,不能为不同的爬虫设置相同的名称。
  • start_urls:一组起始链接集合,爬虫将从这些链接开始。接下来的链接会从这些起始链接中提取。
  • parse:爬虫的一个方法,会以下载起始链接得到的Response作为参数调用。其中Response作为唯一参数传递进去。这个方法负责解析响应数据并提取爬取的数据以及更多的链接。 更进一步,parse方法负责处理相应并返回数据和接下来的链接。

所以我们首先如下定义第一个爬虫,存在tutorial/spiders目录下,命名为dmoz_spider.py

import scrapy

class DmozSpider(scrapy.Spider):
    name = 'dmoz'
    allowed_domains = ["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] + '.html'
        with open(filename, 'wb') as f:
            f.write(response.body)

抓取

Scrapy中我们通过如下命令执行爬虫任务

scrapy crawl dmoz

注意上述命令在项目的最高层目录执行,而dmoz就是上面定义的爬虫唯一标识符。第一次我们会得到类似下面的结果输出:

2014-01-23 18:13:07-0400 [scrapy] INFO: Scrapy started (bot: tutorial)
2014-01-23 18:13:07-0400 [scrapy] INFO: Optional features available: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Overridden settings: {}
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled extensions: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled downloader middlewares: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled spider middlewares: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled item pipelines: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Spider opened
2014-01-23 18:13:08-0400 [scrapy] DEBUG: Crawled (200)  (referer: None)
2014-01-23 18:13:09-0400 [scrapy] DEBUG: Crawled (200)  (referer: None)
2014-01-23 18:13:09-0400 [scrapy] INFO: Closing spider (finished)

由于我们之前的parse中将响应的body写成文件并存储在最高层目录。

对当前爬虫的分析

Scrapy首先针对每一个在start_urls中的链接创建scrapy.Request对象,并将parse方法设置为爬虫的回调函数。这些请求被Scrapy安排时间规划并先后执行,返回scrapy.http.Response对象然后通过parse方法传输给爬虫对象。

提取数据

选择器简介

这里有不同的方法从网页中提取数据。Scrapy使用基于XPATH或者CSS的机制实现了自己的选择器Scrapy Selectors。这里简单介绍一些XPath表达式以及其含义

  • /html/head/title: 选择元素中的</code>元素</li> <li> <code>/html/head/title/text()</code>: 选择上面<code><title></code>元素中的文本</li> <li> <code>//td</code>: 选择所有的<code><td></code>元素</li> <li> <code>//div[@class="mine"]</code>: 选择所有包含属性<code>class="min"</code>的<code>div</code>元素</li> </ul> <p>为了解析CSS和XPath表达式,<strong>Scrapy</strong>提供选择器<code>Selector</code>类以及方便的快捷式避免每次都重复实例化选择器。总体说来,选择器可以被看作是表示文档结构节点的对象。所以,第一个实例化的选择器是和根节点即整个文档关联在一块的。</p> <p><strong>Scrapy</strong>中的<code>Selector</code>含有四个常用的基本方法</p> <ul> <li> <code>xpath()</code>: 返回一个选择器列表,每一个元素代表根据xpath表达式选择的节点。</li> <li> <code>css()</code>: 返回一个选择器列表,每一个元素代表根据css表达式选择的节点。</li> <li> <code>extract</code>: 返回选择数据的unicode字符串</li> <li> <code>re()</code>: 返回作用上正则表达式的unicode字符串列表</li> </ul> <h3>在壳(shell)中尝试选择器</h3> <p>在项目的根目录执行命令</p> <pre><code>scrapy shell "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/" </code></pre> <p>此时IPython环境看起来如下</p> <pre><code>[ ... Scrapy log here ... ] 2014-01-23 17:11:42-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None) [s] Available Scrapy objects: [s] crawler <scrapy.crawler.Crawler object at 0x3636b50> [s] item {} [s] request <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> [s] response <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> [s] settings <scrapy.settings.Settings object at 0x3fadc50> [s] spider <Spider 'default' at 0x3cebf50> [s] Useful shortcuts: [s] shelp() Shell help (print this help) [s] fetch(req_or_url) Fetch request (or URL) and update local objects [s] view(response) View response in a browser In [1]: </code></pre> <p>在shell环境加载后,我们可以通过局部变量<code>response</code>获取响应数据,所以如果用户在命令行中输入<code>response.body</code>,就会看到响应的主体,类似地,你可以通过输入<code>response.headers</code>访问其头部数据。</p> <p>此时,<code>response</code>变量含有选择器<code>selector</code>属性,为<code>Selector</code>类的一个实例,随着这个具体的<code>reponse</code>实例化。因此用户可以通过调用<code>response.selector.xpath()</code>或者<code>response.selector.css()</code>来获取需要的部分,也可以通过快捷表达式<code>response.xpath()</code>和<code>response.css()</code>来调用。测试我们刚刚介绍的选择器,有</p> <pre><code>In [1]: response.xpath('//title') Out[1]: [<Selector xpath='//title' data=u'<title>Open Directory - Computers: Progr'>] In [2]: response.xpath('//title').extract() Out[2]: [u'<title>Open Directory - Computers: Programming: Languages: Python: Books'] In [3]: response.xpath('//title/text()') Out[3]: [] In [4]: response.xpath('//title/text()').extract() Out[4]: [u'Open Directory - Computers: Programming: Languages: Python: Books'] In [5]: response.xpath('//title/text()').re('(\w+):') Out[5]: [u'Computers', u'Programming', u'Languages', u'Python']

    可以看到,直接选择出来为Scrapy中的选择器对象,需要通过extract()或者re()方法提取。

    提取数据

    有了之前的基础,我们现在从这些页面中尝试提取出真实有用的信息。注意到response.body是网页的源代码,为HTML代码,通常很难对其进行直接分析,用户可以使用一些可视化方法来辅助,比如Firebug。通过观察源代码可以发现,需要的网页信息其实都存储在一个

      元素内,所以整体流程是通过选择
    • 元素列表来获取需要信息:

      response.xpath('//ul/li')
      

      以及其中对网页的描述

      response.xpath('//ul/li/text()').extract()
      

      网页的标题为

      response.xpath('//ul/li/a/text()').extract()
      

      以及这些网页的链接地址

      response.xpath('//ul/li/a/@href').extract()
      

      正如我们提到的,每一个.xpath()返回一个选择器列表,所以我们可以通过对选择器调用.xpath()方法去进一步获取需要数据,例如我们将之前的几种选择器融合在一块,有

      for sel in response.xpath('//ul/li'):
          title = sel.xpath('a/text()').extract()
          link = sel.xpath('a/@href').extract()
          desc = sel.xpath('text()').extract()
          print title, link, desc
      

      我们将这部分放到之前的parse()中,有

      import scrapy
      
      class DmozSpider(scrapy.Spider):
          name = "dmoz"
          allowed_domains = ["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):
              for sel in response.xpath('//ul/li'):
                  title = sel.xpath('a/text()').extract()
                  link = sel.xpath('a/@href').extract()
                  desc = sel.xpath('text()').extract()
                  print title, link, desc
      

      此时我们将不再得到简单的HTML文件,而是需要的数据格式

      使用我们的Item

      Item对象是Scrapy定制的Python字典,所以我们可以简单使用标准的字典语法来获取我们想要的值

      >>> item = DmozItem()
      >>> item['title'] = 'Example title'
      >>> item['title']
      'Example title'
      

      和普通字典没有什么区别。为了返回当前我们爬到的数据,最终的爬虫看上去如下:

      import scrapy
      
      from tutorial.items import DmozItem
      
      class DmozSpider(scrapy.Spider):
          name = "dmoz"
          allowed_domains = ["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):
              for sel in response.xpath('//ul/li'):
                  item = DmozItem()
                  item['title'] = sel.xpath('a/text()').extract()
                  item['link'] = sel.xpath('a/@href').extract()
                  item['desc'] = sel.xpath('text()').extract()
                  yield item
      

      使用这个爬虫去爬取dmoz.org可以得到DmozItem对象

      [scrapy] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
           {'desc': [u' - By David Mertz; Addison Wesley. Book in progress, full text, ASCII format. Asks for feedback. [author website, Gnosis Software, Inc.\n],
            'link': [u'http://gnosis.cx/TPiP/'],
            'title': [u'Text Processing in Python']}
      [scrapy] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
           {'desc': [u' - By Sean McGrath; Prentice Hall PTR, 2000, ISBN 0130211192, has CD-ROM. Methods to build XML applications fast, Python tutorial, DOM and SAX, new Pyxie open source XML processing library. [Prentice Hall PTR]\n'],
            'link': [u'http://www.informit.com/store/product.aspx?isbn=0130211192'],
            'title': [u'XML Processing with Python']}
      

      紧接着的链接

      假设除开仅仅爬取BooksResources页面,我们还想获取所有Python路径下的页面。现在用户知道如何从一个页面提取数据,所以现在问题是如何从当前页面抓取用户感兴趣页面的链接,紧接着在这些页面再次提取感兴趣的数据。将爬虫代码做一些小小的修正:

      import scrapy
      
      from tutorial.items import DmozItem
      
      class DmozSpider(scrapy.Spider):
          name = "dmoz"
          allowed_domains = ["dmoz.org"]
          start_urls = [
              "http://www.dmoz.org/Computers/Programming/Languages/Python/",
          ]
      
          def parse(self, response):
              for href in response.css("ul.directory.dir-col > li > a::attr('href')"):
                  url = response.urljoin(href.extract())
                  yield scrapy.Request(url, callback=self.parse_dir_contents)
      
          def parse_dir_contents(self, response):
              for sel in response.xpath('//ul/li'):
                  item = DmozItem()
                  item['title'] = sel.xpath('a/text()').extract()
                  item['link'] = sel.xpath('a/@href').extract()
                  item['desc'] = sel.xpath('text()').extract()
                  yield item
      

      现在parse()方法仅仅提取了链接数据,通过response.urljoin方法建立绝对路径并且产生新的请求,并注册回调函数parse_dir_contents()来爬取需要的数据。这里Scrapy的机制是这样的,当产生新的请求时,Scrapy会调度进程发送请求而回调函数会在请求完成后执行。在这样的机制下,用户可以设计很复杂的爬虫机制,根据规则得到下一步的链接,以及根据当前页面规则提取不同的数据。一个常用的模式是先使用回调函数提取数据,寻找下一个链接然后产生新的请求

      def parse_articles_follow_next_page(self, response):
          for article in response.xpath("//article"):
              item = ArticleItem()
      
              ... extract article data here
      
              yield item
      
          next_page = response.css("ul.navigation > li.next-page > a::attr('href')")
          if next_page:
              url = response.urljoin(next_page[0].extract())
              yield scrapy.Request(url, self.parse_articles_follow_next_page)
      

      存储爬取的数据

      最简单的方法是调用下面的命令

      scrapy crawl dmoz -o items.json
      

      这会产生一个包含所有数据的文件items.json,使用JSON序列化。在小项目中,这种方法一般是足够的,如果想设计更复杂的系统,用户可以编写一个Item Pipeline,即在tutorial/pipelines.py中实现自己的存储方法。

你可能感兴趣的:(Scrapy 教程)