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) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/> (referer: None)
2014-01-23 18:13:09-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (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: 选择<head>元素中的<title>元素
  • /html/head/title/text(): 选择上面<title>元素中的文本
  • //td: 选择所有的<td>元素
  • //div[@class="mine"]: 选择所有包含属性class="min"div元素

为了解析CSS和XPath表达式,Scrapy提供选择器Selector类以及方便的快捷式避免每次都重复实例化选择器。总体说来,选择器可以被看作是表示文档结构节点的对象。所以,第一个实例化的选择器是和根节点即整个文档关联在一块的。

Scrapy中的Selector含有四个常用的基本方法

  • xpath(): 返回一个选择器列表,每一个元素代表根据xpath表达式选择的节点。
  • css(): 返回一个选择器列表,每一个元素代表根据css表达式选择的节点。
  • extract: 返回选择数据的unicode字符串
  • re(): 返回作用上正则表达式的unicode字符串列表

在壳(shell)中尝试选择器

在项目的根目录执行命令

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

此时IPython环境看起来如下

[ ... 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]:

在shell环境加载后,我们可以通过局部变量response获取响应数据,所以如果用户在命令行中输入response.body,就会看到响应的主体,类似地,你可以通过输入response.headers访问其头部数据。

此时,response变量含有选择器selector属性,为Selector类的一个实例,随着这个具体的reponse实例化。因此用户可以通过调用response.selector.xpath()或者response.selector.css()来获取需要的部分,也可以通过快捷表达式response.xpath()response.css()来调用。测试我们刚刚介绍的选择器,有

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</title>']

In [3]: response.xpath('//title/text()')
Out[3]: [<Selector xpath='//title/text()' data=u'Open Directory - Computers: Programming:'>]

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。通过观察源代码可以发现,需要的网页信息其实都存储在一个<ul>元素内,所以整体流程是通过选择<li>元素列表来获取需要信息:

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中实现自己的存储方法。

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

推荐阅读更多精彩内容