Scrapy 1.5 文档

这篇文档包含了所有你需要了解的关于Scrapy的知识。

获得帮助

遇到麻烦? 我们能提供帮助!

第一步

初窥Scrapy

Scrapy是一个应用程序框架,用于抓取网站并提取可用于广泛的有用应用程序的结构化数据,如数据挖掘,信息处理或历史档案。

尽管Scrapy最初是为web scraping设计的,但它也可以用于使用API​​(如Amazon Associates Web Services)提取数据或用作通用目的的网页抓取工具。

Spider示例演示

为了向您展示Scrapy带来的东西,我们将通过一个Scrapy爬虫示例来向您演示运行Spider的最简单方式。

这里是一个爬虫的代码,它可以在分页之后从网站http://quotes.toscrape.com中抓取名人名言。

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/tag/humor/',
    ]

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.xpath('span/small/text()').extract_first(),
            }

        next_page = response.css('li.next a::attr("href")').extract_first()
        if next_page is not None:
            yield response.follow(next_page, self.parse)

把它放在一个文本文件中,命名为quotes_spider.py,然后使用runspider命令运行Spider:

scrapy runspider quotes_spider.py -o quotes.json

完成后,您将得到包含了文本和作者的JSON格式引号列表的quotes.json文件,看起来像这样(为了更好的可读性在这里重新格式化):

[{
    "author": "Jane Austen",
    "text": "\u201cThe person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.\u201d"
},
{
    "author": "Groucho Marx",
    "text": "\u201cOutside of a dog, a book is man's best friend. Inside of a dog it's too dark to read.\u201d"
},
{
    "author": "Steve Martin",
    "text": "\u201cA day without sunshine is like, you know, night.\u201d"
},
...]
刚刚发生了什么?

当您运行scrapy runspider quotes_spider.py命令时,Scrapy会从内部查找Spider的定义并通过它的爬虫引擎运行Spider。

爬虫通过向start_urls属性中定义的URL发送请求(本例中,仅在humor类别中引用的URL),然后调用默认回调方法 parse,将响应对象作为参数传递。 parse回调中,我们CSS选择器循环引用元素,产生一个被提取引用的文本和作者的Python字典,查找下一页的链接并对另一个请求使用同样的parse方法作为回调。

在这里您会注意到Scrapy的一个主要优势:请求被scheduled and processed asynchronously 这意味着Scrapy不需要等待请求处理完成,它可以同时发送另一个请求或做其他事情。 这也意味着即使某些请求失败或处理时发生错误也可以继续执行其他请求。

这不仅使您能够快速执行爬取(在容错方式下同时发送多个并发请求),Scrapy还可以使你通过a few settings来优雅的控制爬虫。 您可以执行诸如在每个请求之间设置下载延迟,限制每个域或每个IP的并发请求数量,甚至using an auto-throttling extension尝试自动调节这些延迟。

Note

这里使用了feed exports生成JSON文件,您可以轻易更改导出格式(例如XML或CSV)或存储后端(例如FTP或Amazon S3)。 您还可以编写item pipeline将项目存储在数据库中。

还有什么?

您已经看过如何使用Scrapy从网站中提取和存储信息,但这只是表面。 Scrapy提供了许多强大的功能,可以使抓取变得简单高效,例如:

  • 内置支持使用扩展CSS选择器和XPath表达式从HTML/XML源selecting and extracting数据,使用正则表达式作为辅助方法进行提取。
  • 一个交互式shell控制台(IPython软件)用于尝试使用CSS和XPath表达式来抓取数据,这在编写或调试蜘蛛程序时非常有用。
  • 内置支持导出多种格式(JSON,CSV,XML),也可将它们存储在多种后端(FTP,S3,本地文件系统)。
  • 强大的编码支持和自动检测,用于处理外部的,非标准的和破损的编码声明。
  • 强大的可扩展性支持,允许您使用signals和定义良好的API(middlewares,extensionspipelines)。
  • 广泛的内置扩展和中间件处理:
    • cookies和会话处理
    • HTTP特性,如压缩,认证,缓存
    • 用户代理欺骗
    • robots.txt
    • 爬行深度限制
    • 更多
  • 一个Telnet控制台,用于连接Scrapy进程中运行的Python控制台,以反编译和调试您的爬虫程序
  • 还有其他有用的东西,如可重复使用的用于抓取站点地图和XML/CSV中网站的spider,与抓取的信息关联的自动下载图片(或任何其他媒体)的媒体管道,缓存DNS解析器,等等!

接下来是什么?

下一步是安装Scrapy 跟随教程学习如何创建一个全面的Scrapy项目和加入社区 感谢您的关注!

安装指南

安装Scrapy

Scrapy在Python 2.7和Python 3.4以上运行,在CPython(默认Python实现)和PyPy(从PyPy 5.9开始)下运行。

如果您使用AnacondaMiniconda,可以从conda-forge渠道安装软件包,该软件包具有最新的软件包适用于Linux,Windows和OS X.

要使用conda安装Scrapy,请运行:

conda install -c conda-forge scrapy

或者,如果您已经熟悉Python包的安装,则可以使用PyPI安装Scrapy及其依赖项:

pip install Scrapy

请注意,有时这可能需要根据您的操作系统解决某些Scrapy依赖项的编译问题,因此请务必检查平台特定安装说明

我们强烈建议您在专用的virtualenv中安装Scrapy,以避免与系统软件包冲突。

有关更详细的平台特定说明,请继续阅读。

有用的知识

Scrapy是用纯Python编写的,并且依赖于几个关键的Python包:

  • lxml,一种高效的XML和HTML解析器
  • parsel,一个基于lxml的HTML/XML数据提取库
  • w3lib,一款用于处理网址和网页编码的多用途帮手
  • twisted,一个异步网络框架
  • cryptographypyOpenSSL,用来处理各种网络级安全需求

已测试过Scrapy的最低版本是:

  • Twisted 14.0
  • lxml 3.4
  • pyOpenSSL 0.14

Scrapy可能会使用这些软件包的旧版本,但不能保证它会继续工作,因为它没有经过测试。

其中一些软件包本身依赖于非Python包,这可能需要额外的安装步骤,具体取决于您的平台。 请查阅平台特性指南

如果出现依赖关系相关的任何问题,请参阅其各自的安装说明:

平台特定的安装说明

Windows

尽管可以使用pip在Windows上安装Scrapy,但我们建议您安装AnacondaMiniconda,并使用conda-forge渠道中的软件包,这将避免大多数安装问题。

当你安装AnacondaMiniconda完成之后,安装Scrapy:

conda install -c conda-forge scrapy
Ubuntu 14.04或更高版本

Scrapy is currently tested with recent-enough versions of lxml, twisted and pyOpenSSL, and is compatible with recent Ubuntu distributions. But it should support older versions of Ubuntu too, like Ubuntu 14.04, albeit with potential issues with TLS connections.

Don’t use the python-scrapy package provided by Ubuntu, they are typically too old and slow to catch up with latest Scrapy.

To install scrapy on Ubuntu (or Ubuntu-based) systems, you need to install these dependencies:

sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev
  • python-dev, zlib1g-dev, libxml2-dev and libxslt1-dev are required for lxml
  • libssl-dev and libffi-dev are required for cryptography

If you want to install scrapy on Python 3, you’ll also need Python 3 development headers:

sudo apt-get install python3 python3-dev

Inside a virtualenv, you can install Scrapy with pip after that:

pip install scrapy

Note

The same non-Python dependencies can be used to install Scrapy in Debian Jessie (8.0) and above.

Mac OS X

构建Scrapy的依赖需要存在C编译器和开发头文件。 在OS X上,这通常由Apple的Xcode开发工具提供。 要安装Xcode命令行工具,请打开一个终端窗口并运行:

xcode-select --install

已知问题可能会阻止pip更新系统包。 必须解决这个问题才能成功安装Scrapy及其依赖项。 以下是一些建议的解决方案

  • (推荐) 不要使用系统python,安装一个不会与系统其余部分冲突的新的更新版本。 下面介绍了如何使用 homebrew 软件包管理器:

    • 按照https://brew.sh/中的说明安装homebrew

    • 更新你的PATH变量​​,应该在系统包之前声明homebrew包(如果你使用 zsh作为默认shell,将.bashrc 改为 .zshrc):

      echo "export PATH=/usr/local/bin:/usr/local/sbin:$PATH" >> ~/.bashrc
      
    • 重新加载.bashrc以确保更改发生:

      source ~/.bashrc
      
    • 安装python:

      brew install python
      
    • Python的最新版本已经捆绑了pip,所以你不需要单独安装它。 如果情况并非如此,请升级python:

      brew update; brew upgrade python
      
  • (可选)在隔离的python环境中安装Scrapy。

    此方法是上述OS X问题的一种解决方法,也是管理依赖关系的良好实践,可以作为第一种方法的补充。

    virtualenv是一个可用于在python中创建虚拟环境的工具。 我们建议阅读http://docs.python-guide.org/en/latest/dev/virtualenvs/这个教程入门。

在以上任一方法完成后,您应该能够安装Scrapy:

pip install Scrapy
PyPy

我们建议使用最新的PyPy版本。 测试版本是5.9.0。 对于PyPy3,仅测试了Linux安装。

大多数scrapy依赖现在都有用于CPython的二进制转义,但不适用于PyPy。 这意味着这些依赖将在安装过程中建立。 在OS X上,您可能会遇到构建密码依赖性的问题,此问题的解决方案在这里描述,即brew install openssl,然后导出此命令推荐的标志(仅在安装scrapy时需要)。 除了安装构建依赖关系之外,在Linux上安装没有特殊问题。 在Windows上使用PyPy安装scrapy未经测试。

You can check that scrapy is installed correctly by running scrapy bench. If this command gives errors such as TypeError: ... got 2 unexpected keyword arguments, this means that setuptools was unable to pick up one PyPy-specific dependency. To fix this issue, run pip install 'PyPyDispatcher>=2.1.0'.

Scrapy教程

在本教程中,我们假定您的系统上已经安装了Scrapy。 如果不是这种情况,请参阅安装指南

我们将爬取quota.toscrape.com,一个列出著名作家语录的网站。

本教程将引导您完成这些任务:

  1. 创建一个新的Scrapy项目
  2. 编写一个spider来抓取网站并提取数据
  3. 使用命令行导出爬取的数据
  4. 更改spider递归地跟随链接
  5. 使用spider参数

Scrapy是用Python编写的。 如果您对语言很陌生,您可能想先了解语言是什么样子,以充分利用Scrapy。

如果您已经熟悉其他语言,希望快速学习Python,我们建议您阅读Dive Into Python 3 或者,您可以按照Python教程进行操作。

如果您是编程新手,想从Python开始,那么在线书籍Learn Python The Hard Way将对您非常有用。 你也可以看看非程序员的Python资源列表

创建一个项目

在开始抓取之前,您不得不建立一个新的Scrapy项目。 进入您想要存储代码并运行的目录:

scrapy startproject tutorial

这将创建一个包含以下内容的tutorial目录:

tutorial/
    scrapy.cfg            # 部署配置文件

    tutorial/             # 项目的Python模块,你将在这里输入你的代码
        __init__.py

        items.py          # 项目的items定义文件

        middlewares.py    # 项目中间件文件

        pipelines.py      # 项目管道文件

        settings.py       # 项目设置文件

        spiders/          # 稍后放置spider的文件夹
            __init__.py

我们的第一个Spider

Spider是你定义的类,并且Scrapy用它从网站(或一组网站)爬取信息。 它们继承自scrapy.Spider,定义初始请求,可选择如何跟随页面中的链接,以及如何解析下载的页面内容以提取数据。

这是我们第一个Spider的代码。 将它保存在项目中tutorial/spiders目录下名为quotes_spider.py的文件中:

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log('Saved file %s' % filename)

正如你所看到的,我们的Spider继承自scrapy.Spider,定义了一些属性和方法:

  • name:标识Spider。 它在项目中必须是唯一的,也就是说,不能为不同的Spider设置相同的名称。

  • start_requests():必须提供一个Spider开始抓取的迭代请求(你可以返回一个请求列表或者编写一个生成器函数)。 随后的请求将从这些初始请求中接连生成。

  • parse():一个用来处理每个请求下载的响应的方法。 response参数是TextResponse的一个实例,它包含了页面内容以便进一步处理。

    parse()方法通常会解析response,将抓到的数据提取为字典,同时找出接下来新的URL创建新的请求(Request)。

如何运行我们的spider

为了让我们的spider工作,请转到项目的顶层目录并运行:

scrapy crawl quotes

这个命令运行我们刚刚添加名为quotes的spider,它将发送一些针对quotes.toscrape.com域的请求。 你会得到类似于这样的输出:

... (omitted for brevity)
2016-12-16 21:24:05 [scrapy.core.engine] INFO: Spider opened
2016-12-16 21:24:05 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:24:05 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://quotes.toscrape.com/robots.txt> (referer: None)
2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/2/> (referer: None)
2016-12-16 21:24:05 [quotes] DEBUG: Saved file quotes-1.html
2016-12-16 21:24:05 [quotes] DEBUG: Saved file quotes-2.html
2016-12-16 21:24:05 [scrapy.core.engine] INFO: Closing spider (finished)
...

现在,检查当前目录中的文件。 您应该注意到创建了两个新文件:quotes-1.htmlquotes-2.html,其中包含各个网址的内容,就像parse方法指示的那样。

Note

如果您想知道为什么我们还没有解析HTML,请坚持下去,我们很快就会涉及。

发生了什么?

Scrapy调度Spider的start_requests方法返回的scrapy.Request对象。 在收到每个响应后,它会实例化Response对象并调用与请求相关的回调方法(本例中为parse方法)将响应作为参数传递。

start_requests方法的快捷方式

除了实现从网址生成scrapy.Request对象的start_requests()方法外,您还可以定义一个包含网址列表的start_urls类属性。 这个列表将被默认的start_requests()用来为你的spider创建初始请求:

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
        'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)

parse()方法将会被调用来处理这些URL的每个请求,即使我们没有明确告诉Scrapy这样做。 发生这种情况是因为在没有为request明确分配回调方法时,parse()是Scrapy的默认回调方法。

提取数据

学习如何使用Scrapy提取数据的最佳方式是尝试使用shell选择器Scrapy shell 运行:

scrapy shell 'http://quotes.toscrape.com/page/1/'

注意

请记住,从命令行运行Scrapy shell时应该将url用引号括起来,否则包含参数的url(例如 & 字符) 将出现问题。

在Windows上,请使用双引号:

scrapy shell "http://quotes.toscrape.com/page/1/"

你会看到类似于:

[ ... Scrapy log here ... ]
2016-09-19 12:09:27 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x7fa91d888c90>
[s]   item       {}
[s]   request    <GET http://quotes.toscrape.com/page/1/>
[s]   response   <200 http://quotes.toscrape.com/page/1/>
[s]   settings   <scrapy.settings.Settings object at 0x7fa91d888c10>
[s]   spider     <DefaultSpider 'default' at 0x7fa91c8af990>
[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
>>>

使用shell,您可以在响应对象中使用CSS选择元素:

>>> response.css('title')
[<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]

运行response.css('title')的结果是一个名为SelectorList的列表对象,它表示一个包裹着XML/HTML元素的Selector对象的列表,并允许您运行更多查询来细化选择或提取数据。

要从上述title中提取文本,您可以执行以下操作:

>>> response.css('title::text').extract()
['Quotes to Scrape']

这里需要注意两点:其一是我们已经向CSS查询添加了:: text,这意味着我们只想直接在<title>中选择text元素。 如果我们不指定:: text,我们会得到完整的标题元素,包括它的标签:

>>> response.css('title').extract()
['<title>Quotes to Scrape</title>']

另一件事是调用.extract()的结果是一个列表,因为我们正在处理SelectorList的实例。 如果你只是想要第一个结果,在这种情况下,你可以这样做:

>>> response.css('title::text').extract_first()
'Quotes to Scrape'

或者,你可以这样写:

>>> response.css('title::text')[0].extract()
'Quotes to Scrape'

但是,如果找不到与选择匹配的任何元素,使用.extract_first()可避免发生IndexError并返回None

这里有一个教训:对于大多数爬虫代码,你希望它能够适应在页面上找不到的东西而产生的错误,所以即使某些部分没有被爬取,你至少可以得到一部分数据。

除了extract()extract_first()方法之外,还可以使用re()方法应用正则表达式提取:

>>> response.css('title::text').re(r'Quotes.*')
['Quotes to Scrape']
>>> response.css('title::text').re(r'Q\w+')
['Quotes']
>>> response.css('title::text').re(r'(\w+) to (\w+)')
['Quotes', 'Scrape']

为了找到合适的CSS选择器来使用,你可能会发现在你的web浏览器的shell中使用view(response)打开响应页面很有用。 您可以使用浏览器开发工具或Firebug扩展(请参阅使用Firebug进行爬取使用Firefox进行爬取部分)。

选择器小工具也是一个很好的工具,可以快速查找可视化选定元素的CSS选择器,它可以在许多浏览器中使用。

XPath:简要介绍

除了CSS,Scrapy选择器还支持使用XPath表达式:

>>> response.xpath('//title')
[<Selector xpath='//title' data='<title>Quotes to Scrape</title>'>]
>>> response.xpath('//title/text()').extract_first()
'Quotes to Scrape'

XPath表达式非常强大,是Scrapy选择器的基础。 实际上,CSS选择器在底层被转换为XPath。 如果仔细阅读shell中选择器对象的文本表示形式,你可以发现这一点。

尽管XPath表达式可能不如CSS选择器那么受欢迎,但它提供了更加强大的功能,除了浏览结构之外,它还可以查看内容。 使用XPath,您可以像这样选择内容:选择包含文本“下一页”的链接 这使得XPath非常适合抓取任务,并且即使您已经知道如何构建CSS选择器,我们也鼓励您学习XPath,这会使抓取更容易。

我们在这里不会涉及很多XPath,但您可以阅读有关使用XPath和Scrapy选择器的更多信息 想要了解更多XPath的信息,我们建议通过示例学习XPath学习“如何用XPath思考”

提取语录和作者

现在您已经了解了一些关于选择和提取的内容,让我们通过编写代码来从网页中提取语录来完成我们的spider。

http://quotes.toscrape.com中的每个语录都是由HTML元素表示的,如下所示:

<div class="quote">
    <span class="text">“The world as we have created it is a process of our
    thinking. It cannot be changed without changing our thinking.”</span>
    <span>
        by <small class="author">Albert Einstein</small>
        <a href="/author/Albert-Einstein">(about)</a>
    </span>
    <div class="tags">
        Tags:
        <a class="tag" href="/tag/change/page/1/">change</a>
        <a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a>
        <a class="tag" href="/tag/thinking/page/1/">thinking</a>
        <a class="tag" href="/tag/world/page/1/">world</a>
    </div>
</div>

让我们打开scrapy shell,试着提取我们想要的数据:

$ scrapy shell 'http://quotes.toscrape.com'

我们通过以下方式获得语录HTML元素的选择器列表:

>>> response.css("div.quote")

上述查询返回的每个选择器都允许我们对其子元素运行更多查询。 让我们将第一个选择器分配给一个变量,以便我们可以直接在特定的语录上运行我们的CSS选择器:

>>> quote = response.css("div.quote")[0]

现在,我们从刚刚创建的quote对象中提取titleauthortags

>>> title = quote.css("span.text::text").extract_first()
>>> title
'“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'
>>> author = quote.css("small.author::text").extract_first()
>>> author
'Albert Einstein'

鉴于标签是一个字符串列表,我们可以使用.extract()方法来获取所有这些标签:

>>> tags = quote.css("div.tags a.tag::text").extract()
>>> tags
['change', 'deep-thoughts', 'thinking', 'world']

在弄清楚了如何提取每一个数据之后,我们现在可以遍历所有语录元素,将它们放在一起形成一个Python字典:

>>> for quote in response.css("div.quote"):
...     text = quote.css("span.text::text").extract_first()
...     author = quote.css("small.author::text").extract_first()
...     tags = quote.css("div.tags a.tag::text").extract()
...     print(dict(text=text, author=author, tags=tags))
{'tags': ['change', 'deep-thoughts', 'thinking', 'world'], 'author': 'Albert Einstein', 'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'}
{'tags': ['abilities', 'choices'], 'author': 'J.K. Rowling', 'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”'}
    ... a few more of these, omitted for brevity
>>>
在我们的spider中提取数据

让我们回到我们的spider。 直到现在,它并没有特别提取任何数据,只是将整个HTML页面保存到本地文件中。 让我们将提取逻辑整合到我们的spider中。

Scrapy spider通常会生成许多包含从页面提取的数据的字典。 为此,我们在回调中使用yield Python关键字,如下所示:

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
        'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }

如果你运行这个spider,它会输出提取的数据和日志:

2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['life', 'love'], 'author': 'André Gide', 'text': '“It is better to be hated for what you are than to be loved for what you are not.”'}
2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['edison', 'failure', 'inspirational', 'paraphrased'], 'author': 'Thomas A. Edison', 'text': "“I have not failed. I've just found 10,000 ways that won't work.”"}

存储抓取的数据

存储抓取数据的最简单方法是应用Feed exports,使用以下命令:

scrapy crawl quotes -o quotes.json

这将生成一个quotes.json文件,其中包含所有序列化为JSON的已抓取项目。

由于历史原因,Scrapy附加到给定文件而不是覆盖其内容。 如果您执行该命令两次在第二次执行前未移除该文件,则会生成一个损坏的JSON文件。

您也可以使用其他格式,如JSON Lines

scrapy crawl quotes -o quotes.jl

JSON行格式非常有用,因为它类似于流,您可以轻松地向其添加新记录。 当您运行两次时,它不会发生和JSON相同的问题。 另外,由于每条记录都是一条独立的行,因此可以处理大文件而不必将所有内容都放在内存中,因此有些工具(如JQ)可帮助在命令行执行此操作。

在小型项目中(如本教程中的),应该足够了。 但是,如果您想使用爬虫项目执行更复杂的事情,可以编写Item Pipeline 项目创建时,已经为您设置了Item Pipelines的预留文件,位于tutorial/pipelines.py中。 尽管如果你只是想存储抓取的数据,你不需要实现任何的item pipeline。

使用spider参数

您可以在命令行运行spider时使用-a选项提供参数:

scrapy crawl quotes -o quotes-humor.json -a tag=humor

这些参数被传递给Spider的__init__方法,并默认成为spider的属性。

在此示例中,为tag参数提供的值可通过self.tag获得。 你可以使用它来让你的spider获取带有特定标签的语录,根据参数构建URL:

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        url = 'http://quotes.toscrape.com/'
        tag = getattr(self, 'tag', None)
        if tag is not None:
            url = url + 'tag/' + tag
        yield scrapy.Request(url, self.parse)

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
            }

        next_page = response.css('li.next a::attr(href)').extract_first()
        if next_page is not None:
            yield response.follow(next_page, self.parse)

如果您将tag=humor参数传递给spider,您会注意到它只会访问humor标记中的网址,例如http//quotes.toscrape.com/tag/humor

您可以详细了解如何处理spider参数

下一步

以上教程仅介绍了Scrapy的基础知识,还有很多其他功能未在此处提及。 查看 还有什么? 初窥Scrapy章节中只是对最重要的部分快速浏览。

您可以从基本概念部分继续了解更多关于命令行工具,Spider,选择器和本教程未涉及的其他内容,如对抓取的数据进行建模。 如果您喜欢使用示例项目,请查看示例部分。

示例

学习的最好方法是使用示例,Scrapy也不例外。 出于这个原因,这里有名为quotesbot的Scrapy项目的例子,您可以使用它来玩和学习更多关于Scrapy的知识。 它包含两个http://quotes.toscrape.com的spider,一个使用CSS选择器,另一个使用XPath表达式。

quotesbot项目位于:https://github.com/scrapy/quotesbot 您可以在项目的README中找到更多关于它的信息。

如果你熟悉git,你可以签出代码。 否则,您可以通过单击此处下载该项目的zip文件。

初窥Scrapy
了解Scrapy是什么以及它如何帮助你。
安装指南
在计算机上安装Scrapy。
Scrapy教程
编写你的第一个Scrapy项目。
示例
通过玩预置的Scrapy项目了解更多信息。

基本概念

命令行工具

0.10版本中的新功能。

Scrapy通过scrapy命令行工具进行控制,在这里被称为“Scrapy工具”,以区别于我们称之为“命令”或“Scrapy命令”的子命令。

Scrapy工具提供了多种命令,用于多种目的,并且每个命令都接受一组不同的参数和选项。

scrapy deploy命令已在1.0版中被移除,变为独立的scrapyd-deploy 请参阅部署项目。)

配置设置

Scrapy将在标准位置的ini类型scrapy.cfg文件中查找配置参数:

  1. /etc/scrapy.cfgc:\scrapy\scrapy.cfg(系统范围),
  2. 〜/.config/scrapy.cfg$XDG_CONFIG_HOME)和〜/ .scrapy.cfg$HOME )用于全局(用户范围)设置和
  3. scrapy.cfg在scrapy项目的根目录中(请参阅下一节)。

这些文件中的设置按所列出的优先顺序进行合并:用户定义的值具有比系统范围内的默认值更高的优先级,并且在定义时,项目范围的设置将覆盖所有其他文件。

Scrapy也可以通过一些环境变量进行配置。 目前可设置:

Scrapy项目的默认结构

在深入研究命令行工具及其子命令之前,我们先来了解Scrapy项目的目录结构。

虽然可以修改,但所有Scrapy项目默认具有相同的文件结构,与此类似:

scrapy.cfg
myproject/
    __init__.py
    items.py
    middlewares.py
    pipelines.py
    settings.py
    spiders/
        __init__.py
        spider1.py
        spider2.py
        ...

scrapy.cfg文件所在的目录称为项目根目录 该文件包含定义项目设置的python模块的名称。 这里是一个例子:

[settings]
default = myproject.settings

使用scrapy工具

您可以在开始时运行不带参数的Scrapy工具,它将打印一些使用帮助和可用的命令:

Scrapy X.Y - no active project

Usage:
  scrapy <command> [options] [args]

Available commands:
  crawl         Run a spider
  fetch         Fetch a URL using the Scrapy downloader
[...]

如果您在Scrapy项目中,则第一行将打印当前活动的项目。 在这个例子中,它是在一个项目之外运行的。 如果在一个项目中运行,它会打印出如下所示的内容:

Scrapy X.Y - project: myproject

Usage:
  scrapy <command> [options] [args]

[...]
创建项目

您通常使用scrapy工具做的第一件事是创建您的Scrapy项目:

scrapy startproject myproject [project_dir]

这将在project_dir目录下创建一个Scrapy项目。 如果未指定project_dir,则project_dir将与myproject相同。

接下来,进入新的项目目录:

cd project_dir

您可以使用scrapy命令管理和控制您的项目。

控制项目

您可以在项目中使用scrapy工具来控制和管理它们。

例如,要创建一个新的Spider:

scrapy genspider mydomain mydomain.com

一些Scrapy命令(如crawl)必须从Scrapy项目中运行。 请参阅下面的命令参考了解哪些命令必须从项目内运行,哪些不是。

另外请记住,某些命令在从项目内部运行时可能会有稍微不同的行为。 例如,如果获取的URL与某个特定的Spider相关联,则获取命令将使用Spider覆盖行为(例如user_agent属性覆盖用户代理)。 这是故意的,因为fetch命令是用来检查Spider是如何下载页面的。

可用的工具命令

本节包含可用内置命令的列表及说明和一些使用示例。 请记住,您始终可以通过运行以下命令获取有关每个命令的更多信息:

scrapy <command> -h

你可以看到所有可用的命令:

scrapy -h

有两种类型的命令,一些只能在Scrapy项目中使用的命令(特定于项目的命令);另外一些可以在没有活动的Scrapy项目的情况下使用(全局命令),尽管它们在项目中运行时可能略有不同因为他们会使用项目重写设置)。

全局命令:

仅限项目的命令:

startproject命令
  • 语法:scrapy startproject <project_name> [project_dir]
  • 需要项目:不需要

project_dir目录下创建一个名为project_name的新Scrapy项目。 如果未指定project_dirproject_dir将与project_name相同。

用法示例:

$ scrapy startproject myproject
genspider
  • 语法:scrapy genspider [-t template] <name> <domain>
  • 需要项目:不需要

如果在项目中调用,则在当前文件夹或当前项目的spiders文件夹中创建一个新Spider。 <name> 参数用来设置Spider的 name, <domain>设置allowed_domainsstart_urls .

用法示例:

$ scrapy genspider -l
Available templates:
  basic
  crawl
  csvfeed
  xmlfeed

$ scrapy genspider example example.com
Created spider 'example' using template 'basic'

$ scrapy genspider -t crawl scrapyorg scrapy.org
Created spider 'scrapyorg' using template 'crawl'

这只是一个用预定义模板创建Spider的快捷命令,并不是创建Spider的唯一方法。 您可以自己创建Spider源代码文件,不使用此命令。

crawl
  • 语法:scrapy crawl <spider>
  • 需要项目:需要

开始使用Spider爬取。

用法示例:

$ scrapy crawl myspider
[ ... myspider starts crawling ... ]
check
  • 语法:scrapy check [-l] <spider>
  • 需要项目:需要

运行约定检查。

用法示例:

$ scrapy check -l
first_spider
  * parse
  * parse_item
second_spider
  * parse
  * parse_item

$ scrapy check
[FAILED] first_spider:parse_item
>>> 'RetailPricex' field is missing

[FAILED] first_spider:parse
>>> Returned 92 requests, expected 0..4
list
  • 语法:scrapy list
  • 需要项目:需要

列出当前项目中所有可用的Spider。 每行输出一个Spider。

用法示例:

$ scrapy list
spider1
spider2
edit
  • 语法:scrapy edit <spider>
  • 需要项目:需要

使用EDITOR环境变量中定义的编辑器编辑给定的蜘蛛,或者(如果未设置)编辑EDITOR设置。

此命令仅作为最常见情况的便捷快捷方式提供,开发人员可以自由选择任何工具或IDE来编写和调试spider。

用法示例:

$ scrapy edit spider1
fetch
  • 语法:scrapy fetch <url>
  • 需要项目:不需要

使用Scrapy下载器下载给定的URL并将内容写到标准输出。

这个命令的有趣之处在于它抓取页面Spider如何下载它。 例如,如果Spider具有覆盖用户代理的USER_AGENT属性,它将使用该属性。

所以这个命令可以用来“看”你的spider如何获取某个页面。

如果在项目之外使用,则不会应用特定Spider的行为,它将使用默认的Scrapy下载器设置。

支持的选项:

  • --spider=SPIDER:绕过spider自动检测并强制使用特定的spider
  • --headers :打印响应的HTTP headers而不是响应的正文
  • --no-redirect:不follow HTTP 3xx重定向(默认是follow它们)

用法示例:

$ scrapy fetch --nolog http://www.example.com/some/page.html
[ ... html content here ... ]

$ scrapy fetch --nolog --headers http://www.example.com/
{'Accept-Ranges': ['bytes'],
 'Age': ['1263   '],
 'Connection': ['close     '],
 'Content-Length': ['596'],
 'Content-Type': ['text/html; charset=UTF-8'],
 'Date': ['Wed, 18 Aug 2010 23:59:46 GMT'],
 'Etag': ['"573c1-254-48c9c87349680"'],
 'Last-Modified': ['Fri, 30 Jul 2010 15:30:18 GMT'],
 'Server': ['Apache/2.2.3 (CentOS)']}
view
  • 语法:scrapy view <url>
  • 需要项目:需要

在浏览器中打开给定的URL,就像Scrapy Spider“看到”的那样。 有时候,Spider看到的网页与普通用户不同,所以这可以用来检查Spider“看到”了什么,并确认它是否是你期望的。

支持的选项:

  • --spider=SPIDER:绕过Spider自动检测并强制使用特定的Spider
  • --no-redirect:不follow HTTP 3xx重定向(默认是follow它们)

用法示例:

$ scrapy view http://www.example.com/some/page.html
[ ... browser starts ... ]
shell
  • 语法:scrapy shell [url]
  • 需要项目:不需要

为指定的URL(如果给定)启动Scrapy shell,如果没有给出URL,则为空。 还支持UNIX风格的本地文件路径,./../前缀的相对路径或绝对路径。 有关更多信息,请参阅Scrapy shell

支持的选项:

  • --spider=SPIDER:绕过Spider自动检测并强制使用特定的Spider
  • -c code:获取shell中的代码,打印结果并退出
  • --no-redirect:不follow HTTP 3xx重定向(默认是follow它们);这只会影响您在命令行上作为参数传递的URL;在shell运行时,fetch(url)默认仍然会follow HTTP重定向。

用法示例:

$ scrapy shell http://www.example.com/some/page.html
[ ... scrapy shell starts ... ]

$ scrapy shell --nolog http://www.example.com/ -c '(response.status, response.url)'
(200, 'http://www.example.com/')

# shell follows HTTP redirects by default
$ scrapy shell --nolog http://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F -c '(response.status, response.url)'
(200, 'http://example.com/')

# you can disable this with --no-redirect
# (only for the URL passed as command line argument)
$ scrapy shell --no-redirect --nolog http://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F -c '(response.status, response.url)'
(302, 'http://httpbin.org/redirect-to?url=http%3A%2F%2Fexample.com%2F')
parse
  • 语法:scrapy parse <url> [options]
  • 需要项目:需要

获取给定的URL,Spider使用--callback选项传递的方法或parse处理它。

支持的选项:

  • --spider=SPIDER:绕过spider自动检测并强制使用特定的spider
  • --a NAME=VALUE:设置spider参数(可以重复)
  • --callback-c:用作spider解析响应的回调方法
  • --meta-m:通过回调请求传回附加请求元标签。 这必须是有效的json字符串。 例如:-meta ='{“foo”:“bar”}'
  • --pipelines:通过pipeline处理item
  • --rules-r:使用CrawlSpider规则来发现用于解析响应的回调(即spider方法)
  • --noitems:不显示被抓到的item
  • --nolinks:不显示提取的链接
  • --nocolour:避免使用pygments对输出着色
  • --depth-d:递归请求后的深度级别(默认值:1)
  • --verbose-v:显示每个深度级别的信息

用法示例:

$ scrapy parse http://www.example.com/ -c parse_item
[ ... scrapy log lines crawling example.com spider ... ]

>>> STATUS DEPTH LEVEL 1 <<<
# Scraped Items  ------------------------------------------------------------
[{'name': u'Example item',
 'category': u'Furniture',
 'length': u'12 cm'}]

# Requests  -----------------------------------------------------------------
[]
settings
  • 语法:scrapy settings [options]
  • 需要项目:需要

获取Scrapy设置值。

如果在项目中使用,它将显示项目设置值,否则将显示默认Scrapy设置值。

用法示例:

$ scrapy settings --get BOT_NAME
scrapybot
$ scrapy settings --get DOWNLOAD_DELAY
0
runspider
  • 语法:scrapy runspider <spider_file.py>
  • 需要项目:不需要

运行一个包含在Python文件中的Spider,而不必创建一个项目。

用法示例:

$ scrapy runspider myspider.py
[ ... spider starts crawling ... ]
version
  • 语法:scrapy version [-v]
  • 需要项目:需要

打印Scrapy版本。 如果与-v一起使用,它还会打印Python,Twisted和Platform信息,这对于错误报告很有用。

bench

0.17版本中的新功能。

  • 语法:scrapy bench
  • 需要项目:不需要

运行一个快速基准测试。 基准

自定义项目命令

您还可以使用COMMANDS_MODULE设置来添加自定义项目命令。 有关如何实现自定义命令的示例,请参阅scrapy/commands中的Scrapy命令。

COMMANDS_MODULE

默认:''(空字符串)

用于查找自定义Scrapy命令的模块。 这用于为您的Scrapy项目添加自定义命令。

例:

COMMANDS_MODULE = 'mybot.commands'
通过setup.py入口点注册命令

注意

这是一个实验性功能,请谨慎使用。

您还可以通过在setup.py库文件的入口点添加scrapy.commands部分,从外部库添加Scrapy命令。

以下示例添加了my_command命令:

from setuptools import setup, find_packages

setup(name='scrapy-mymodule',
  entry_points={
    'scrapy.commands': [
      'my_command=my_scrapy_module.commands:MyCommand',
    ],
  },
 )

Spiders

Spider是定义了如何抓取某个站点(或一组站点)的类,包括如何执行爬取(即follow链接)以及如何从其页面中提取结构化数据(即抓取item)。 换句话说,Spider是您定义的为抓取和解析特定网站(在某些情况下是一组网站)页面的自定义行为的地方。

对于蜘蛛来说,抓取周期会经历这样一些事情:

  1. 首先生成抓取第一个URL的初始请求,为这些请求下载的响应来调用指定回调函数。

    通过调用start_requests()方法来获得第一个执行请求,该方法默认为start_urls中指定的URL生成Request,parse方法作为请求的回调函数。

  2. 在回调函数中,解析响应(网页)并返回带有提取数据的字典,Item对象,Request对象或这些对象的迭代。 这些请求还将包含一个回调(可能是相同的),然后由Scrapy下载,通过指定的回调处理它们的响应。

  3. 在回调函数中,通常使用Selectors(您也可以使用BeautifulSoup,lxml或您喜欢的任何机制)解析页面内容,并使用解析的数据生成Item。

  4. 最后,从spider返回的Item通常会被持久化到数据库(在某些Item Pipeline中)或使用Feed exports写入文件。

尽管这个周期适用于(或多或少)任何类型的Spider,但为了不同的目的,有不同类型的默认Spider捆绑到Scrapy中。 我们将在这里讨论这些类型。

scrapy.Spider

class scrapy.spiders.Spider

这是最简单的Spider,也是其他Spider必须继承的(包括与Scrapy捆绑在一起的Spider,以及自己写的Spider)。 它不提供任何特殊功能。 它只是提供一个默认的start_requests()实现,从start_urls spider属性发送请求,并为每个结果响应调用spider的parse 方法。

name

一个字符串,它定义了这个Spider的名字。 Scrapy通过Spider名称定位(并实例化)Spider,因此它必须是唯一的。 然而,没有什么能够阻止你实例化同一个蜘蛛的多个实例。 这是最重要的Spider属性,它是必需的。

如果Spider爬取单独一个域名,通常的做法是使用域名命名Spider,无论是否使用TLD 因此,例如,抓取 mywebsite.com的Spider通常会被命名为mywebsite

注意

在Python 2中,这只能是ASCII。

allowed_domains

包含允许Spider抓取域的可选字符串列表。 如果启用了OffsiteMiddleware,则不会follow不属于此列表中指定的域名(或其子域)的URL的请求。

假设您的目标网址为https://www.example.com/1.html,然后将'example.com'添加到列表中。

start_urls

当没有特别指定的网址时,Spider将从哪个网址开始抓取的网址列表。 所以,下载的第一个页面将在这里列出。 随后的URL将从包含在起始URL中的数据中连续生成。

custom_settings

运行Spider时将从项目范围配置覆盖的设置字典。 它必须被定义为类属性,设置在实例化之前被更新。

有关可用内置设置的列表,请参阅:内置设置参考

crawler

该属性在初始化类后由from_crawler()类方法设置,并链接到此spider实例绑定的Crawler对象。

Crawler在项目中封装了大量组件,以便进行单一入口访问(例如扩展,中间件,信号管理器等)。 请参阅Crawler API以了解更多关于它们的信息。

settings

运行这个Spider的配置。 这是一个 Settings 实例,请参阅 Settings 解有关此主题的详细介绍。

logger

Python logger是用Spider的 name 创建的。 您可以按照记录Spider中所述的方式使用它发送日志消息。

from_crawler(crawler, *args, **kwargs)

这是Scrapy用来创建Spider的类方法。

您可能不需要直接覆盖它,因为它默认实现是充当__init__()方法的代理,用给定参数args和命名参数 kwargs

尽管如此,该方法会在新实例中设置crawlersettings属性,以便稍后可以在Spider代码中访问它们。

Parameters:
  • crawlerCrawler实例) - Spider将被绑定到的爬虫
  • argslist) -传递给__init__()方法的参数
  • kwargsdict) - 传递给__ init __()方法的关键字参数
start_requests()

此方法必须为Spider返回可迭代的初始请求。 当Spider开始抓取时它会被Scrapy调用。 Scrapy只调用它一次,所以将start_requests()作为发生器是安全的。

默认实现为start_urls中的每个网址生成Request(url, dont_filter=True)

如果您想更改用于开始抓取域的请求,需要覆盖此方法。 例如,如果您需要使用POST请求登录,则可以执行以下操作:

class MySpider(scrapy.Spider):
    name = 'myspider'

    def start_requests(self):
        return [scrapy.FormRequest("http://www.example.com/login",
                                   formdata={'user': 'john', 'pass': 'secret'},
                                   callback=self.logged_in)]

    def logged_in(self, response):
        # 在这里你用另外一个回调函数提取follow的链接
        # 并返回每个请求
        pass
parse(response)

这是Scrapy用来处理下载响应的默认回调,当请求没有指定回调时。

parse方法负责处理响应并返回抓取的数据和/或更多的URL。 其他请求回调与Spider类具有相同的要求。

该方法以及任何其他Request回调一样,必须返回一个可迭代的Request和/或字典或Item对象。

Parameters: response response ) - 解析的响应
log(message[, level, component])

通过Spider的logger发送日志消息的包装器,保持向后兼容性。 有关更多信息,请参阅Spider日志记录

closed(reason)

当Spider关闭时调用。 此方法为spider_closed信号提供了signals.connect()的快捷方式。

我们来看一个例子:

import scrapy


class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = [
        'http://www.example.com/1.html',
        'http://www.example.com/2.html',
        'http://www.example.com/3.html',
    ]

    def parse(self, response):
        self.logger.info('A response from %s just arrived!', response.url)

从单个回调中返回多个请求和Item:

import scrapy

class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = [
        'http://www.example.com/1.html',
        'http://www.example.com/2.html',
        'http://www.example.com/3.html',
    ]

    def parse(self, response):
        for h3 in response.xpath('//h3').extract():
            yield {"title": h3}

        for url in response.xpath('//a/@href').extract():
            yield scrapy.Request(url, callback=self.parse)

您可以直接使用start_requests()来代替start_urls; 如果想为数据提供更多结构,您可以使用Item

import scrapy
from myproject.items import MyItem

class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['example.com']

    def start_requests(self):
        yield scrapy.Request('http://www.example.com/1.html', self.parse)
        yield scrapy.Request('http://www.example.com/2.html', self.parse)
        yield scrapy.Request('http://www.example.com/3.html', self.parse)

    def parse(self, response):
        for h3 in response.xpath('//h3').extract():
            yield MyItem(title=h3)

        for url in response.xpath('//a/@href').extract():
            yield scrapy.Request(url, callback=self.parse)

Spider参数

Spider可以接收修改其行为的参数。 Spider参数的一些常见用途是定义起始URL或将爬虫限制到站点的某些部分,但它们可用于配置Spider的任何功能。

使用crawl命令-a选项传递Spider参数。 例如:

scrapy crawl myspider -a category=electronics

Spider可以在它的__init__方法中访问参数:

import scrapy

class MySpider(scrapy.Spider):
    name = 'myspider'

    def __init__(self, category=None, *args, **kwargs):
        super(MySpider, self).__init__(*args, **kwargs)
        self.start_urls = ['http://www.example.com/categories/%s' % category]
        # ...

默认的__init__方法将获取所有Spider参数并将其作为属性复制到Spider中。 上面的例子也可以写成如下:

import scrapy

class MySpider(scrapy.Spider):
    name = 'myspider'

    def start_requests(self):
        yield scrapy.Request('http://www.example.com/categories/%s' % self.category)

请注意,Spider参数只能是字符串。 Spider不会自行解析。 如果要从命令行设置start_urls属性,则必须使用类似ast.literal_evaljson.loads的方式将它解析为列表然后将其设置为属性。 否则,将导致迭代start_urls字符串(一个非常常见的python陷阱),使每个字符被视为一个单独的url。

有效的用例是设置由HttpAuthMiddleware使用的http认证凭证或由UserAgentMiddleware使用的用户代理:

scrapy crawl myspider -a http_user=myuser -a http_pass=mypassword -a user_agent=mybot

Spider参数也可以通过Scrapyd的schedule.json API传递。 参见Scrapyd文档

通用Spider

Scrapy附带了一些有用的通用Spider,您可以继承它们。 它们的目标是为一些常见的抓取案例提供方便的功能,例如以特定规则follow网站上的所有链接,从Sitemaps抓取或解析XML/CSV feed。

对于以下蜘蛛中使用的示例,我们假设您有一个项目,其中包含在myproject.items模块中声明的TestItem

import scrapy

class TestItem(scrapy.Item):
    id = scrapy.Field()
    name = scrapy.Field()
    description = scrapy.Field()
CrawlSpider
class scrapy.spiders.CrawlSpider

这是抓取常规网站最常用的Spider,因为它提供了一个通过定义一组规则来follow链接的便捷机制。 它可能不是最适合您的特定网站或项目的,但它对于多种情况是足够通用的,所以您可以从它开始并根据需要覆盖它以获得更多自定义功能,或者只是实现您自己的Spider。

除了从Spider继承的属性(必须指定)之外,该类还支持一个新的属性:

rules

这是一个(或多个)Rule对象的列表。 每个Rule定义用于抓取站点的特定行为。 Rule对象如下所述。 如果多个规则匹配相同的链接,则会根据它在此属性中定义的顺序使用第一个规则。

这个Spider也暴露了一个可覆盖的方法:

parse_start_url(response)

这个方法被start_urls响应调用。 它允许解析初始响应,并且必须返回一个Item对象,一个Request对象或包含它们中的任何一个的迭代器。

抓取规则
class scrapy.spiders.Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)

link_extractor是一个链接提取器对象,它定义了如何从每个已爬网页中提取链接。

callback是可调用的或字符串(在这种情况下,将使用具有该名称的Spider对象的方法)被使用指定的link_extractor提取的链接调用。 这个回调接收一个响应作为它的第一个参数,并且必须返回一个包含Item和/或Request对象(或者它们的任何子类)的列表。

警告

编写爬虫rule时,避免使用parse作为回调,因为CrawlSpider使用parse方法来实现其逻辑。 因此,如果您重写parse方法,抓取Spider将不再起作用。

cb_kwargs是一个包含要传递给回调函数的关键字参数字典。

follow是一个布尔值,它指定是否应该使用此规则提取的每个响应follow链接。 如果callback为None ,follow默认为True,否则follow默认为False

process_links是可调用的或字符串(在这种情况下,将使用具有该名称的spider对象的方法),将被使用指定的 link_extractor 的响应提取到的链接调用。 这主要用于过滤目的。

process_request是一个可调用的或一个字符串(在这种情况下,将使用具有该名称的spider对象的方法),这个方法将被该规则提取的每个请求调用,并且必须返回一个请求或None(用于过滤请求)。

CrawlSpider示例

现在我们来看一个带有规则的示例CrawlSpider:

import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class MySpider(CrawlSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com']

    rules = (
        # 提取匹配'category.php'的链接(但不匹配'subsection.php')
        # 然后follow这些链接(因为没有callback参数意味着默认follow=True)
        Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),

        # 提取匹配'item.php'的链接,然后用Spider的parse_item解析它们
        Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'),
    )

    def parse_item(self, response):
        self.logger.info('Hi, this is an item page! %s', response.url)
        item = scrapy.Item()
        item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
        item['name'] = response.xpath('//td[@id="item_name"]/text()').extract()
        item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
        return item

这个Spider将开始抓取example.com的主页,收集category链接和item链接,用parse_item方法解析后者。 对于每个Item响应,将使用XPath从HTML中提取一些数据填充Item

XMLFeedSpider
class scrapy.spiders.XMLFeedSpider

XMLFeedSpider旨在通过遍历特定节点名称来解析XML提要。 迭代器可以从iternodesxmlhtml中选择。 出于性能原因,建议使用iternodes迭代器,因为xmlhtml迭代器会一次生成整个DOM以解析它。 但是,使用html作为迭代器时,可能会在解析具有错误标记的XML时很有用。

要设置迭代器和标签名称,您必须定义以下类属性:

iterator

一个定义要使用的迭代器的字符串。 它可以是:

  • 'iternodes' - 一个基于正则表达式的快速迭代器
  • 'html' - 使用Selector的迭代器。 请注意,这个迭代器使用DOM解析,必须把所有DOM加载到内存中,当信息量过大时可能会引起问题
  • 'xml' - 使用Selector的迭代器。 请注意,这个迭代器使用DOM解析,必须把所有DOM加载到内存中,当信息量过大时可能会引起问题

默认为:'iternodes'

itertag

一个字符串,其中包含要迭代的节点(或元素)的名称。 例:

itertag = 'product'
namespaces

定义spider将要处理的文档中可用命名空间的(prefix, uri)元组列表。 prefixuri将用于使用register_namespace()方法自动注册名称空间。

然后可以在itertag属性中指定具有名称空间的节点。

例:

class YourSpider(XMLFeedSpider):

    namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')]
    itertag = 'n:url'
    # ...

除了这些新的属性外,这个Spider还有以下可覆盖的方法:

adapt_response(response)

在Spider开始分析它之前,一旦收到响应就传到Spider中间件中。 它可以用来在解析响应之前修改响应主体。 这个方法接收一个响应,并返回一个响应(它可能是相同的或另一个)。

parse_node(response, selector)

对于与提供的标签名称匹配的节点(itertag),将调用此方法。 接收每个节点的响应和Selector 覆盖此方法是强制性的。 否则,你的Spider将无法工作。 该方法必须返回一个Item对象或一个Request对象或包含它们的迭代器。

process_results(response, results)

该方法针对Spider所返回的每个结果(Item或请求)进行调用,并且它在将结果返回给框架核心之前执行所需的最后一次处理,例如设置Item ID。 它接收结果列表和这些结果的源响应。 它必须返回结果列表(Item或请求)。

XMLFeedSpider示例

这些Spider很容易使用,让我们来看一个例子:

from scrapy.spiders import XMLFeedSpider
from myproject.items import TestItem

class MySpider(XMLFeedSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com/feed.xml']
    iterator = 'iternodes'  # 实际上这个是非必须的,因为iternodes是默认值
    itertag = 'item'

    def parse_node(self, response, node):
        self.logger.info('Hi, this is a <%s> node!: %s', self.itertag, ''.join(node.extract()))

        item = TestItem()
        item['id'] = node.xpath('@id').extract()
        item['name'] = node.xpath('name').extract()
        item['description'] = node.xpath('description').extract()
        return item

基本上我们做的是创建一个Spider,它从给定的start_urls下载一个feed,然后迭代它的每个item标签,打印出来并存储一些随机数据到Item中。

CSVFeedSpider
class scrapy.spiders.CSVFeedSpider

这个Spider与XMLFeedSpider非常相似,只是它遍历行而不是节点。 每次迭代调用的方法是parse_row()

delimiter

包含CSV文件中每个字段的分隔符的字符串,默认为','(逗号)。

quotechar

包含CSV文件中每个字段的外围字符的字符串,默认为'“'(引号)。

headers

CSV文件中列名称的列表。

parse_row(response, row)

接收一个响应和一个以被提供(或被检测)的CSV文件头作为关键字的字典(代表每行)。 这个Spider还有机会覆盖用于预处理和后置处理目的的adapt_responseprocess_results方法。

CSVFeedSpider示例

我们来看一个与前一个类似的例子,但是使用CSVFeedSpider

from scrapy.spiders import CSVFeedSpider
from myproject.items import TestItem

class MySpider(CSVFeedSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com/feed.csv']
    delimiter = ';'
    quotechar = "'"
    headers = ['id', 'name', 'description']

    def parse_row(self, response, row):
        self.logger.info('Hi, this is a row!: %r', row)

        item = TestItem()
        item['id'] = row['id']
        item['name'] = row['name']
        item['description'] = row['description']
        return item
SitemapSpider
class scrapy.spiders.SitemapSpider

SitemapSpider允许您通过使用Sitemaps发现网址来抓取网站。

它支持嵌套站点地图并从robots.txt中发现站点地图网址。

sitemap_urls

指向您要抓取的网址的站点地图的网址列表。

您也可以指向一个robots.txt,将其解析并从中提取站点地图网址。

sitemap_rules

一个元组列表(regex, callback) ,其中:

  • regex是一个正则表达式,用于匹配从站点地图提取的网址。 regex可以是字符串或编译的正则表达式对象。
  • callback 是用于处理匹配正则表达式的url的回调。 callback可以是一个字符串(指定spider的方法名)或可调用的。

例如:

sitemap_rules = [('/product/', 'parse_product')]

规则按顺序应用,只有第一个匹配的将被使用。

如果你省略了这个属性,所有在站点地图中找到的URL都会用parse回调进行处理。

sitemap_follow

应该follow的站点地图的正则表达式的列表。 这仅适用于使用了指向其他站点地图文件的站点地图索引文件的站点。

默认情况下,follow所有站点地图。

指定是否应该follow一个url的备用链接。 这些是在同一个url块中传递的另一种语言的同一网站的链接。

例如:

<url>
    <loc>http://example.com/</loc>
    <xhtml:link rel="alternate" hreflang="de" href="http://example.com/de"/>
</url>

通过设置sitemap_alternate_links,将检索两个网址。 禁用sitemap_alternate_links时,只会检索http://example.com/

sitemap_alternate_links默认禁用。

SitemapSpider示例

最简单的例子:使用parse回调处理通过站点地图发现的所有网址:

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/sitemap.xml']

    def parse(self, response):
        pass # ... scrape item here ...

使用指定的回调处理某些URL,对其他URL使用不同的回调:

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/sitemap.xml']
    sitemap_rules = [
        ('/product/', 'parse_product'),
        ('/category/', 'parse_category'),
    ]

    def parse_product(self, response):
        pass # ... scrape product ...

    def parse_category(self, response):
        pass # ... scrape category ...

followrobots.txt文件中定义的站点地图,并且只follow链接中包含/ sitemap_shop的站点地图:

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/robots.txt']
    sitemap_rules = [
        ('/shop/', 'parse_shop'),
    ]
    sitemap_follow = ['/sitemap_shops']

    def parse_shop(self, response):
        pass # ... scrape shop here ...

将SitemapSpider与其他网址来源相结合:

from scrapy.spiders import SitemapSpider

class MySpider(SitemapSpider):
    sitemap_urls = ['http://www.example.com/robots.txt']
    sitemap_rules = [
        ('/shop/', 'parse_shop'),
    ]

    other_urls = ['http://www.example.com/about']

    def start_requests(self):
        requests = list(super(MySpider, self).start_requests())
        requests += [scrapy.Request(x, self.parse_other) for x in self.other_urls]
        return requests

    def parse_shop(self, response):
        pass # ... scrape shop here ...

    def parse_other(self, response):
        pass # ... scrape other here ...

选择器

在抓取网页时,需要执行的最常见任务是从HTML源中提取数据。 有几个库可以实现这一点:

  • BeautifulSoup是Python程序员中非常流行的网络抓取库,它基于HTML代码的结构构建Python对象,还能适当地处理损坏的标记,但它有一个缺点:速度很慢。
  • lxml是一个基于ElementTree的pythonic API的XML解析库(它也解析HTML)。 (lxml不是Python标准库的一部分。)

Scrapy自带提取数据的机制。 它们被称为选择器,因为它们“选择”由XPathCSS表达式指定的HTML文档的某些部分。

XPath是用于选择XML文档中的节点的语言,也可以用于HTML。 CSS是一种将样式应用于HTML文档的语言。 它定义选择器将这些样式与特定的HTML元素相关联。

Scrapy选择器是建立在lxml库上的,这意味着它们在速度和解析精度上非常相似。

这个页面解释了选择器是如何工作的,并描述了它们小且简单的API,不像lxml API那样大,因为lxml库除了选择标记文件还可以用于许多其他任务,。

有关选择器API的完整参考,请参阅选择器参考

使用选择器

构造选择器

Scrapy选择器是被构造用来传递textTextResponse对象的Selector类的实例。 它会根据输入类型(XML vs HTML)自动选择最佳的解析规则:

>>> from scrapy.selector import Selector
>>> from scrapy.http import HtmlResponse

从文本构建:

>>> body = '<html><body><span>good</span></body></html>'
>>> Selector(text=body).xpath('//span/text()').extract()
[u'good']

从响应构建:

>>> response = HtmlResponse(url='http://example.com', body=body)
>>> Selector(response=response).xpath('//span/text()').extract()
[u'good']

为方便起见,响应对象在.selector属性上显示选择器,完全可以在可能的情况下使用此快捷方式:

>>> response.selector.xpath('//span/text()').extract()
[u'good']
使用选择器

为了解释如何使用选择器,我们将使用Scrapy文档服务器中的Scrapy shell(提供交互式测试)和一个示例页面:

这是它的HTML代码:

<html>
 <head>
  <base href='http://example.com/' />
  <title>Example website</title>
 </head>
 <body>
  <div id='images'>
   <a href='image1.html'>Name: My image 1 <br /><img src='image1_thumb.jpg' /></a>
   <a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' /></a>
   <a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' /></a>
   <a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' /></a>
   <a href='image5.html'>Name: My image 5 <br /><img src='image5_thumb.jpg' /></a>
  </div>
 </body>
</html>

首先,我们打开shell:

scrapy shell https://doc.scrapy.org/en/latest/_static/selectors-sample1.html

然后,在加载shell之后,您得到的响应将成为response shell变量,响应附加的选择器为response.selector属性。

由于我们正在处理HTML,选择器将自动使用HTML解析器。

因此,通过查看该页面的HTML代码,我们构建一个用于选择标题标签内的文本的XPath:

>>> response.selector.xpath('//title/text()')
[<Selector (text) xpath=//title/text()>]

使用XPath和CSS查询响应非常常见,以至于响应包含两个快捷途径:response.xpath()response.css()

>>> response.xpath('//title/text()')
[<Selector (text) xpath=//title/text()>]
>>> response.css('title::text')
[<Selector (text) xpath=//title/text()>]

如您所见,.xpath().css()方法返回一个SelectorList实例,该实例是新选择器的列表。 该API可用于快速选择嵌套数据:

>>> response.css('img').xpath('@src').extract()
[u'image1_thumb.jpg',
 u'image2_thumb.jpg',
 u'image3_thumb.jpg',
 u'image4_thumb.jpg',
 u'image5_thumb.jpg']

要实际提取文本数据,您必须调用选择器.extract()方法,如下所示:

>>> response.xpath('//title/text()').extract()
[u'Example website']

如果你只想提取第一个匹配的元素,你可以调用选择器.extract_first()

>>> response.xpath('//div[@id="images"]/a/text()').extract_first()
u'Name: My image 1 '

如果找不到元素,它将返回None

>>> response.xpath('//div[@id="not-exists"]/text()').extract_first() is None
True

默认返回值可以作为参数提供,用来代替None

>>> response.xpath('//div[@id="not-exists"]/text()').extract_first(default='not-found')
'not-found'

请注意,CSS选择器可以使用CSS3伪元素选择文本或属性节点:

>>> response.css('title::text').extract()
[u'Example website']

现在我们要获取基本URL和一些图像链接:

>>> response.xpath('//base/@href').extract()
[u'http://example.com/']

>>> response.css('base::attr(href)').extract()
[u'http://example.com/']

>>> response.xpath('//a[contains(@href, "image")]/@href').extract()
[u'image1.html',
 u'image2.html',
 u'image3.html',
 u'image4.html',
 u'image5.html']

>>> response.css('a[href*=image]::attr(href)').extract()
[u'image1.html',
 u'image2.html',
 u'image3.html',
 u'image4.html',
 u'image5.html']

>>> response.xpath('//a[contains(@href, "image")]/img/@src').extract()
[u'image1_thumb.jpg',
 u'image2_thumb.jpg',
 u'image3_thumb.jpg',
 u'image4_thumb.jpg',
 u'image5_thumb.jpg']

>>> response.css('a[href*=image] img::attr(src)').extract()
[u'image1_thumb.jpg',
 u'image2_thumb.jpg',
 u'image3_thumb.jpg',
 u'image4_thumb.jpg',
 u'image5_thumb.jpg']
嵌套选择器

选择方法(.xpath().css())会返回相同类型的选择器列表,因此您也可以对这些选择器调用选择方法。 这是一个例子:

>>> links = response.xpath('//a[contains(@href, "image")]')
>>> links.extract()
[u'<a href="image1.html">Name: My image 1 <br><img src="image1_thumb.jpg"></a>',
 u'<a href="image2.html">Name: My image 2 <br><img src="image2_thumb.jpg"></a>',
 u'<a href="image3.html">Name: My image 3 <br><img src="image3_thumb.jpg"></a>',
 u'<a href="image4.html">Name: My image 4 <br><img src="image4_thumb.jpg"></a>',
 u'<a href="image5.html">Name: My image 5 <br><img src="image5_thumb.jpg"></a>']

>>> for index, link in enumerate(links):
...     args = (index, link.xpath('@href').extract(), link.xpath('img/@src').extract())
...     print 'Link number %d points to url %s and image %s' % args

Link number 0 points to url [u'image1.html'] and image [u'image1_thumb.jpg']
Link number 1 points to url [u'image2.html'] and image [u'image2_thumb.jpg']
Link number 2 points to url [u'image3.html'] and image [u'image3_thumb.jpg']
Link number 3 points to url [u'image4.html'] and image [u'image4_thumb.jpg']
Link number 4 points to url [u'image5.html'] and image [u'image5_thumb.jpg']
使用具有正则表达式的选择器

Selector也有一个使用正则表达式提取数据的.re()方法。 然而,与.xpath().css()方法不同,.re()返回unicode字符串列表。 所以你不能构造嵌套的.re()调用。

以下是一个用于从HTML代码中提取图像名称的示例:

>>> response.xpath('//a[contains(@href, "image")]/text()').re(r'Name:\s*(.*)')
[u'My image 1',
 u'My image 2',
 u'My image 3',
 u'My image 4',
 u'My image 5']

.re()有一个额外的类似.extract_first()辅助,名为.re_first() 使用它只提取第一个匹配的字符串:

>>> response.xpath('//a[contains(@href, "image")]/text()').re_first(r'Name:\s*(.*)')
u'My image 1'
使用相对XPaths

请记住,如果您正在嵌套选择器并使用以/开头的XPath,则该XPath对文档是绝对的,而不是相对于它来自的Selector

例如,假设你想提取<div>中所有的<p> 元素。 首先,你获取所有的<div>元素:

>>> divs = response.xpath('//div')

起初,您可能会尝试使用以下方法,这是错误的,因为它实际上会提取文件中所有<p>,不只是<div>元素中的。

>>> for p in divs.xpath('//p'):  # 这是错的 - 获取整个文件的<p>
...     print p.extract()

这是做到这一点的正确方法(注意.//p XPath的点前缀):

>>> for p in divs.xpath('.//p'):  # 提取内部所有的<p>
...     print p.extract()

另一个常见的情况是提取所有直接的<p>子节点:

>>> for p in divs.xpath('p'):
...     print p.extract()

有关相对XPath的更多详细信息,请参阅XPath规范中的位置路径部分。

XPath表达式中的变量

XPath允许使用$ somevariable语法引用XPath表达式中的变量。 这有些类似于SQL中的参数化查询或预先声明,您可以用占位符替换查询中的某些参数像是 ?,然后用查询传递的值替换它们。

下面是一个基于其“id”属性值匹配元素的示例,不用对其进行硬编码(预先给定):

>>> # `$val` 用在表达式中, `val`参数需要被传递
>>> response.xpath('//div[@id=$val]/a/text()', val='images').extract_first()
u'Name: My image 1 '

这里有另一个例子,找到有5个<a>子元素的<div>元素的“id”属性(在这里我们传递一个整数值5):

>>> response.xpath('//div[count(a)=$cnt]/@id', cnt=5).extract_first()
u'images'

调用.xpath()时,所有变量引用都必须具有绑定值(否则您将得到 ValueError: XPath error: 异常)。 这是通过根据需要传递许多命名参数来完成的。

parsel,强大的Scrapy选择器库,有更多关于XPath变量的细节和示例。

使用EXSLT扩展名

构建在lxml之上,Scrapy选择器还支持一些EXSLT扩展,并附带这些预先注册的名称空间以用于XPath表达式中:

prefix namespace usage
re http://exslt.org/regular-expressions regular expressions
set http://exslt.org/sets set manipulation
正则表达式

例如,当XPath的starts-with()contains()功能不足时,test()函数可能非常有用。

使用以数字结尾的“class”属性选择列表项中链接的示例:

>>> from scrapy import Selector
>>> doc = """
... <div>
...     <ul>
...         <li class="item-0"><a href="link1.html">first item</a></li>
...         <li class="item-1"><a href="link2.html">second item</a></li>
...         <li class="item-inactive"><a href="link3.html">third item</a></li>
...         <li class="item-1"><a href="link4.html">fourth item</a></li>
...         <li class="item-0"><a href="link5.html">fifth item</a></li>
...     </ul>
... </div>
... """
>>> sel = Selector(text=doc, type="html")
>>> sel.xpath('//li//@href').extract()
[u'link1.html', u'link2.html', u'link3.html', u'link4.html', u'link5.html']
>>> sel.xpath('//li[re:test(@class, "item-\d$")]//@href').extract()
[u'link1.html', u'link2.html', u'link4.html', u'link5.html']
>>>

警告

C语言库libxslt本身不支持EXSLT正则表达式,所以lxml实现时对Python的re模块使用了钩子。 因此,在XPath表达式中使用正则表达式函数可能会增加一点性能损失。

设置操作

例如,在提取文本元素之前,这些操作可以方便地排除文档树的部分内容。

使用itemscopes组和相应的itemprops提取微数据(从http://schema.org/Product取得的样本内容)示例:

>>> doc = """
... <div itemscope itemtype="http://schema.org/Product">
...   <span itemprop="name">Kenmore White 17" Microwave</span>
...   <img src="kenmore-microwave-17in.jpg" alt='Kenmore 17" Microwave' />
...   <div itemprop="aggregateRating"
...     itemscope itemtype="http://schema.org/AggregateRating">
...    Rated <span itemprop="ratingValue">3.5</span>/5
...    based on <span itemprop="reviewCount">11</span> customer reviews
...   </div>
...
...   <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
...     <span itemprop="price">$55.00</span>
...     <link itemprop="availability" href="http://schema.org/InStock" />In stock
...   </div>
...
...   Product description:
...   <span itemprop="description">0.7 cubic feet countertop microwave.
...   Has six preset cooking categories and convenience features like
...   Add-A-Minute and Child Lock.</span>
...
...   Customer reviews:
...
...   <div itemprop="review" itemscope itemtype="http://schema.org/Review">
...     <span itemprop="name">Not a happy camper</span> -
...     by <span itemprop="author">Ellie</span>,
...     <meta itemprop="datePublished" content="2011-04-01">April 1, 2011
...     <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
...       <meta itemprop="worstRating" content = "1">
...       <span itemprop="ratingValue">1</span>/
...       <span itemprop="bestRating">5</span>stars
...     </div>
...     <span itemprop="description">The lamp burned out and now I have to replace
...     it. </span>
...   </div>
...
...   <div itemprop="review" itemscope itemtype="http://schema.org/Review">
...     <span itemprop="name">Value purchase</span> -
...     by <span itemprop="author">Lucas</span>,
...     <meta itemprop="datePublished" content="2011-03-25">March 25, 2011
...     <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
...       <meta itemprop="worstRating" content = "1"/>
...       <span itemprop="ratingValue">4</span>/
...       <span itemprop="bestRating">5</span>stars
...     </div>
...     <span itemprop="description">Great microwave for the price. It is small and
...     fits in my apartment.</span>
...   </div>
...   ...
... </div>
... """
>>> sel = Selector(text=doc, type="html")
>>> for scope in sel.xpath('//div[@itemscope]'):
...     print "current scope:", scope.xpath('@itemtype').extract()
...     props = scope.xpath('''
...                 set:difference(./descendant::*/@itemprop,
...                                .//*[@itemscope]/*/@itemprop)''')
...     print "    properties:", props.extract()
...     print

current scope: [u'http://schema.org/Product']
    properties: [u'name', u'aggregateRating', u'offers', u'description', u'review', u'review']

current scope: [u'http://schema.org/AggregateRating']
    properties: [u'ratingValue', u'reviewCount']

current scope: [u'http://schema.org/Offer']
    properties: [u'price', u'availability']

current scope: [u'http://schema.org/Review']
    properties: [u'name', u'author', u'datePublished', u'reviewRating', u'description']

current scope: [u'http://schema.org/Rating']
    properties: [u'worstRating', u'ratingValue', u'bestRating']

current scope: [u'http://schema.org/Review']
    properties: [u'name', u'author', u'datePublished', u'reviewRating', u'description']

current scope: [u'http://schema.org/Rating']
    properties: [u'worstRating', u'ratingValue', u'bestRating']

>>>

在这里,我们首先迭代itemscope元素,在每个元素中查找所有itemprops元素并排除那些位于另一个itemscope内的元素。

一些XPath提示

这里有一些在Scrapy选择器中使用XPath时可能会有用的提示,这些提示基于ScrapingHub博客的帖子 如果您还不太熟悉XPath,那么您可能需要先看看这个XPath教程

在条件中使用文本节点

当您需要将文本内容用作XPath字符串函数的参数时,请避免使用.//text()并仅使用 . 代替。

这是因为表达式.//text()会产生一组文本元素 - 节点集 当一个节点集被转换成一个字符串,作为参数传递给一个字符串函数如contains()starts-with()时,返回结果将是文本的第一个元素。

例:

>>> from scrapy import Selector
>>> sel = Selector(text='<a href="#">Click here to go to the <strong>Next Page</strong></a>')

节点集转换为字符串:

>>> sel.xpath('//a//text()').extract() # take a peek at the node-set
[u'Click here to go to the ', u'Next Page']
>>> sel.xpath("string(//a[1]//text())").extract() # convert it to string
[u'Click here to go to the ']

然而,将一个节点转换为一个字符串,将会获得它自身加上所有后代的文本:

>>> sel.xpath("//a[1]").extract() # select the first node
[u'<a href="#">Click here to go to the <strong>Next Page</strong></a>']
>>> sel.xpath("string(//a[1])").extract() # convert it to string
[u'Click here to go to the Next Page']

因此,在这种情况下,使用.//text()节点集不会选择任何内容:

>>> sel.xpath("//a[contains(.//text(), 'Next Page')]").extract()
[]

但使用 . 代表节点是可行的:

>>> sel.xpath("//a[contains(., 'Next Page')]").extract()
[u'<a href="#">Click here to go to the <strong>Next Page</strong></a>']
注意//node[1]和(//node)[1]之间的区别

//node[1] 选择所有在它们各自父项下第一个节点。

(// node)[1]选择文档中的所有节点,然后仅获取它们中的第一个节点。

例:

>>> from scrapy import Selector
>>> sel = Selector(text="""
....:     <ul class="list">
....:         <li>1</li>
....:         <li>2</li>
....:         <li>3</li>
....:     </ul>
....:     <ul class="list">
....:         <li>4</li>
....:         <li>5</li>
....:         <li>6</li>
....:     </ul>""")
>>> xp = lambda x: sel.xpath(x).extract()

获取所有的第一个<li>,无论它们的父母是什么:

>>> xp("//li[1]")
[u'<li>1</li>', u'<li>4</li>']

获取整个文档的<li>元素:

>>> xp("(//li)[1]")
[u'<li>1</li>']

获取所有<ul>元素中的第一个<li>子元素:

>>> xp("//ul/li[1]")
[u'<li>1</li>', u'<li>4</li>']

这将得到整个文档中第一个在<ul>父元素中的<li>元素:

>>> xp("(//ul/li)[1]")
[u'<li>1</li>']
按类查询时,请考虑使用CSS

由于一个元素可以包含多个CSS类,因此按类选择元素的XPath方法相当冗长:

*[contains(concat(' ', normalize-space(@class), ' '), ' someclass ')]

如果你使用@class='someclass',你最终可能会丢失具有其他类的元素,如果你只是使用contains(@class, 'someclass')来弥补这一点,那么当它们有不同的类名称来共享字符串someclass时,您可能会得到比你想要的更多的元素。

事实证明,Scrapy选择器允许您链接选择器,所以大多数情况下,您可以使用CSS按类选择,然后在需要时切换到XPath:

>>> from scrapy import Selector
>>> sel = Selector(text='<div class="hero shout"><time datetime="2014-07-23 19:00">Special date</time></div>')
>>> sel.css('.shout').xpath('./time/@datetime').extract()
[u'2014-07-23 19:00']

这比使用上面使用的冗长的XPath技巧更清晰。 记得使用 . 在随后的XPath表达式中。

内置选择器参考

选择器对象
class scrapy.selector.Selector(response=None, text=None, type=None)

Selector的实例是选择对响应内容某一部分的封装。

response是将被用于选择和提取数据的HtmlResponseXmlResponse对象.

text是unicode字符串或utf-8编码文本,当response 不可用时使用。 同时使用textresponse是未定义的行为。

type定义了选择器类型,它可以是“html”“xml”None(默认)。

如果typeNone,那么选择器将自动根据response类型选择最佳类型(请参见下文),如果与text一起使用则默认为“html “

如果typeNone且传递了response,则从响应类型推断选择器类型关系如下:

否则,如果设置了type,则选择器类型将被强制且不会进行检测。

xpath(query)

找到与xpath query匹配的节点,并将结果作为带有所有展平元素的SelectorList实例返回. 列表元素也实现Selector接口。

query是一个包含要应用的XPATH查询的字符串。

注意

为方便起见,此方法可写成response.xpath()

css(query)

应用给定的CSS选择器并返回一个SelectorList实例。

query是一个包含要应用的CSS选择器的字符串。

在后台,会使用cssselect库将CSS查询转换为XPath查询并运行​​ .xpath()方法。

注意

为了方便起见,这个方法可以写成response.css()

extract()

序列化并返回匹配的节点作为unicode字符串列表。 Percent encoded content is unquoted.

re(regex)

应用给定的正则表达式并返回匹配到的unicode字符串列表。

regex可以是编译的正则表达式,也可以是使用re.compile(egex)编译为正则表达式的字符串

注意

请注意,re()re_first()都解码HTML实体(除了&lt;&amp;)

register_namespace(prefix, uri)

注册在Selector中使用的给定名称空间。 如果不注册名称空间,则无法从非标准名称空间中选择或提取数据。 见下面的例子。

remove_namespaces()

删除所有名称空间,允许使用不含名称空间的xpaths来遍历文档。 见下面的例子。

__nonzero__()

如果有实际内容被选择,则返回True,否则返回False 换句话说,Selector的布尔值由它选择的内容给出。

SelectorList objects
class scrapy.selector.SelectorList

SelectorList类是内置的list类的一个子类,它提供了一些额外的方法。

xpath(query)

为此列表中的每个元素调用.xpath()方法,并将其结果展平为另一个SelectorList

querySelector.xpath()中的参数相同

css(query)

对此列表中每个元素调用.css()方法,并将其结果展平为另一个SelectorList

querySelector.css()中的参数相同

extract()

为此列表中的每个元素调用.extract()方法,并将其结果展平,作为unicode字符串列表。

re()

对此列表中每个元素调用.re()方法,并将其结果展平,作为unicode字符串列表。

HTML响应选择器示例

这里有几个Selector的例子用来说明几个概念。 在所有情况下,我们都假设已经有一个SelectorHtmlResponse对象实例化,如下所示:

sel = Selector(html_response)
  1. 从HTML响应主体中选择全部<h1>元素,返回一个Selector对象列表(即 一个SelectorList对象):

    sel.xpath("//h1")
    
  2. 从HTML响应主体中提取所有<h1>文本,返回一个Unicode字符串列表:

    sel.xpath("//h1").extract()         # this includes the h1 tag
    sel.xpath("//h1/text()").extract()  # this excludes the h1 tag
    
  3. 遍历所有<p>标签,打印出他们的类属性:

    for node in sel.xpath("//p"):
        print node.xpath("@class").extract()
    
XML响应选择器示例

这里有几个例子来说明几个概念。 在这两种情况下,我们都假定已经有一个SelectorXmlResponse对象实例化,如下所示:

sel = Selector(xml_response)
  1. 从XML响应主体中选择全部<product>元素,返回一个Selector对象列表,(即 一个SelectorList对象):

    sel.xpath("//product")
    
  2. Google Base XML Feed中提取所有的价格需要注册命名空间:

    sel.register_namespace("g", "http://base.google.com/ns/1.0")
    sel.xpath("//g:price").extract()
    
删除命名空间

在处理抓取项目时,通常完全摆脱名称空间并仅使用元素名称来编写更简单/便捷的XPath非常方便。 您可以使用Selector.remove_namespaces()方法做到这点。

我们来看一个用GitHub博客atom feed来说明的例子。

首先,用我们想要抓取的url打开shell:

$ scrapy shell https://github.com/blog.atom

一旦进入shell,我们可以尝试选择所有的<link> 对象,发现它不工作(因为这个Atom XML命名空间使节点模糊)

>>> response.xpath("//link")
[]

但是一旦我们调用了Selector.remove_namespaces()方法,所有节点都可以直接通过它们的名字来访问:

>>> response.selector.remove_namespaces()
>>> response.xpath("//link")
[<Selector xpath='//link' data=u'<link xmlns="http://www.w3.org/2005/Atom'>,
 <Selector xpath='//link' data=u'<link xmlns="http://www.w3.org/2005/Atom'>,
 ...

如果您想知道为什么命名空间删除过程并不总是被默认调用从而而不必手动调用它,这是因为两个原因,按照相关性顺序,这两个原因是:

  1. 删除名称空间需要迭代和修改文档中的所有节点,这对于Scrapy搜索的所有文档来说是相当昂贵的操作
  2. 在某些情况下,实际上需要使用名称空间,以防某些元素名称在名称空间之间发生冲突。 虽然这种情况非常罕见。

Items

抓取的主要目标是从非结构化来源(通常是网页)中提取结构化数据。 Scrapy Spider可以将提取的数据作为Python字典返回。 虽然方便且常见,但Python字典缺乏结构:很容易发生字段名拼写错误或返回不一致数据,尤其是在包含许多Spider的大型项目中。

为定义公共输出数据格式,Scrapy提供了Item类。 Item对象是用于收集抓取数据的简单容器。 它们提供了一个带有便捷语法的类似字典API,用于声明其可用字段。

各种Scrapy组件使用Items提供的额外信息:exporter查看已声明的字段以确定要导出的列,序列化可以使用Item字段元数据定制,trackref跟踪Item实例以帮助查找内存泄漏(请参阅使用trackref调试内存泄漏),等等。

声明Item

使用简单的类定义语法和Field对象声明Item。 这里是一个例子:

import scrapy

class Product(scrapy.Item):
    name = scrapy.Field()
    price = scrapy.Field()
    stock = scrapy.Field()
    last_updated = scrapy.Field(serializer=str)

注意

熟悉Django的人会注意到Scrapy Item的声明与Django Models相似,只是Scrapy项目更简单,因为没有不同字段类型的概念。

Item字段

Field对象用于为每个字段指定元数据。 例如,上例中所示的last_updated字段的串行器函数说明。

您可以为每个字段指定任何类型的元数据。 Field对象接受的值没有限制。 出于同样的原因,没有所有可用元数据键的参考列表。 Field对象中定义的每个键都可以由不同的组件使用,只有那些组件才知道它。 您也可以在您的项目中定义和使用任何其他Field键,以满足您的需要。 Field对象的主要目标是提供一种在一个地方定义所有字段元数据的方法。 通常,那些行为依赖于每个字段的组件使用特定的字段键来配置该行为。 您必须参考他们的文档以查看每个组件使用哪些元数据键。

请注意,用于声明Item的Field对象不会保留为类属性。 相反,它们可以通过Item.fields属性进行访问。

使用Item

这是使用上面声明ProductItem展现对Item执行的常见任务的一些示例。 您会注意到API与dict API非常相似。

创建Item
>>> product = Product(name='Desktop PC', price=1000)
>>> print product
Product(name='Desktop PC', price=1000)
获取字段值
>>> product['name']
Desktop PC
>>> product.get('name')
Desktop PC

>>> product['price']
1000

>>> product['last_updated']
Traceback (most recent call last):
    ...
KeyError: 'last_updated'

>>> product.get('last_updated', 'not set')
not set

>>> product['lala'] # 获取未知字段
Traceback (most recent call last):
    ...
KeyError: 'lala'

>>> product.get('lala', 'unknown field')
'unknown field'

>>> 'name' in product  # is name field populated?
True

>>> 'last_updated' in product  # is last_updated populated?
False

>>> 'last_updated' in product.fields  # is last_updated a declared field?
True

>>> 'lala' in product.fields  # is lala a declared field?
False
设置字段值
>>> product['last_updated'] = 'today'
>>> product['last_updated']
today

>>> product['lala'] = 'test' # 设置未知字段
Traceback (most recent call last):
    ...
KeyError: 'Product does not support field: lala'
访问所有填充值

要访问所有填充值,只需使用典型的dict API

>>> product.keys()
['price', 'name']

>>> product.items()
[('price', 1000), ('name', 'Desktop PC')]
其他常见任务

复制Item:

>>> product2 = Product(product)
>>> print product2
Product(name='Desktop PC', price=1000)

>>> product3 = product2.copy()
>>> print product3
Product(name='Desktop PC', price=1000)

从Item创建字典:

>>> dict(product) # 用所有填充值创建一个字典
{'price': 1000, 'name': 'Desktop PC'}

从字典创建Item:

>>> Product({'name': 'Laptop PC', 'price': 1500})
Product(price=1500, name='Laptop PC')

>>> Product({'name': 'Laptop PC', 'lala': 1500}) # 警告: 字典中未知字段
Traceback (most recent call last):
    ...
KeyError: 'Product does not support field: lala'

扩展Item

您可以通过声明原始Item的子类来扩展Item(以添加更多字段或更改某些字段的某些元数据)。

例如:

class DiscountedProduct(Product):
    discount_percent = scrapy.Field(serializer=str)
    discount_expiration_date = scrapy.Field()

您还可以对以前的字段元数据并附加更多值或更改现有值来扩展字段元数据,如下所示:

class SpecificProduct(Product):
    name = scrapy.Field(Product.fields['name'], serializer=my_serializer)

它为name字段添加(或替换)serializer元数据键,保留所有先前存在的元数据值。

Item对象

class scrapy.item.Item([arg])

返回一个新的Item,可以从给定的参数中初始化。

Item复制了标准的dict API,包括其构造函数。 提供给Item的唯一附加属性是:

fields

一个包含所有已声明字段的字典,不只包含添加的。 键是字段名称,值是Item声明中使用的Field对象。

Field objects

class scrapy.item.Field([arg])

Field类仅仅是内置的dict类的别名,不提供任何额外的功能或属性。 换句话说,Field对象是普通的Python字典。 是一个用于支持基于类属性的Item声明语法的单独的类。

Item加载器

Item加载器提供了一种便捷的机制来填充已被抓取的Items 虽然Item可以使用自己的字典API来填充,Item加载器在抓取进程中提供了更便利的API填充它们,通过自动操作一些常见的任务类似在指定它之前解析原始数据。

换句话说,Items提供了抓取数据的容器,而Item Loaders提供了填充该容器的机制。

Item加载器旨在提供一种灵活,高效且简单的机制来扩展和覆盖不同的字段解析规则,无论是通过Spider还是通过源格式(HTML,XML等),不会成为维护的噩梦。

使用Item加载器填充Item

要使用Item加载器,你必须首先实例化它。 您可以使用类似字典的对象实例化它(例如 Item或dict)或者什么也不用,在这种情况下,Item会在Item Loader构造函数中使用ItemLoader.default_item_class属性中指定的Item类自动实例化。

然后,您开始将值收集到Item Loader中,通常使用选择器 您可以将多个值添加到相同的Item字段; Item Loader将知道如何使用适当的处理函数“加入”这些值。

这是Spider中典型Item Loader用法,使用Item章节中声明的Product item

from scrapy.loader import ItemLoader
from myproject.items import Product

def parse(self, response):
    l = ItemLoader(item=Product(), response=response)
    l.add_xpath('name', '//div[@class="product_name"]')
    l.add_xpath('name', '//div[@class="product_title"]')
    l.add_xpath('price', '//p[@id="price"]')
    l.add_css('stock', 'p#stock]')
    l.add_value('last_updated', 'today') # 你也可以使用文本值
    return l.load_item()

通过快速查看代码,我们可以看到name字段是从页面中两个不同的XPath位置提取的:

  1. //div[@class="product_name"]
  2. //div[@class="product_title"]

换句话说,通过使用add_xpath()方法从两个XPath位置提取来收集数据。 这是稍后将分配给name字段的数据。

之后,类似的调用用于pricestock字段(后者使用CSS选择器和add_css()方法),最后last_update字段使用不同的方法直接填充文本值(today):add_value()

最后,当收集到所有数据时,将调用ItemLoader.load_item()方法,该方法实际上会返回填充了之前使用add_xpath()add_css()add_value()提取和收集的数据Item。

输入和输出处理器

Item加载器为每个(Item)字段提供了一个输入处理器和一个输出处理器。 输入处理器一收到(通过add_xpath()add_css()add_value()方法)提取的数据就立即处理,输入处理器的结果被收集并保存在ItemLoader中。 收集完所有数据后,调用ItemLoader.load_item()方法来填充并获取填充的Item对象。 这是在输出处理器被调用之前收集数据(并使用输入处理器处理)的情况。 输出处理器的结果是分配给Item的最终值。

我们来看一个例子来说明如何为指定字段调用输入和输出处理器(这同样适用于其他字段):

l = ItemLoader(Product(), some_selector)
l.add_xpath('name', xpath1) # (1)
l.add_xpath('name', xpath2) # (2)
l.add_css('name', css) # (3)
l.add_value('name', 'test') # (4)
return l.load_item() # (5)

将会发生:

  1. 来自xpath1的数据被提取并通过name字段的输入处理器传递。 输入处理器的结果被收集并保存在Item Loader中(但尚未分配给Item)。
  2. 来自xpath2的数据被提取,并通过(1)中使用的相同的输入处理器传递。 输入处理器的结果附加到(1)中收集的数据(如果有的话)。
  3. 除了使用CSS选择器从css 提取数据,这种情况与之前的类似,通过使用与(1)和(2)中相同输入处理器 输入处理器的结果附加到(1)和(2)中收集的数据(如果有的话)。
  4. 这种情况也类似于以前的情况,不同之处在于要收集的值是直接分配的,而不是从XPath表达式或CSS选择器中提取。 但是,该值仍然通过输入处理器传递。 在这种情况下,由于该值不可迭代,在将其传递给输入处理器之前将其转换为单个元素的迭代器,因为输入处理器总是接收迭代器。
  5. 在步骤(1),(2),(3)和(4)中收集的数据通过name字段的输出处理器传递。 输出处理器的结果被分配给Item中的name字段的值。

值得注意的是,处理器仅仅是可调用的对象,它们被调用以解析数据,并返回一个解析的值。 所以你可以使用任何方法作为输入或输出处理器。 唯一的要求是它们必须接受一个(且只有一个)位置参数,它将是一个迭代器。

注意

输入和输出处理器都必须接收迭代器作为它们的第一个参数。 这些函数的输出可以是任何东西。 输入处理器的结果将被附加到包含收集值(对于该字段)的内部列表(在加载器中)。 输出处理器的结果是最终将分配给该Item的值。

另外需要注意的是输入处理器返回的值在内部收集(以列表形式),然后传递给输出处理器以填充字段。

最后但同样重要的是,Scrapy附带了一些内置的常用处理器以方便使用。

声明Item加载器

声明Item加载器与声明Item类似,都是通过使用类定义语法。 这里是一个例子:

from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, MapCompose, Join

class ProductLoader(ItemLoader):

    default_output_processor = TakeFirst()

    name_in = MapCompose(unicode.title)
    name_out = Join()

    price_in = MapCompose(unicode.strip)

    # ...

如您所见,输入处理器是使用_in后缀声明的,而输出处理器是使用_out后缀声明的。 您还可以使用ItemLoader.default_input_processorItemLoader.default_output_processor属性声明默认的输入/输出处理器。

声明输入和输出处理器

如前一节所述,可以在Item Loader定义中声明输入和输出处理器,以这种方式声明输入处理器是很常见的。 您还可以在 Item字段元数据中指定要使用的输入和输出处理器。 这里是一个例子:

import scrapy
from scrapy.loader.processors import Join, MapCompose, TakeFirst
from w3lib.html import remove_tags

def filter_price(value):
    if value.isdigit():
        return value

class Product(scrapy.Item):
    name = scrapy.Field(
        input_processor=MapCompose(remove_tags),
        output_processor=Join(),
    )
    price = scrapy.Field(
        input_processor=MapCompose(remove_tags, filter_price),
        output_processor=TakeFirst(),
    )
>>> from scrapy.loader import ItemLoader
>>> il = ItemLoader(item=Product())
>>> il.add_value('name', [u'Welcome to my', u'<strong>website</strong>'])
>>> il.add_value('price', [u'&euro;', u'<span>1000</span>'])
>>> il.load_item()
{'name': u'Welcome to my website', 'price': u'1000'}

输入和输出处理器的优先顺序如下:

  1. Item加载器字段特定的属性:field_infield_out(最优先)
  2. 字段元数据(input_processoroutput_processor键)
  3. Item加载器默认值:ItemLoader.default_input_processor()ItemLoader.default_output_processor()(最低优先级)

另见:重用和扩展Item加载器

Item加载器上下文

Item加载器上下文是Item加载器中所有输入和输出处理器共享的任意键/值的字典。 它可以在声明,实例化或使用Item加载器时传递。 它们用于修改输入/输出处理器的行为。

例如,假设您有一个函数parse_length,它接收一个文本值并从中提取文本长度:

def parse_length(text, loader_context):
    unit = loader_context.get('unit', 'm')
    # ... length parsing code goes here ...
    return parsed_length

通过接受一个loader_context参数,该函数明确告诉Item加载器它能够接收Item加载器上下文,因此Item加载器在调用它时传递当前活动的上下文,以便处理器函数(本例中为 parse_length)可以使用它们。

有几种方法可以修改Item加载器上下文值:

  1. 通过修改当前活动的Item加载器上下文(context属性):

    loader = ItemLoader(product)
    loader.context['unit'] = 'cm'
    
  2. 在Item Loader实例化时(Item加载器构造函数的关键字参数存储在Item Loader上下文中):

    loader = ItemLoader(product, unit='cm')
    
  3. 在Item Loader声明中,对于那些支持用Item Loader上下文实例化的输入/输出处理器。 MapCompose就是其中之一:

    class ProductLoader(ItemLoader):
        length_out = MapCompose(parse_length, unit='cm')
    

ItemLoader对象

class scrapy.loader.ItemLoader([item, selector, response, ]**kwargs)

返回一个新的Item Loader来填充给定的Item。 如果没有给出Item,则使用default_item_class中的类自动实例化。

当使用selectorresponse参数实例化时,ItemLoader类提供了使用selectors从网页中提取数据的方便机制。

参数:

Item,Selector,Response和其余关键字参数被分配给Loader上下文(可通过context属性访问)。

ItemLoader实例具有以下方法:

get_value(value, *processors, **kwargs)

通过给定的processors和关键字参数处理给定的value

可用关键字参数:

参数:re (str 已编译的正则表达式) - 一个正则表达式,用于在处理器之前应用extract_regex()方法从给定值中提取数据

示例:

>>> from scrapy.loader.processors import TakeFirst
>>> loader.get_value(u'name: foo', TakeFirst(), unicode.upper, re='name: (.+)')
'FOO`
add_value(field_name, value, *processors, **kwargs)

处理,然后为给定字段添加给定的value

value首先通过get_value()传递给processorskwargs,然后通过field input processor传递,结果附加到字段收集的数据中。 如果该字段已包含收集的数据,则添加新数据。

给定的field_name可以是None,在这种情况下,可以添加多个字段的值。 处理后的值应该是一个带有field_name映射值的字典。

示例:

loader.add_value('name', u'Color TV')
loader.add_value('colours', [u'white', u'blue'])
loader.add_value('length', u'100')
loader.add_value('name', u'name: foo', TakeFirst(), re='name: (.+)')
loader.add_value(None, {'name': u'foo', 'sex': u'male'})
replace_value(field_name, value, *processors, **kwargs)

add_value()类似,但用新值替换收集的数据,而不是添加它。

get_xpath(xpath, *processors, **kwargs)

ItemLoader.get_value()类似,但接收XPath而不是value,该Xpath用于从与ItemLoader关联的选择器中提取unicode字符串列表。

Parameters:
  • xpathstr) - ​​提取数据的XPath
  • re (str 已编译的正则表达式) - 从选定的XPath区域提取数据的正则表达式

示例:

# HTML snippet: <p class="product-name">Color TV</p>
loader.get_xpath('//p[@class="product-name"]')
# HTML snippet: <p id="price">the price is $1200</p>
loader.get_xpath('//p[@id="price"]', TakeFirst(), re='the price is (.*)')
add_xpath(field_name, xpath, *processors, **kwargs)

ItemLoader.add_value()类似,但接收XPath而不是value,Xpath用于从与ItemLoader关联的选择器中提取unicode字符串列表。

See get_xpath() for kwargs.

Parameters:xpath (str) – the XPath to extract data from

Examples:

# HTML snippet: <p class="product-name">Color TV</p>
loader.add_xpath('name', '//p[@class="product-name"]')
# HTML snippet: <p id="price">the price is $1200</p>
loader.add_xpath('price', '//p[@id="price"]', re='the price is (.*)')
replace_xpath(field_name, xpath, *processors, **kwargs)

Similar to add_xpath() but replaces collected data instead of adding it.

get_css(css, *processors, **kwargs)

Similar to ItemLoader.get_value() but receives a CSS selector instead of a value, which is used to extract a list of unicode strings from the selector associated with this ItemLoader.

Parameters:
  • css (str) – the CSS selector to extract data from
  • re (str or compiled regex) – a regular expression to use for extracting data from the selected CSS region

Examples:

# HTML snippet: <p class="product-name">Color TV</p>
loader.get_css('p.product-name')
# HTML snippet: <p id="price">the price is $1200</p>
loader.get_css('p#price', TakeFirst(), re='the price is (.*)')
add_css(field_name, css, *processors, **kwargs)

Similar to ItemLoader.add_value() but receives a CSS selector instead of a value, which is used to extract a list of unicode strings from the selector associated with this ItemLoader.

See get_css() for kwargs.

Parameters:css (str) – the CSS selector to extract data from

Examples:

# HTML snippet: <p class="product-name">Color TV</p>
loader.add_css('name', 'p.product-name')
# HTML snippet: <p id="price">the price is $1200</p>
loader.add_css('price', 'p#price', re='the price is (.*)')
replace_css(field_name, css, *processors, **kwargs)

Similar to add_css() but replaces collected data instead of adding it.

load_item()

用目前收集的数据填充项目,然后返回。 收集到的数据首先通过output processors传递给每个项目字段以获取最终值。

nested_xpath(xpath)

用xpath选择器创建一个嵌套的Loader。 提供的选择器与ItemLoader关联的选择器应用是相对的。 嵌套的Loader与父ItemLoader共享Item,所以调用add_xpath()add_value() replace_value()等将正常工作。

nested_css(css)

用css选择器创建一个嵌套的Loader。 提供的选择器与ItemLoader关联的选择器应用是相对的。 嵌套的Loader与父ItemLoader共享Item,所以调用add_xpath()add_value() replace_value()等将正常工作。

get_collected_values(field_name)

返回给定字段的收集值。

get_output_value(field_name)

对于给定的字段,返回使用输出处理器分析的收集值。 此方法不能填充或修改Item。

get_input_processor(field_name)

返回给定字段的输入处理器。

get_output_processor(field_name)

返回给定字段的输出处理器。

ItemLoader实例具有以下属性:

item

Item Loader解析的Item对象。

context

Item Loader中当前活动的Context

default_item_class

Item类(或工厂),用于在构造函数中未给出Item时,实例化Item。

default_input_processor

默认输入处理器,用于没有指定输入处理器的字段。

default_output_processor

默认输出处理器,用于那些没有指定输出处理器的字段。

default_selector_class

如果在构造函数中只给出response,则该类用于构造ItemLoaderselector 如果在构造函数中给出了选择器,则忽略此属性。 该属性有时在子类中被覆盖。

selector

要从中提取数据的Selector对象。 它可以是构造函数中给出的,也可以是有response的构造函数使用default_selector_class创建的选择器。 该属性是只读的。

嵌套Loader

从文档的子部分解析相关值时,创建嵌套的Loader可能很有用。 想象一下,您正在从页面的页脚中提取详细信息,如下所示:

例:

<footer>
    <a class="social" href="https://facebook.com/whatever">Like Us</a>
    <a class="social" href="https://twitter.com/whatever">Follow Us</a>
    <a class="email" href="mailto:whatever@example.com">Email Us</a>
</footer>

没有嵌套的Loader,你需要为你想要提取的每个值指定完整的xpath(或css)。

例:

loader = ItemLoader(item=Item())
# load stuff not in the footer
loader.add_xpath('social', '//footer/a[@class = "social"]/@href')
loader.add_xpath('email', '//footer/a[@class = "email"]/@href')
loader.load_item()

或者,您可以使用页脚选择器创建一个嵌套的Loader,然后添加页脚的相对值。 功能相同,但您可以避免重复页脚选择器。

例:

loader = ItemLoader(item=Item())
# load stuff not in the footer
footer_loader = loader.nested_xpath('//footer')
footer_loader.add_xpath('social', 'a[@class = "social"]/@href')
footer_loader.add_xpath('email', 'a[@class = "email"]/@href')
# no need to call footer_loader.load_item()
loader.load_item()

您可以使用xpath或css选择器任意嵌套Loader。 一般来说,当使用嵌套加载器可以使您的代码变得更简单时使用它们,但不要过度嵌套,否则解析器会变得难以阅读。

重用和扩展Item Loader

随着项目越来越大并且获得越来越多的Spider,维护成为一个基本问题,尤其是当你必须处理每个Spider的许多不同解析规则和大量的异常处理情况,但同时也想重复使用通用处理器。

Item Loader旨在减轻解析规则的维护负担,不失灵活性,同时还提供了扩展和覆盖它们的便利机制。 出于这个原因, Item Loader支持传统的Python类继承来处理特定的Spider(或Spider组)的差异。

例如,假设一些特定的网站用三个破折号(例如---Plasma TV---)封装其产品名称,而您不想在最终产品名称中取得这些破折号。

您可以通过重用和扩展默认Product Item Loader(ProductLoader)来删除这些破折号:

from scrapy.loader.processors import MapCompose
from myproject.ItemLoaders import ProductLoader

def strip_dashes(x):
    return x.strip('-')

class SiteSpecificLoader(ProductLoader):
    name_in = MapCompose(strip_dashes, ProductLoader.name_in)

另一种扩展项目加载器的情况会非常有用,那就是当你有多种源格式时,例如XML和HTML。 在XML版本中,您可能需要删除CDATA事件。 以下是如何执行此操作的示例:

from scrapy.loader.processors import MapCompose
from myproject.ItemLoaders import ProductLoader
from myproject.utils.xml import remove_cdata

class XmlProductLoader(ProductLoader):
    name_in = MapCompose(remove_cdata, ProductLoader.name_in)

这就是典型的扩展输入处理器的方法。

至于输出处理器,在字段元数据中声明它们更为常见,因为它们通常仅取决于字段,而不取决于每个特定的站点解析规则(如输入处理器所做的那样)。 另见:声明输入和输出处理器

还有很多其他可能的方式来扩展,继承和覆盖您的Item Loader,而不同的Item Loader层次结构可能适合不同的项目。 Scrapy只提供机制;它不会对您的Loaders集合实施任何特定的组织 - 这取决于您和您的项目需求。

可用的内置处理器

尽管您可以使用任何可调用的函数作为输入和输出处理器,但Scrapy提供了一些常用的处理器,下面将对其进行介绍。 其中一些,如MapCompose(通常用作输入处理器)生成顺序执行的几个函数的输出,以产生最终解析值。

以下是所有内置处理器的列表:

class scrapy.loader.processors.Identity

最简单的处理器,它什么都不做。 它返回原始值不做任何改变。 它不接收任何构造函数参数,也不接受Loader上下文。

例:

>>> from scrapy.loader.processors import Identity
>>> proc = Identity()
>>> proc(['one', 'two', 'three'])
['one', 'two', 'three']
class scrapy.loader.processors.TakeFirst

从接收到的值中返回第一个非空值,因此它通常用作单值字段的输出处理器。 它不接收任何构造函数参数,也不接受Loader上下文。

例:

>>> from scrapy.loader.processors import TakeFirst
>>> proc = TakeFirst()
>>> proc(['', 'one', 'two', 'three'])
'one'
class scrapy.loader.processors.Join(separator=u' ')

返回使用构造函数中给出的分隔符​​连接后的值,默认值为u' ' 它不接受Loader上下文。

当使用默认的分隔符时,这个处理器相当于下面的函数:u' '.join

例:

>>> from scrapy.loader.processors import Join
>>> proc = Join()
>>> proc(['one', 'two', 'three'])
u'one two three'
>>> proc = Join('<br>')
>>> proc(['one', 'two', 'three'])
u'one<br>two<br>three'
class scrapy.loader.processors.Compose(*functions, **default_loader_context)

由给定函数的组合构成的处理器。 这意味着该处理器的每个输入值都被传递给第一个函数,并且该函数的结果被传递给第二个函数,依此类推,直到最后一个函数返回该处理器的输出值。

默认情况下,处理器遇到None值停止。 这种行为可以通过传递关键字参数stop_on_none=False来改变。

例:

>>> from scrapy.loader.processors import Compose
>>> proc = Compose(lambda v: v[0], str.upper)
>>> proc(['hello', 'world'])
'HELLO'

每个函数都可以选择接收一个loader_context参数。 处理器将通过该参数传递当前活动的Loader context

传给构造函数的关键字参数作为传递给每个函数调用的默认Loader context值。 但是,通过ItemLoader.context()属性可以访问当前活动的Loader context,从而将传递给函数的最后的Loader context值覆盖。

class scrapy.loader.processors.MapCompose(*functions, **default_loader_context)

由给定函数的组合构成的处理器,类似于Compose处理器。 不同之处在于内部结果在各个函数之间传递的方式,如下所示:

该处理器的输入值是迭代的,第一个函数被应用于每个元素。 这些函数调用的结果(每个元素一个)被连接起来构成一个新的迭代器,然后传递给​​第二个函数,依此类推,直到最后一个函数被应用到所收集的值列表中的每个值为止。 最后一个函数的输出值被连接在一起产生该处理器的输出。

每个特定的函数都可以返回一个值或一个值列表,同一个函数不同的输入值返回的值列表是一致的。 这些函数也可以返回None,在这种情况下,该函数的输出将被忽略,以便通过链进一步处理。

该处理器提供了一种便捷的方式来组合仅使用单个值(而不是迭代)的函数。 出于这个原因,MapCompose处理器通常用作输入处理器,因为通常使用 selectorsextract()方法提取数据,该方法返回一个unicode字符串列表。

下面的例子将说明它的工作原理:

>>> def filter_world(x):
...     return None if x == 'world' else x
...
>>> from scrapy.loader.processors import MapCompose
>>> proc = MapCompose(filter_world, unicode.upper)
>>> proc([u'hello', u'world', u'this', u'is', u'scrapy'])
[u'HELLO, u'THIS', u'IS', u'SCRAPY']

与Compose处理器一样,函数可以接收Loader context,并将构造函数关键字参数用作默认context值。 有关更多信息,请参阅Compose处理器。

class scrapy.loader.processors.SelectJmes(json_path)

使用提供给构造函数的json路径查询该值并返回输出。 需要运行jmespath(https://github.com/jmespath/jmespath.py)。 该处理器一次只有一个输入。

Example:

>>> from scrapy.loader.processors import SelectJmes, Compose, MapCompose
>>> proc = SelectJmes("foo") #for direct use on lists and dictionaries
>>> proc({'foo': 'bar'})
'bar'
>>> proc({'foo': {'bar': 'baz'}})
{'bar': 'baz'}

使用Json:

>>> import json
>>> proc_single_json_str = Compose(json.loads, SelectJmes("foo"))
>>> proc_single_json_str('{"foo": "bar"}')
u'bar'
>>> proc_json_list = Compose(json.loads, MapCompose(SelectJmes('foo')))
>>> proc_json_list('[{"foo":"bar"}, {"baz":"tar"}]')
[u'bar']

Scrapy shell

Scrapy shell是一个交互式shell,您可以非常快速地尝试并调试您的抓取代码,而无需运行Spider。 它旨在用于测试数据提取代码,但实际上它可以用于测试任何类型的代码,因为它也是一个常规的Python shell。

该shell用于测试XPath或CSS表达式,查看它们的工作方式以及从您试图抓取的网页中提取到的数据。 它可以让你在写Spider时交互地测试你的表达式,而不必运行Spider来测试每一个变化。

一旦熟悉Scrapy shell,您会发现它是开发和调试您的Spider的宝贵工具。

配置shell

如果安装了IPython,Scrapy shell将使用它(而不是标准的Python控制台)。 IPython控制台功能更强大,并提供了智能自动完成和彩色输出等功能。

我们强烈建议您安装IPython,特别是如果您在Unix系统上工作(IPython擅长平台)。 有关更多信息,请参阅IPython安装指南

Scrapy还支持bpython,并会在IPython不可用的情况下尝试使用它。

通过scrapy的设置,不管安装哪个,您都可以将其配置为使用ipythonbpython或标准python shell中的任何一个。 这是通过设置SCRAPY_PYTHON_SHELL环境变量完成的;或者通过在scrapy.cfg中定义它:

[settings]
shell = bpython

启动shell

要启动Scrapy shell,你可以像这样使用shell命令:

scrapy shell <url>

这个<url>是你想要抓取的链接.

shell也适用于本地文件。 如果你想抓取一个网页的本地副本,这可以很方便。 shell支持本地文件的以下语法:

# UNIX-style
scrapy shell ./path/to/file.html
scrapy shell ../other/path/to/file.html
scrapy shell /absolute/path/to/file.html

# File URI
scrapy shell file:///absolute/path/to/file.html

注意

在使用相对文件路径时,应明确指定它们,并在相关时用./(或../)作为前缀。 scrapy shell index.html不会像预期的那样工作(这是设计而非错误)。

由于shell支持HTTP URL超过File URI,而index.html在语法上与example.com类似,shell会将index.html视为域名从而引发DNS查找错误:

$ scrapy shell index.html
[ ... scrapy shell starts ... ]
[ ... traceback ... ]
twisted.internet.error.DNSLookupError: DNS lookup failed:
address 'index.html' not found: [Errno -5] No address associated with hostname.

shell不会事先测试当前目录中是否存在名为index.html的文件。 请再次确认。

使用shell

Scrapy shell只是一个普通的Python控制台(如果IPython可用的话,就是IPython控制台),它提供了一些额外的快捷方式功方便使用。

可用快捷方式
  • shelp() - 打印可用对象和快捷方式列表帮助
  • fetch(url[, redirect=True]) - 从给定的URL获取新的响应并相应地更新所有相关的对象。 您可以选择通过redirect=False使HTTP 3xx重定向不传递
  • fetch(request) - 从给定的请求中获取新的响应并相应地更新所有相关的对象。
  • view(response) - 在本地网络浏览器中打开给定的响应,以便进行检查。 这将添加一个<base>标签便于外部链接(例如图片或样式表)正常显示. 但请注意,这将在您的计算机中创建一个临时文件,该文件不会被自动删除。
可用的Scrapy对象

Scrapy shell自动根据下载的页面创建一些方便的对象,如Response对象和Selector对象(对于HTML和XML内容)。

这些对象是:

  • crawler - 当前的Crawler对象。
  • spider - 已知处理URL的Spider,如果没有为当前URL指定Spider,则为Spider对象.
  • request - 最后获取页面的Request对象。 您可以使用replace()修改此请求,或使用fetch快捷方式获取新请求(不能脱离shell)。
  • response - 包含最后获取页面的Response对象
  • settings - 当前Scrapy设置

shell会话示例

以下是一个典型的shell会话示例,我们首先通过抓取https://scrapy.org页面开始,然后继续抓取https://reddit.com页面。 最后,我们将(Reddit)请求方法修改为POST并重新获取将会发生错误。 我们通过键入Ctrl-D(在Unix系统中)或Ctrl-Z(在Windows中)来结束会话。

请注意,这里提取的数据在你尝试时可能不尽相同,因为这些页面不是静态的,在测试时可能会发生变化。 这个例子的唯一目的是让你熟悉Scrapy shell的工作原理。

首先,我们启动shell:

scrapy shell 'https://scrapy.org' --nolog

然后,shell获取URL(使用Scrapy下载器)并打印可用对象列表和快捷方式(您会注意到这些行都以[s]前缀开头):

[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x7f07395dd690>
[s]   item       {}
[s]   request    <GET https://scrapy.org>
[s]   response   <200 https://scrapy.org/>
[s]   settings   <scrapy.settings.Settings object at 0x7f07395dd710>
[s]   spider     <DefaultSpider 'default' at 0x7f0735891690>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser

>>>

之后,我们可以开始尝试使用对象:

>>> response.xpath('//title/text()').extract_first()
'Scrapy | A Fast and Powerful Scraping and Web Crawling Framework'

>>> fetch("https://reddit.com")

>>> response.xpath('//title/text()').extract()
['reddit: the front page of the internet']

>>> request = request.replace(method="POST")

>>> fetch(request)

>>> response.status
404

>>> from pprint import pprint

>>> pprint(response.headers)
{'Accept-Ranges': ['bytes'],
 'Cache-Control': ['max-age=0, must-revalidate'],
 'Content-Type': ['text/html; charset=UTF-8'],
 'Date': ['Thu, 08 Dec 2016 16:21:19 GMT'],
 'Server': ['snooserv'],
 'Set-Cookie': ['loid=KqNLou0V9SKMX4qb4n; Domain=reddit.com; Max-Age=63071999; Path=/; expires=Sat, 08-Dec-2018 16:21:19 GMT; secure',
                'loidcreated=2016-12-08T16%3A21%3A19.445Z; Domain=reddit.com; Max-Age=63071999; Path=/; expires=Sat, 08-Dec-2018 16:21:19 GMT; secure',
                'loid=vi0ZVe4NkxNWdlH7r7; Domain=reddit.com; Max-Age=63071999; Path=/; expires=Sat, 08-Dec-2018 16:21:19 GMT; secure',
                'loidcreated=2016-12-08T16%3A21%3A19.459Z; Domain=reddit.com; Max-Age=63071999; Path=/; expires=Sat, 08-Dec-2018 16:21:19 GMT; secure'],
 'Vary': ['accept-encoding'],
 'Via': ['1.1 varnish'],
 'X-Cache': ['MISS'],
 'X-Cache-Hits': ['0'],
 'X-Content-Type-Options': ['nosniff'],
 'X-Frame-Options': ['SAMEORIGIN'],
 'X-Moose': ['majestic'],
 'X-Served-By': ['cache-cdg8730-CDG'],
 'X-Timer': ['S1481214079.394283,VS0,VE159'],
 'X-Ua-Compatible': ['IE=edge'],
 'X-Xss-Protection': ['1; mode=block']}
>>>

在Spider中调用shell来检查响应

有时候你想检查一下Spider某一点正在处理的响应,想要知道到达那里是否符合你的预期。

这可以通过使用scrapy.shell.inspect_response函数来实现。

以下是您如何在您的Spider中调用它的示例:

import scrapy


class MySpider(scrapy.Spider):
    name = "myspider"
    start_urls = [
        "http://example.com",
        "http://example.org",
        "http://example.net",
    ]

    def parse(self, response):
        # We want to inspect one specific response.
        if ".org" in response.url:
            from scrapy.shell import inspect_response
            inspect_response(response, self)

        # Rest of parsing code.

当你运行Spider时,你会得到类似于这样的东西:

2014-01-23 17:48:31-0400 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://example.com> (referer: None)
2014-01-23 17:48:31-0400 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://example.org> (referer: None)
[s] Available Scrapy objects:
[s]   crawler    <scrapy.crawler.Crawler object at 0x1e16b50>
...

>>> response.url
'http://example.org'

然后,你可以检查提取代码是否工作:

>>> response.xpath('//h1[@class="fn"]')
[]

它没有正常工作, 因此,您可以在Web浏览器中打开响应,看看它是否是您期望的响应:

>>> view(response)
True

最后,您按Ctrl-D(或Windows中的Ctrl-Z)以退出shell并继续爬取:

>>> ^D
2014-01-23 17:50:03-0400 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://example.net> (referer: None)
...

请注意,由于Scrapy引擎被shell阻塞,因此您不能在此处使用fetch快捷方式。 但是,在离开shell后,Spider将继续爬取,如上所示。

Item管道

Item被Spider抓取后,它被发送到Item管道,Item管道通过顺序执行的多个组件处理它。

每个Item管道组件(有时简称为“Item管道”)是一个执行简单方法的Python类。 他们接收一个Item对其执行操作,并决定该Item是否应该继续通过管道或是被丢弃并不再处理。

Item管道的典型用途是:

  • 清理HTML数据
  • 验证抓取的数据(检查Item是否包含某些字段)
  • 检查重复项(并丢弃它们)
  • 将抓取的Item存储在数据库中

编写自己的Item管道

每个Item管道组件都是一个Python类,它必须实现以下方法:

process_item(self, item, spider)

每个Item管道组件都会调用此方法。 process_item()必须满足其中一条:返回一个带数据的字典,返回一个Item(或任何后代类)对象,返回一个Twisted Deferred或抛出DropItem异常。 被丢弃的Item不会被进一步的Item组件处理。

参数:
  • itemItem对象或字典) - 被抓取的Item
  • spider (Spider 对象) – 抓取Item的Spider

另外,它们还可以实现以下方法:

open_spider(self, spider)

这个方法在Spider被打开时调用。

参数:spider (Spider object) – 被打开的Spider
close_spider(self, spider)

这个方法在Spider被关闭时调用。

参数:spider (Spider object) – 被关闭的Spider
from_crawler(cls, crawler)

如果存在,就调用这个类方法为Crawler创建管道实例。 它必须返回一个新的管道实例。 Crawler对象提供对所有Scrapy核心组件的访问,如设置和标志;这是管道访问它们并将其功能挂接到Scrapy的一种方式。

参数:crawler (Crawler object) – 使用这个管道的Crawler

Item管道示例

价格验证并丢弃没有价格的Item

我们来看看下面的假设管道,它调整那些不包含增值税(price_excludes_vat属性)的Item的price属性,并删除那些不包含价格的Item:

from scrapy.exceptions import DropItem

class PricePipeline(object):

    vat_factor = 1.15

    def process_item(self, item, spider):
        if item['price']:
            if item['price_excludes_vat']:
                item['price'] = item['price'] * self.vat_factor
            return item
        else:
            raise DropItem("Missing price in %s" % item)
将Item写入JSON文件

下面的管道将所有抓取的Item(来自所有Spider)存储到单独的items.jl文件中,每行包含一个以JSON格式序列化的Item:

import json

class JsonWriterPipeline(object):

    def open_spider(self, spider):
        self.file = open('items.jl', 'w')

    def close_spider(self, spider):
        self.file.close()

    def process_item(self, item, spider):
        line = json.dumps(dict(item)) + "\n"
        self.file.write(line)
        return item

注意

JsonWriterPipeline的目的只是介绍如何编写Item管道。 如果你真的想把所有被抓取的Item存储到一个JSON文件中,你应该使用Feed exports

将Item写入MongoDB

在这个例子中,我们将使用pymongo将Item写入MongoDB MongoDB地址和数据库名称在Scrapy设置中指定; MongoDB集合以item类命名。

这个例子的要点是展示如何使用from_crawler()方法以及如何正确地清理资源。:

import pymongo

class MongoPipeline(object):

    collection_name = 'scrapy_items'

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        self.db[self.collection_name].insert_one(dict(item))
        return item
获取Item截图

本示例演示如何从process_item()方法返回Deferred 它使用Splash呈现Item url的屏幕截图。 管道请求本地运行的Splash实例。 下载请求并回调Deferred后,它将Item保存到一个文件并将文件名添加到Item。

import scrapy
import hashlib
from urllib.parse import quote


class ScreenshotPipeline(object):
    """Pipeline that uses Splash to render screenshot of
    every Scrapy item."""

    SPLASH_URL = "http://localhost:8050/render.png?url={}"

    def process_item(self, item, spider):
        encoded_item_url = quote(item["url"])
        screenshot_url = self.SPLASH_URL.format(encoded_item_url)
        request = scrapy.Request(screenshot_url)
        dfd = spider.crawler.engine.download(request, spider)
        dfd.addBoth(self.return_item, item)
        return dfd

    def return_item(self, response, item):
        if response.status != 200:
            # Error happened, return item.
            return item

        # Save screenshot to file, filename will be hash of url.
        url = item["url"]
        url_hash = hashlib.md5(url.encode("utf8")).hexdigest()
        filename = "{}.png".format(url_hash)
        with open(filename, "wb") as f:
            f.write(response.body)

        # Store filename in item.
        item["screenshot_filename"] = filename
        return item
重复过滤器

过滤器查找重复的Item,并删除已处理的重复Item。 假设我们的Item具有唯一的ID,但我们的Spider会使用相同的ID返回多个Item:

from scrapy.exceptions import DropItem

class DuplicatesPipeline(object):

    def __init__(self):
        self.ids_seen = set()

    def process_item(self, item, spider):
        if item['id'] in self.ids_seen:
            raise DropItem("Duplicate item found: %s" % item)
        else:
            self.ids_seen.add(item['id'])
            return item

激活Item管道组件

要激活Item Pipeline组件,必须将其类添加到ITEM_PIPELINES设置中,如下例所示:

ITEM_PIPELINES = {
    'myproject.pipelines.PricePipeline': 300,
    'myproject.pipelines.JsonWriterPipeline': 800,
}

您在此设置中分配给类的整数值决定了它们的运行顺序:Item从较低值类到较高值类。 通常在0-1000范围内定义这些数字。

导出文件

0.10版本中的新功能。

在实现爬虫时最常用的功能之一是能够正确存储抓取的数据,而且通常这意味着生成一个带有抓取数据的“输出文件”(通常称为“导出文件”),以供其他系统使用。

Scrapy通过Feed Export提供了这种功能,它允许您使用多个序列化格式和存储后端生成一个包含抓取的Item的文件。

序列化格式

为了序列化抓取的数据,导出文件使用Item exporters 这些格式支持开箱即用:

您也可以通过FEED_EXPORTERS设置扩展支持的格式。

JSON
JSON lines
CSV
  • FEED_FORMAT: csv
  • 使用的导出器:CsvItemExporter
  • 要指定要导出的列及其顺序,请使用FEED_EXPORT_FIELDS 其他文件导出器也可以使用此选项,但它对CSV很重要,因为与许多其他导出格式不同,CSV使用固定标题。
XML
Pickle
Marshal
  • FEED_FORMAT: marshal
  • 使用的导出器: MarshalItemExporter

存储器

使用导出文件时,您可以使用URI(通过 FEED_URI设置)定义存储文件的位置。 文件导出支持由URI方案定义的多个存储后端类型。

支持的存储后端是:

如果所需的外部库不存在,某些存储后端可能不可用。 例如,只有安装了botocoreboto库(Scrapy仅支持Python 2上的boto)时,S3后端才可用。

存储URI参数

存储URI还可以包含在创建文件时被替换的参数。 这些参数是:

  • %(time)s - 在创建文件时替换时间戳
  • %(name)s - 替换Spider名

任何其他命名参数将被相同名称的spider属性替换。 例如,在文件被创建的那一刻,%(site_id)s将被替换为spider.site_id属性。

以下是一些例子来说明:

  • 每个Spider使用单独一个目录存储在FTP中:
    • ftp://user:password@ftp.example.com/scraping/feeds/%(name)s/%(time)s.json
  • 每个Spider使用单独一个目录在S3中存储:
    • s3://mybucket/scraping/feeds/%(name)s/%(time)s.json

存储后端

本地文件系统

将文件存储在本地文件系统中。

  • URI方案:file
  • 示例URI:file:///tmp/export.csv
  • 所需的外部库:无

请注意,对于本地文件系统存储(仅限于),如果您指定像/tmp/export.csv这样的绝对路径,则可以省略该方案。 但这只适用于Unix系统。

FTP

将文件存储在FTP服务器中。

  • URI方案:ftp
  • 示例URI:ftp://user:pass@ftp.example.com/path/to/export.csv
  • 所需的外部库:无
S3

将文件存储在Amazon S3上。

  • URI方案:s3
  • 示例URI:
    • s3://mybucket/path/to/export.csv
    • s3://aws_key:aws_secret@mybucket/path/to/export.csv
  • 所需的外部库:botocoreboto

AWS凭证可以作为URI中的用户/密码传递,也可以通过以下设置传递:

标准输出

文件被写入Scrapy进程的标准输出。

  • URI方案:stdout
  • 示例URI: stdout:
  • 所需的外部库:无

设置

这些是用于配置文件输出的设置:

FEED_URI

默认值:None

导出文件的URI。 有关支持的URI方案,请参阅存储后端

此设置对于启用文件输出是必需的。

FEED_FORMAT

用于文件的序列化格式。 有关可能的值,请参阅序列化格式

FEED_EXPORT_ENCODING

默认值:None

要用于文件的编码。

如果未设置或设置为None(默认),则对于除JSON输出外的所有内容都使用UTF-8,因为历史原因,该输出使用安全数字编码(\ uXXXX转义字符)。

如果您还想为JSON使用UTF-8,请使用utf-8

FEED_EXPORT_FIELDS

默认值:None

要导出的字段列表,可选。 示例:FEED_EXPORT_FIELDS = ["foo", "bar", "baz"].

使用FEED_EXPORT_FIELDS选项来定义要导出的字段及其顺序。

当FEED_EXPORT_FIELDS为空或None(默认值)时,Scrapy使用字典中定义的字段或Spider产生的Item子类。

如果导出器需要一组固定的字段(这是CSV导出格式的情况),并且FEED_EXPORT_FIELDS为空或None,则Scrapy会尝试从导出的​​数据中推断字段名称 - 目前它使用第一个Item的字段名称。

FEED_EXPORT_INDENT

默认值:0

每一级缩进输出的空格数。 如果FEED_EXPORT_INDENT是一个非负整数,则数组元素和对象成员将与该缩进级别相匹配。 缩进级别0(默认值)或负值会将每个Item放在新行中。 None选择最紧凑的表示。

目前仅通过JsonItemExporterXmlItemExporter实现,即当您导出到.json.xml时。

FEED_STORE_EMPTY

默认值:False

是否导出空的文件(即. 没有Item的文件)。

FEED_STORAGES

默认值:{}

包含项目支持的附加的后端存储的字典。 关键字是URI方案,值是存储类的路径。

FEED_STORAGES_BASE

默认:

{
    '': 'scrapy.extensions.feedexport.FileFeedStorage',
    'file': 'scrapy.extensions.feedexport.FileFeedStorage',
    'stdout': 'scrapy.extensions.feedexport.StdoutFeedStorage',
    's3': 'scrapy.extensions.feedexport.S3FeedStorage',
    'ftp': 'scrapy.extensions.feedexport.FTPFeedStorage',
}

包含Scrapy支持的内置文件存储后端的字典。 您可以通过在FEED_STORAGES中为其URI方案分配None来禁用这些后端中的任何一个。 例如,要禁用内置FTP存储后端(无需替换),请将它放在settings.py中:

FEED_STORAGES = {
    'ftp': None,
}
FEED_EXPORTERS

默认值:{}

包含您的项目支持的其他导出器的字典。 关键字是序列化格式,值是Item导出器类的路径。

FEED_EXPORTERS_BASE

默认:

{
    'json': 'scrapy.exporters.JsonItemExporter',
    'jsonlines': 'scrapy.exporters.JsonLinesItemExporter',
    'jl': 'scrapy.exporters.JsonLinesItemExporter',
    'csv': 'scrapy.exporters.CsvItemExporter',
    'xml': 'scrapy.exporters.XmlItemExporter',
    'marshal': 'scrapy.exporters.MarshalItemExporter',
    'pickle': 'scrapy.exporters.PickleItemExporter',
}

包含Scrapy支持的内置文件导出器的字典。 您可以通过在FEED_EXPORTERS中将None分配给它们的序列化格式来禁用这些导出器中的任何一个。 例如,要禁用内置的CSV导出器(无需替换),请将它放在settings.py中:

FEED_EXPORTERS = {
    'csv': None,
}

请求和响应

Scrapy使用RequestResponse对象来抓取网站。

通常,在Spider中生成Request对象,跨系统传递直到它们到达Downloader,Downloader执行请求并返回一个Response对象给发出请求的Spider。

RequestResponse类都有子类,它们添加了基类中非必需的功能。 这些在请求子类响应子类中描述。

请求对象

class scrapy.http.Request(url[, callback, method='GET', headers, body, cookies, meta, encoding='utf-8', priority=0, dont_filter=False, errback, flags])

一个Request对象表示一个HTTP请求,它通常在Spider中生成并由Downloader执行,从而生成一个Response

参数:
  • url (string) – 请求的网址
  • callback (callable) - 将此请求的响应(下载完成后)作为其第一个参数调用的函数。 有关更多信息,请参阅下面的将附加数据传递给回调函数 如果请求没有指定回调,则将使用Spider的parse()方法。 请注意,如果在处理期间引发异常,则会调用errback。
  • method (string) – 请求的HTTP方法. 默认为'GET'.
  • meta (dict) – Request.meta属性的初始值. 如果给出,在此参数中传递的字典将被浅拷贝。
  • body (str or unicode) - 请求正文。 如果传递了一个unicode,那么它将被编码为相应的(默认为utf-8)str 如果未给出body,则会存储空字符串。 无论此参数的类型如何,存储的最终值都将是str(不会是unicodeNone)。
  • headers (dict) - 请求的头文件。 字典值可以是字符串(对于单值标题)或列表(对于多值标题)。 如果将None作为值传递,则不会发送HTTP头文件。
  • cookies (dict or list) –

    请求的cookies。 可以以两种形式发送。

    1. 使用字典:
      request_with_cookies = Request(url="http://www.example.com",
                                     cookies={'currency': 'USD', 'country': 'UY'})
      
    2. 使用字典列表:
      request_with_cookies = Request(url="http://www.example.com",
                                     cookies=[{'name': 'currency',
                                              'value': 'USD',
                                              'domain': 'example.com',
                                              'path': '/currency'}])
      

    后一种形式允许定制cookie的domainpath属性。 这仅在cookie被保存用于以后的请求时才有用。

    当某个站点返回(在响应中)cookie时,这些cookie将存储在该域的cookie中,并将在未来的请求中再次发送。 这是任何常规Web浏览器的典型行为。 但是,如果出于某种原因想要避免与现有Cookie合并,可以通过在Request.meta中将dont_merge_cookies键设置为True来指示Scrapy执行此操作。

    不合并Cookie的请求示例:

    request_with_cookies = Request(url="http://www.example.com",
                                   cookies={'currency': 'USD', 'country': 'UY'},
                                   meta={'dont_merge_cookies': True})
    

    有关更多信息,请参阅CookiesMiddleware

  • encoding (string) - 请求的编码(默认为'utf-8')。 该编码将用于对URL进行百分比编码并将主体转换为str(如果以unicode的形式给出)。
  • priority (int) – 请求的优先级(默认为0). 调度程序使用优先级来定义处理请求的顺序。 具有较高优先级值的请求将更早执行。 允许用负值表示相对低的优先级。
  • dont_filter (boolean) - 表示此请求不应被调度程序过滤。 当您想多次执行相同的请求时使用此选项以忽略重复过滤器。 小心使用它,否则你将进入爬取循环。 默认为False.
  • errback (callable) - 如果在处理请求时引发异常,将会调用该函数。 这包括404 HTTP错误等失败的页面。 它接受一个Twisted Failure实例作为第一个参数。 有关更多信息,请参阅下面的使用errbacks捕获请求处理中的异常
  • flags (list) - 发送到请求的标志,可用于日志记录或类似目的。
url

包含此请求的URL的字符串。 请记住,此属性包含被转义的URL,因此它可能与构造函数中传递的URL不同。

该属性是只读的。 要更改请求的URL,请使用replace()

method

表示请求中的HTTP方法的字符串。 确保它是大写的。 例: "GET", "POST", "PUT"

headers

一个包含请求头文件的类似字典的对象。

body

包含请求主体的str。

该属性是只读的。 要更改请求的主体,请使用replace()

meta

包含此请求的任意元数据的字典。 对于新的请求这个字典是空的,通常由不同的Scrapy组件(扩展,中间件等)填充。 因此,此字典中包含的数据取决于您启用的扩展。

有关由Scrapy识别的特殊元键列表,请参阅Request.meta特殊键

当使用copy()replace()方法克隆请求时,该字典被浅拷贝,同时也可以在您的Spider中通过response.meta属性访问。

copy()

返回一个新请求,它是此请求的副本。 另请参阅:将其他数据传递给回调函数

replace([url, method, headers, body, cookies, meta, encoding, dont_filter, callback, errback])

使用相同的成员返回Request对象,但通过指定关键字参数给予新值的成员除外。 属性Request.meta默认复制(除非在meta参数中给出新值)。 另请参阅将其他数据传递给回调函数

将其他数据传递给回调函数

请求的回调函数将在该请求的响应下载完成时调用。 回调函数将以下载的Response对象作为第一个参数进行调用。

例:

def parse_page1(self, response):
    return scrapy.Request("http://www.example.com/some_page.html",
                          callback=self.parse_page2)

def parse_page2(self, response):
    # this would log http://www.example.com/some_page.html
    self.logger.info("Visited %s", response.url)

在某些情况下,您可能有兴趣将参数传递给这些回调函数,以便稍后在第二个回调函数中接收参数。 您可以使用Request.meta属性。

以下是如何使用此机制传递Item以填充不同页面的不同字段的示例:

def parse_page1(self, response):
    item = MyItem()
    item['main_url'] = response.url
    request = scrapy.Request("http://www.example.com/some_page.html",
                             callback=self.parse_page2)
    request.meta['item'] = item
    yield request

def parse_page2(self, response):
    item = response.meta['item']
    item['other_url'] = response.url
    yield item
使用errbacks在请求处理中捕获异常

errback是当处理请求发生异常时调用的函数。

它收到一个Twisted Failure实例作为第一个参数,可用于跟踪连接建立超时,DNS错误等。

以下是一个Spider日志记录所有错误并在需要时捕获一些特定错误的示例:

import scrapy

from scrapy.spidermiddlewares.httperror import HttpError
from twisted.internet.error import DNSLookupError
from twisted.internet.error import TimeoutError, TCPTimedOutError

class ErrbackSpider(scrapy.Spider):
    name = "errback_example"
    start_urls = [
        "http://www.httpbin.org/",              # HTTP 200 expected
        "http://www.httpbin.org/status/404",    # Not found error
        "http://www.httpbin.org/status/500",    # server issue
        "http://www.httpbin.org:12345/",        # non-responding host, timeout expected
        "http://www.httphttpbinbin.org/",       # DNS error expected
    ]

    def start_requests(self):
        for u in self.start_urls:
            yield scrapy.Request(u, callback=self.parse_httpbin,
                                    errback=self.errback_httpbin,
                                    dont_filter=True)

    def parse_httpbin(self, response):
        self.logger.info('Got successful response from {}'.format(response.url))
        # do something useful here...

    def errback_httpbin(self, failure):
        # log all failures
        self.logger.error(repr(failure))

        # in case you want to do something special for some errors,
        # you may need the failure's type:

        if failure.check(HttpError):
            # these exceptions come from HttpError spider middleware
            # you can get the non-200 response
            response = failure.value.response
            self.logger.error('HttpError on %s', response.url)

        elif failure.check(DNSLookupError):
            # this is the original request
            request = failure.request
            self.logger.error('DNSLookupError on %s', request.url)

        elif failure.check(TimeoutError, TCPTimedOutError):
            request = failure.request
            self.logger.error('TimeoutError on %s', request.url)

Request.meta特殊键

Request.meta属性可以包含任何数据,但Scrapy及其内置扩展可识别一些特殊的键。

那些是:

bindaddress

用于执行请求的传出IP地址的IP。

download_timeout

下载器在超时之前等待的时间(以秒为单位)。 另请参阅:DOWNLOAD_TIMEOUT

download_latency

从请求开始以来(即通过网络发送HTTP消息)获取响应所花费的时间量。 这个元键只有在响应被下载后才可用。 虽然大多数其他元键用于控制Scrapy行为,但它是只读的。

download_fail_on_dataloss

响应是否失败。 请参阅:DOWNLOAD_FAIL_ON_DATALOSS

max_retry_times

这个元键用于设置每个请求的重试次数。 初始化时,max_retry_times元键优先于RETRY_TIMES设置。

请求子类

这里是内置Request子类的列表。 您也可以将其子类化以实现您的自定义功能。

FormRequest对象

FormRequest类在Request基础上扩展了处理HTML表单的功能。 它使用lxml.html表单预先填充来自Response对象的表单数据的表单字段。

class scrapy.http.FormRequest(url[, formdata, ...])

FormRequest类的构造函数添加了一个新参数。 其余的参数与Request类相同,这里不再说明。

参数:formdata (字典 元组的迭代) - 是一个包含HTML表单数据的字典(或可迭代的(键,值)元组),这些数据将被url编码并分配给请求的主体。

除了标准的Request方法外,FormRequest对象还支持以下类方法:

classmethod from_response(response[, formname=None, formid=None, formnumber=0, formdata=None, formxpath=None, formcss=None, clickdata=None, dont_click=False, ...])

返回一个新的FormRequest对象,它的表单字段值预先填充在给定响应包含的HTML<form>元素中。 有关示例,请参阅使用FormRequest.from_response()模拟用户登录

默认情况下会自动模拟任何可点击的窗体控件上的点击,如<input type="submit">. 尽管这很方便,而且通常是所需的行为,但有时它可能会导致难以调试的问题。 例如,处理使用javascript填充和/或提交的表单时,默认的from_response()行为可能不是最合适的。 要禁用此行为,可以将dont_click参数设置为True 另外,如果要更改点击的控件(而不是禁用它),还可以使用clickdata参数。

警告

由于lxml中的BUG,在选项值中具有前导空白或尾随空白的select元素使用此方法将不起作用,这将在lxml 3.8及更高版本中修复。

参数:
  • response (Response object) - 包含将用于预填充表单字段的HTML表单的响应
  • formname (string) - 如果给定,将使用name属性为给定值的表单
  • formid (string) – 如果给定,将使用id属性为给定值的表单
  • formxpath (string) – 如果给定, 将使用xpath匹配的第一个表单
  • formcss (string) – 如果给定,将使用css选择器匹配的第一个表单
  • formnumber (integer) - 当响应包含多个表单时要使用的表单编号. 第一个(也是默认值)是0
  • formdata (dict) - 要在表单数据中覆盖的字段。 如果一个字段已经存在于响应<form>元素中,这个字段的值将被参数传递的值覆盖. 如果在此参数中传递的值是None,则该字段将不会被包含在请求中,即使它存在于响应的<form>元素中
  • clickdata (dict) - 用于查找被点击控件的属性。 如果没有给出,表单数据将被模拟点击第一个可点击的元素提交。 除了html属性之外,还可以使用nr属性通过相对于表单内其他可提交输入控件从零开始的索引来标识控件。
  • dont_click (boolean) - 如果为True,将不点击任何控件提交表单数据。

这个类方法的其他参数直接传递给FormRequest构造函数。

版本0.10.3中的新增内容: formname参数。

版本0.17中的新增内容: formxpath参数。

版本1.1.0中的新增内容: formcss参数。

版本1.1.0中的新增内容: formid参数。

请求使用示例
使用FormRequest通过HTTP POST发送数据

如果你想在Spider中模拟一个HTML表单POST并发送一些键值字段,你可以像这样返回一个FormRequest对象(从你的Spider中):

return [FormRequest(url="http://www.example.com/post/action",
                    formdata={'name': 'John Doe', 'age': '27'},
                    callback=self.after_post)]
使用FormRequest.from_response()模拟用户登录

网站通常通过<input type="hidden">元素提供预先填写的表单字段,例如与会话相关的数据或身份验证令牌(用于登录页). 在抓取时,您想要自动预填这些字段,仅覆盖其中的几个字段,例如用户名和密码。 您可以使用FormRequest.from_response()方法达到这一目的。 这是一个使用它的Spider示例:

import scrapy

class LoginSpider(scrapy.Spider):
    name = 'example.com'
    start_urls = ['http://www.example.com/users/login.php']

    def parse(self, response):
        return scrapy.FormRequest.from_response(
            response,
            formdata={'username': 'john', 'password': 'secret'},
            callback=self.after_login
        )

    def after_login(self, response):
        # check login succeed before going on
        if "authentication failed" in response.body:
            self.logger.error("Login failed")
            return

        # continue scraping with authenticated session...

响应对象

class scrapy.http.Response(url[, status=200, headers=None, body=b'', flags=None, request=None])

一个Response对象表示一个HTTP响应,它通常被下载(由下载器)并且被馈送给Spider进行处理。

参数:
  • url (string) - 此响应的网址
  • status (integer) - 响应的HTTP状态。 默认为200
  • headers (dict) - 此响应的头文件。 字典值可以是字符串(对于单值头文件)或列表(对于多值头文件)。
  • body (bytes) - 响应正文。 要以str(Python 2中的unicode)的形式访问解码后的文本,可以使用自适应编码的Response子类response.text,例如TextResponse
  • flags(list) - 是包含Response.flags属性初始值的列表。 如果给出,列表将被浅拷贝。
  • request (Request 对象) - Response.request属性的初始值。 这表示生成此响应的Request
url

一个包含响应URL的字符串。

该属性是只读的。 要更改响应的URL,请使用replace()

status

表示响应的HTTP状态的整数。 例如:200404

headers

一个包含响应头文件的类似字典的对象。 可以使用get()返回具有指定名称的第一个头文件值或getlist()返回具有指定名称的所有头文件值。 例如,这个调用会给你头文件中的所有Cookie:

response.headers.getlist('Set-Cookie')
body

这个响应的主体。 请注意,Response.body始终是一个字节对象。 如果您想要unicode版本可以使用TextResponse.text(仅在TextResponse和子类中可用)。

该属性是只读的。 要更改Response的主体,请使用replace()

request

生成此响应的Request对象。 当响应和请求已经通过所有Downloader Middlewares之后,在Scrapy引擎中分配此属性。 特别是,这意味着:

  • HTTP重定向会将原始请求(重定向前的URL)分配给重定向的响应(重定向后使用最终的URL)。
  • Response.request.url并不总是等于Response.url
  • 该属性仅在spider代码和Spider Middlewares中可用,但不能在Downloader Middleware中(尽管您可以通过其他方式获得请求)和response_downloaded信号处理程序中使用.
meta

Response.request对象的Request.meta属性的快捷方式(即 self.request.meta).

Response.request属性不同,Response.meta属性在重定向和重试之间传递,因此你将获得你的Spider发送的原始Request.meta数据。

也可以看看

Request.meta属性

flags

包含此响应标识的列表。 标识是用于标记响应的标签。 例如:'cached''redirected'等等。它们显示在Response的字符串表示中(__ str __方法)用来记录引擎。

copy()

返回一个新的Response,它是Response的副本。

replace([url, status, headers, body, request, flags, cls])

使用相同的成员返回一个Response对象,除了那些由指定的关键字参数赋予新值的成员。 属性Response.meta默认被复制。

urljoin(url)

通过将响应的url与可能的相对网址结合,构建绝对网址。

这是 urlparse.urljoin的一个包装,它仅仅是一个用于进行此调用的别名:

urlparse.urljoin(response.url, url)
follow(url, callback=None, method='GET', headers=None, body=None, cookies=None, meta=None, encoding='utf-8', priority=0, dont_filter=False, errback=None)

返回一个Request实例以follow链接url 它接受与Request .__ init __方法相同的参数,但url不仅仅是绝对URL,还可以是相对URL或scrapy.link.Link对象,。

TextResponse提供了一个follow()方法,除了绝对/相对URL和链接对象以外,还支持选择器。

响应子类

以下是可用的内置Response子类的列表。 您也可以继承Response类来实现您自己的功能。

TextResponse对象
class scrapy.http.TextResponse(url[, encoding[, ...]])

TextResponse对象为基本Response类添加了编码功能,这意味着该类仅用于二进制数据,如图像,声音或任何媒体文件。

TextResponse对象在基础Response对象之上还添加了新的构造函数参数。 其余功能与Response类相同,这里不再赘述。

参数:encoding (string) - 是一个包含此响应编码的字符串。 如果使用unicode主体创建一个TextResponse对象,它将使用此编码进行编码(记住body属性始终是一个字符串)。 如果encodingNone(默认值),则将在响应头文件和正文中查找编码。

除了标准的Response对象外,TextResponse对象还支持以下属性:

text

响应主体,如unicode。

response.body.decode(response.encoding)相同,但结果在第一次调用后被缓存,因此您可以多次访问response.text而无需额外的开销。

注意

unicode(response.body)不是将响应主体转换为unicode的正确方法:您将使用系统默认编码(通常为ascii)代替响应编码。

encoding

一个包含响应编码的字符串。 顺序地通过以下机制尝试解决编码问题:

  1. 传递给构造函数encoding参数的编码
  2. 在Content-Type HTTP头中声明的编码。 如果此编码无效(即. 未知),它将被被忽略,尝试下一个解决机制。
  3. 在响应正文中声明的编码。 TextResponse类没有为此提供任何特殊功能。 但是,HtmlResponseXmlResponse类可以。
  4. 通过查看响应主体来推断编码。 这是更脆弱的方法,但也是最后的尝试。
selector

用响应作为目标的Selector实例。 第一次访问时,选择器是延迟实例化的。

除了标准的Response之外,TextResponse对象还支持以下方法:

xpath(query)

TextResponse.selector.xpath(query)的快捷方式:

response.xpath('//p')
css(query)

TextResponse.selector.css(query)的快捷方式:

response.css('p')
follow(url, callback=None, method='GET', headers=None, body=None, cookies=None, meta=None, encoding=None, priority=0, dont_filter=False, errback=None)

返回一个Request实例以follow链接url 它接受与Request .__ init __方法相同的参数,但url不仅可以是绝对URL,还可以是

  • 相对URL;
  • scrapy.link.Link对象(例如链接提取器结果);
  • 属性选择器(不是选择器列表) - 例如response.css('a::attr(href)')[0]response.xpath('//img/@src')[0]
  • <a><link>元素的选择器,例如response.css('a.my_link')[0].

有关用法示例,请参阅创建请求的快捷方式

body_as_unicode()

text相同,但可作为方法使用。 这个方法保持向后兼容;请优先使用response.text

HtmlResponse对象
class scrapy.http.HtmlResponse(url[, ...])

HtmlResponse类是TextResponse的一个子类,添加了通过查看HTML meta http-equiv属性自动发现支持编码。 参见TextResponse.encoding

XmlResponse对象
class scrapy.http.XmlResponse(url[, ...])

XmlResponse类是TextResponse的一个子类,添加了通过查看XML声明行来自动发现支持编码。 参见TextResponse.encoding

设置

Scrapy设置允许您自定义所有Scrapy组件的行为,包括核心,扩展,管道和Spider本身。

设置的基础结构提供了代码可用于从中提取配置值的键值映射的全局名称空间。 这些设置可以通过不同的机制进行填充,下面将对此进行介绍。

这些设置也是选择当前活动Scrapy项目的机制(假设你有很多)。

有关可用内置设置的列表,请参阅:内置设置参考

指定设置

当你使用Scrapy时,你必须告诉它你要使用哪些设置。 您可以通过使用环境变量SCRAPY_SETTINGS_MODULE来完成此操作。

SCRAPY_SETTINGS_MODULE的值应该是Python路径语法,myproject.settings 请注意,设置模块应该位于Python 导入搜索路径中。

填充设置

可以使用不同的机制来填充设置,每种机制都有不同的优先级。 以下是按优先级降序排列的列表:

  1. 命令行选项(最优先)
  2. 每个Spider的设置
  3. 项目设置模块
  4. 每个命令的默认设置
  5. 默认的全局设置(优先级最低)

这些设置源总体在内部得到了处理,但使用API​​调用可以手动处理。 请参阅设置API主题。

下面更详细地对这些机制进行描述。

1. 命令行选项

命令行提供的参数是最优先的参数,覆盖任何其他选项。 您可以使用-s (或 --set)命令行选项明确地覆盖一个(或多个)设置。

例:

scrapy crawl myspider -s LOG_FILE=scrapy.log
2. 每个Spider的设置

Spider(请参阅Spider章节以供参考)可以定义它们自己的设置,这将优先考虑并覆盖项目的设置。 他们可以通过设置custom_settings属性来完成此操作:

class MySpider(scrapy.Spider):
    name = 'myspider'

    custom_settings = {
        'SOME_SETTING': 'some value',
    }
3. 项目设置模块

项目设置模块是Scrapy项目的标准配置文件,它是大多数自定义设置将被填充的地方。 对于标准Scrapy项目,这意味着您将添加或更改为您的项目创建的settings.py文件中的设置。

4. 每个命令的默认设置

每个Scrapy工具命令都可以有自己的默认设置,它们覆盖全局默认设置。 这些自定义命令设置在命令类的default_settings属性中指定。

5. 默认全局设置

全局默认值位于scrapy.settings.default_settings模块中,内置设置参考部分中有所记录。

如何访问设置

在Spider中,这些设置可以通过self.settings获得:

class MySpider(scrapy.Spider):
    name = 'myspider'
    start_urls = ['http://example.com']

    def parse(self, response):
        print("Existing settings: %s" % self.settings.attributes.keys())

注意

在spider初始化后,settings属性在基本Spider类中设置。 如果你想在初始化之前使用这些设置(例如,在你的Spider的__init__()方法中),你需要重载from_crawler()方法。

可以通过扩展,中间件和项目管道中传递给from_crawler方法的Crawler的scrapy.crawler.Crawler.settings属性访问设置:

class MyExtension(object):
    def __init__(self, log_is_enabled=False):
        if log_is_enabled:
            print("log is enabled!")

    @classmethod
    def from_crawler(cls, crawler):
        settings = crawler.settings
        return cls(settings.getbool('LOG_ENABLED'))

设置对象可以像字典一样使用(例如settings['LOG_ENABLED']),但通常最好使用Settings提供的API提取您需要的设置格式以避免类型错误。

设置名称的基本原理

设置名称通常以它们配置的组件为前缀。 例如,虚构的robots.txt扩展的正确设置名称将是ROBOTSTXT_ENABLEDROBOTSTXT_OBEYROBOTSTXT_CACHEDIR等。

内置设置参考

以下按字母顺序列出了所有可用的Scrapy设置,以及它们的默认值和应用范围。

范围(如果可用)显示设置的使用位置,是否与任何特定组件绑定。 在这种情况下,将显示该组件的模块,通常是扩展,中间件或管道。 这也意味着必须启用组件才能使设置发挥作用。

AWS_ACCESS_KEY_ID

默认: None

需要访问Amazon Web服务的代码使用的AWS访问秘钥,例如S3文件存储后端

AWS_SECRET_ACCESS_KEY

默认: None

需要访问Amazon Web服务的代码使用的AWS密钥,例如S3文件存储后端

BOT_NAME

默认: 'scrapybot'

此Scrapy项目实现的bot的名称(也称为项目名称)。 这将用于默认构建User-Agent,也用于记录。

当您使用startproject命令创建项目时,它会自动填充项目名称。

CONCURRENT_ITEMS

默认: 100

在Item处理器(也称为Item管道)中并行处理的最大并发Item数(每个响应)。

CONCURRENT_REQUESTS

默认: 16

最大并发(即. 同时)将由Scrapy下载器执行的请求数。

CONCURRENT_REQUESTS_PER_DOMAIN

默认:8

最大并发(即. 同时)将被执行到任何单个域的请求数。

另见:AutoThrottle扩展及其AUTOTHROTTLE_TARGET_CONCURRENCY选项。

CONCURRENT_REQUESTS_PER_IP

默认: 0

最大并发(即. 同时)将对任何单个IP执行的请求数。 如果非零,则忽略CONCURRENT_REQUESTS_PER_DOMAIN设置,并使用此设置。 换言之,并发限制将应用于每个IP,而不是每个域。

此设置还会影响DOWNLOAD_DELAYAutoThrottle扩展:如果CONCURRENT_REQUESTS_PER_IP不为零,则下载延迟会针对每个IP强制执行,而不是每个域强制执行。

DEFAULT_ITEM_CLASS

Default: 'scrapy.item.Item'

用于在Scrapy shell中实例化Item的默认类。

DEFAULT_REQUEST_HEADERS

Default:

{
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
}

用于Scrapy HTTP请求的默认头文件。 它们被填充到DefaultHeadersMiddleware中。

DEPTH_LIMIT

Default: 0

Scope: scrapy.spidermiddlewares.depth.DepthMiddleware

The maximum depth that will be allowed to crawl for any site. If zero, no limit will be imposed.

DEPTH_PRIORITY

Default: 0

Scope: scrapy.spidermiddlewares.depth.DepthMiddleware

An integer that is used to adjust the request priority based on its depth:

  • if zero (default), no priority adjustment is made from depth
  • a positive value will decrease the priority, i.e. higher depth requests will be processed later ; this is commonly used when doing breadth-first crawls (BFO)
  • a negative value will increase priority, i.e., higher depth requests will be processed sooner (DFO)

See also: Does Scrapy crawl in breadth-first or depth-first order? about tuning Scrapy for BFO or DFO.

Note

This setting adjusts priority in the opposite way compared to other priority settings REDIRECT_PRIORITY_ADJUST and RETRY_PRIORITY_ADJUST.

DEPTH_STATS

Default: True

Scope: scrapy.spidermiddlewares.depth.DepthMiddleware

Whether to collect maximum depth stats.

DEPTH_STATS_VERBOSE

Default: False

Scope: scrapy.spidermiddlewares.depth.DepthMiddleware

Whether to collect verbose depth stats. If this is enabled, the number of requests for each depth is collected in the stats.

DNSCACHE_ENABLED

Default: True

Whether to enable DNS in-memory cache.

DNSCACHE_SIZE

Default: 10000

DNS in-memory cache size.

DNS_TIMEOUT

Default: 60

Timeout for processing of DNS queries in seconds. Float is supported.

DOWNLOADER

Default: 'scrapy.core.downloader.Downloader'

The downloader to use for crawling.

DOWNLOADER_HTTPCLIENTFACTORY

Default: 'scrapy.core.downloader.webclient.ScrapyHTTPClientFactory'

Defines a Twisted protocol.ClientFactory class to use for HTTP/1.0 connections (for HTTP10DownloadHandler).

Note

HTTP/1.0 is rarely used nowadays so you can safely ignore this setting, unless you use Twisted<11.1, or if you really want to use HTTP/1.0 and override DOWNLOAD_HANDLERS_BASE for http(s) scheme accordingly, i.e. to 'scrapy.core.downloader.handlers.http.HTTP10DownloadHandler'.

DOWNLOADER_CLIENTCONTEXTFACTORY

Default: 'scrapy.core.downloader.contextfactory.ScrapyClientContextFactory'

Represents the classpath to the ContextFactory to use.

Here, “ContextFactory” is a Twisted term for SSL/TLS contexts, defining the TLS/SSL protocol version to use, whether to do certificate verification, or even enable client-side authentication (and various other things).

Note

Scrapy default context factory does NOT perform remote server certificate verification. This is usually fine for web scraping.

If you do need remote server certificate verification enabled, Scrapy also has another context factory class that you can set, 'scrapy.core.downloader.contextfactory.BrowserLikeContextFactory', which uses the platform’s certificates to validate remote endpoints. This is only available if you use Twisted>=14.0.

If you do use a custom ContextFactory, make sure it accepts a method parameter at init (this is the OpenSSL.SSL method mapping DOWNLOADER_CLIENT_TLS_METHOD).

DOWNLOADER_CLIENT_TLS_METHOD

Default: 'TLS'

Use this setting to customize the TLS/SSL method used by the default HTTP/1.1 downloader.

This setting must be one of these string values:

  • 'TLS': maps to OpenSSL’s TLS_method() (a.k.a SSLv23_method()), which allows protocol negotiation, starting from the highest supported by the platform; default, recommended
  • 'TLSv1.0': this value forces HTTPS connections to use TLS version 1.0 ; set this if you want the behavior of Scrapy<1.1
  • 'TLSv1.1': forces TLS version 1.1
  • 'TLSv1.2': forces TLS version 1.2
  • 'SSLv3': forces SSL version 3 (not recommended)

Note

We recommend that you use PyOpenSSL>=0.13 and Twisted>=0.13 or above (Twisted>=14.0 if you can).

DOWNLOADER_MIDDLEWARES

默认: {}

包含您项目中启用的下载器中间件和顺序的字典。 有关更多信息,请参阅激活下载中间件

DOWNLOADER_MIDDLEWARES_BASE

默认:

{
    'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
    'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
    'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
    'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
    'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
    'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
    'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
    'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
    'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
    'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
}

包含Scrapy中默认启用的下载器中间件的字典。 低顺序靠近引擎,高顺序靠近下载器。 您不应该在您的项目中修改此设置,而是修改DOWNLOADER_MIDDLEWARES 有关更多信息,请参阅激活下载中间件

DOWNLOADER_STATS

Default: True

Whether to enable downloader stats collection.

DOWNLOAD_DELAY

默认值:0

下载器在从同一网站下载连续页面之前应等待的时间(以秒为单位)。 这可以用来限制爬取速度,避免对服务器造成太大的负担。 支持小数。 例:

DOWNLOAD_DELAY = 0.25    # 250 ms of delay

此设置还受到RANDOMIZE_DOWNLOAD_DELAY设置(默认情况下启用)的影响。 默认情况下,Scrapy不会在两次请求之间等待一段固定的时间,而是使用0.5 * DOWNLOAD_DELAY和1.5 * DOWNLOAD_DELAY之间的随机时间间隔。

CONCURRENT_REQUESTS_PER_IP非零时,延迟按每个IP地址而不是每个域强制执行。

您还可以通过设置download_delay spider属性来更改每个Spider的此项设置。

DOWNLOAD_HANDLERS

默认值:{}

包含在您的项目中启用的请求下载器处理程序的字典。 DOWNLOAD_HANDLERS_BASE的示例。

DOWNLOAD_HANDLERS_BASE

默认:

{
    'file': 'scrapy.core.downloader.handlers.file.FileDownloadHandler',
    'http': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler',
    'https': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler',
    's3': 'scrapy.core.downloader.handlers.s3.S3DownloadHandler',
    'ftp': 'scrapy.core.downloader.handlers.ftp.FTPDownloadHandler',
}

包含Scrapy中默认启用的请求下载处理程序的字典。 您不应该在您的项目中修改此设置,而是修改DOWNLOAD_HANDLERS

您可以通过在DOWNLOAD_HANDLERS中将None分配给其URI方案来禁用任何这些下载处理程序。 例如,要禁用内置的FTP处理程序(无需替换),请将它放在settings.py中:

DOWNLOAD_HANDLERS = {
    'ftp': None,
}
DOWNLOAD_TIMEOUT

Default: 180

The amount of time (in secs) that the downloader will wait before timing out.

Note

This timeout can be set per spider using download_timeout spider attribute and per-request using download_timeout Request.meta key.

DOWNLOAD_MAXSIZE

Default: 1073741824 (1024MB)

The maximum response size (in bytes) that downloader will download.

If you want to disable it set to 0.

Note

This size can be set per spider using download_maxsize spider attribute and per-request using download_maxsize Request.meta key.

This feature needs Twisted >= 11.1.

DOWNLOAD_WARNSIZE

Default: 33554432 (32MB)

The response size (in bytes) that downloader will start to warn.

If you want to disable it set to 0.

Note

This size can be set per spider using download_warnsize spider attribute and per-request using download_warnsize Request.meta key.

This feature needs Twisted >= 11.1.

DOWNLOAD_FAIL_ON_DATALOSS

Default: True

Whether or not to fail on broken responses, that is, declared Content-Length does not match content sent by the server or chunked response was not properly finish. If True, these responses raise a ResponseFailed([_DataLoss]) error. If False, these responses are passed through and the flag dataloss is added to the response, i.e.: 'dataloss' in response.flags is True.

Optionally, this can be set per-request basis by using the download_fail_on_dataloss Request.meta key to False.

Note

A broken response, or data loss error, may happen under several circumstances, from server misconfiguration to network errors to data corruption. It is up to the user to decide if it makes sense to process broken responses considering they may contain partial or incomplete content. If RETRY_ENABLED is True and this setting is set to True, the ResponseFailed([_DataLoss]) failure will be retried as usual.

DUPEFILTER_CLASS

Default: 'scrapy.dupefilters.RFPDupeFilter'

The class used to detect and filter duplicate requests.

The default (RFPDupeFilter) filters based on request fingerprint using the scrapy.utils.request.request_fingerprint function. In order to change the way duplicates are checked you could subclass RFPDupeFilter and override its request_fingerprint method. This method should accept scrapy Request object and return its fingerprint (a string).

You can disable filtering of duplicate requests by setting DUPEFILTER_CLASS to 'scrapy.dupefilters.BaseDupeFilter'. Be very careful about this however, because you can get into crawling loops. It’s usually a better idea to set the dont_filter parameter to True on the specific Request that should not be filtered.

DUPEFILTER_DEBUG

Default: False

By default, RFPDupeFilter only logs the first duplicate request. Setting DUPEFILTER_DEBUG to True will make it log all duplicate requests.

EDITOR

Default: vi (on Unix systems) or the IDLE editor (on Windows)

The editor to use for editing spiders with the edit command. Additionally, if the EDITOR environment variable is set, the edit command will prefer it over the default setting.

EXTENSIONS

默认: {}

一个包含在您的项目中启用的扩展及其顺序的字典。

EXTENSIONS_BASE

默认:

{
    'scrapy.extensions.corestats.CoreStats': 0,
    'scrapy.extensions.telnet.TelnetConsole': 0,
    'scrapy.extensions.memusage.MemoryUsage': 0,
    'scrapy.extensions.memdebug.MemoryDebugger': 0,
    'scrapy.extensions.closespider.CloseSpider': 0,
    'scrapy.extensions.feedexport.FeedExporter': 0,
    'scrapy.extensions.logstats.LogStats': 0,
    'scrapy.extensions.spiderstate.SpiderState': 0,
    'scrapy.extensions.throttle.AutoThrottle': 0,
}

包含Scrapy中默认可用扩展名及其顺序的字典。 该设置包含所有固定内置扩展。 请注意,其中一些需要通过设置启用。

更多信息请参阅扩展用户指南可用扩展列表

FEED_TEMPDIR

The Feed Temp dir allows you to set a custom folder to save crawler temporary files before uploading with FTP feed storage and Amazon S3.

FTP_PASSIVE_MODE

Default: True

Whether or not to use passive mode when initiating FTP transfers.

FTP_PASSWORD

Default: "guest"

The password to use for FTP connections when there is no "ftp_password" in Request meta.

Note

Paraphrasing RFC 1635, although it is common to use either the password “guest” or one’s e-mail address for anonymous FTP, some FTP servers explicitly ask for the user’s e-mail address and will not allow login with the “guest” password.

FTP_USER

Default: "anonymous"

The username to use for FTP connections when there is no "ftp_user" in Request meta.

ITEM_PIPELINES

默认值:{}

包含要使用的Item管道及其顺序的字典。 顺序值是任意的,但习惯上将它们定义在0-1000范围内。 低顺序在高顺序之前处理。

例:

ITEM_PIPELINES = {
    'mybot.pipelines.validate.ValidateMyItem': 300,
    'mybot.pipelines.validate.StoreMyItem': 800,
}
ITEM_PIPELINES_BASE

Default: {}

A dict containing the pipelines enabled by default in Scrapy. You should never modify this setting in your project, modify ITEM_PIPELINES instead.

LOG_ENABLED

Default: True

Whether to enable logging.

LOG_ENCODING

Default: 'utf-8'

The encoding to use for logging.

LOG_FILE

Default: None

File name to use for logging output. If None, standard error will be used.

LOG_FORMAT

Default: '%(asctime)s [%(name)s] %(levelname)s: %(message)s'

String for formatting log messsages. Refer to the Python logging documentation for the whole list of available placeholders.

LOG_DATEFORMAT

Default: '%Y-%m-%d %H:%M:%S'

String for formatting date/time, expansion of the %(asctime)s placeholder in LOG_FORMAT. Refer to the Python datetime documentation for the whole list of available directives.

LOG_LEVEL

Default: 'DEBUG'

Minimum level to log. Available levels are: CRITICAL, ERROR, WARNING, INFO, DEBUG. For more info see Logging.

LOG_STDOUT

Default: False

If True, all standard output (and error) of your process will be redirected to the log. For example if you print 'hello' it will appear in the Scrapy log.

LOG_SHORT_NAMES

Default: False

If True, the logs will just contain the root path. If it is set to False then it displays the component responsible for the log output

MEMDEBUG_ENABLED

Default: False

Whether to enable memory debugging.

MEMDEBUG_NOTIFY

Default: []

When memory debugging is enabled a memory report will be sent to the specified addresses if this setting is not empty, otherwise the report will be written to the log.

Example:

MEMDEBUG_NOTIFY = ['user@example.com']
MEMUSAGE_ENABLED

Default: True

Scope: scrapy.extensions.memusage

Whether to enable the memory usage extension. This extension keeps track of a peak memory used by the process (it writes it to stats). It can also optionally shutdown the Scrapy process when it exceeds a memory limit (see MEMUSAGE_LIMIT_MB), and notify by email when that happened (see MEMUSAGE_NOTIFY_MAIL).

See Memory usage extension.

MEMUSAGE_LIMIT_MB

Default: 0

Scope: scrapy.extensions.memusage

The maximum amount of memory to allow (in megabytes) before shutting down Scrapy (if MEMUSAGE_ENABLED is True). If zero, no check will be performed.

See Memory usage extension.

MEMUSAGE_CHECK_INTERVAL_SECONDS

New in version 1.1.

Default: 60.0

Scope: scrapy.extensions.memusage

The Memory usage extension checks the current memory usage, versus the limits set by MEMUSAGE_LIMIT_MB and MEMUSAGE_WARNING_MB, at fixed time intervals.

This sets the length of these intervals, in seconds.

See Memory usage extension.

MEMUSAGE_NOTIFY_MAIL

Default: False

Scope: scrapy.extensions.memusage

A list of emails to notify if the memory limit has been reached.

Example:

MEMUSAGE_NOTIFY_MAIL = ['user@example.com']

See Memory usage extension.

MEMUSAGE_WARNING_MB

Default: 0

Scope: scrapy.extensions.memusage

The maximum amount of memory to allow (in megabytes) before sending a warning email notifying about it. If zero, no warning will be produced.

NEWSPIDER_MODULE

Default: ''

Module where to create new spiders using the genspider command.

Example:

NEWSPIDER_MODULE = 'mybot.spiders_dev'
RANDOMIZE_DOWNLOAD_DELAY

Default: True

If enabled, Scrapy will wait a random amount of time (between 0.5 * DOWNLOAD_DELAY and 1.5 * DOWNLOAD_DELAY) while fetching requests from the same website.

This randomization decreases the chance of the crawler being detected (and subsequently blocked) by sites which analyze requests looking for statistically significant similarities in the time between their requests.

The randomization policy is the same used by wget --random-wait option.

If DOWNLOAD_DELAY is zero (default) this option has no effect.

REACTOR_THREADPOOL_MAXSIZE

Default: 10

The maximum limit for Twisted Reactor thread pool size. This is common multi-purpose thread pool used by various Scrapy components. Threaded DNS Resolver, BlockingFeedStorage, S3FilesStore just to name a few. Increase this value if you’re experiencing problems with insufficient blocking IO.

REDIRECT_MAX_TIMES

Default: 20

Defines the maximum times a request can be redirected. After this maximum the request’s response is returned as is. We used Firefox default value for the same task.

REDIRECT_PRIORITY_ADJUST

Default: +2

Scope: scrapy.downloadermiddlewares.redirect.RedirectMiddleware

Adjust redirect request priority relative to original request:

  • a positive priority adjust (default) means higher priority.
  • a negative priority adjust means lower priority.
RETRY_PRIORITY_ADJUST

Default: -1

Scope: scrapy.downloadermiddlewares.retry.RetryMiddleware

Adjust retry request priority relative to original request:

  • a positive priority adjust means higher priority.
  • a negative priority adjust (default) means lower priority.
ROBOTSTXT_OBEY

Default: False

Scope: scrapy.downloadermiddlewares.robotstxt

If enabled, Scrapy will respect robots.txt policies. For more information see RobotsTxtMiddleware.

Note

While the default value is False for historical reasons, this option is enabled by default in settings.py file generated by scrapy startproject command.

SCHEDULER

Default: 'scrapy.core.scheduler.Scheduler'

The scheduler to use for crawling.

SCHEDULER_DEBUG

Default: False

Setting to True will log debug information about the requests scheduler. This currently logs (only once) if the requests cannot be serialized to disk. Stats counter (scheduler/unserializable) tracks the number of times this happens.

Example entry in logs:

1956-01-31 00:00:00+0800 [scrapy.core.scheduler] ERROR: Unable to serialize request:
<GET http://example.com> - reason: cannot serialize <Request at 0x9a7c7ec>
(type Request)> - no more unserializable requests will be logged
(see 'scheduler/unserializable' stats counter)
SCHEDULER_DISK_QUEUE

Default: 'scrapy.squeues.PickleLifoDiskQueue'

Type of disk queue that will be used by scheduler. Other available types are scrapy.squeues.PickleFifoDiskQueue, scrapy.squeues.MarshalFifoDiskQueue, scrapy.squeues.MarshalLifoDiskQueue.

SCHEDULER_MEMORY_QUEUE

Default: 'scrapy.squeues.LifoMemoryQueue'

Type of in-memory queue used by scheduler. Other available type is: scrapy.squeues.FifoMemoryQueue.

SCHEDULER_PRIORITY_QUEUE

Default: 'queuelib.PriorityQueue'

Type of priority queue used by scheduler.

SPIDER_CONTRACTS

Default:: {}

A dict containing the spider contracts enabled in your project, used for testing spiders. For more info see Spiders Contracts.

SPIDER_CONTRACTS_BASE

Default:

{
    'scrapy.contracts.default.UrlContract' : 1,
    'scrapy.contracts.default.ReturnsContract': 2,
    'scrapy.contracts.default.ScrapesContract': 3,
}

A dict containing the scrapy contracts enabled by default in Scrapy. You should never modify this setting in your project, modify SPIDER_CONTRACTS instead. For more info see Spiders Contracts.

You can disable any of these contracts by assigning None to their class path in SPIDER_CONTRACTS. E.g., to disable the built-in ScrapesContract, place this in your settings.py:

SPIDER_CONTRACTS = {
    'scrapy.contracts.default.ScrapesContract': None,
}
SPIDER_LOADER_CLASS

Default: 'scrapy.spiderloader.SpiderLoader'

The class that will be used for loading spiders, which must implement the SpiderLoader API.

SPIDER_LOADER_WARN_ONLY

New in version 1.3.3.

Default: False

By default, when scrapy tries to import spider classes from SPIDER_MODULES, it will fail loudly if there is any ImportError exception. But you can choose to silence this exception and turn it into a simple warning by setting SPIDER_LOADER_WARN_ONLY = True.

Note

Some scrapy commands run with this setting to True already (i.e. they will only issue a warning and will not fail) since they do not actually need to load spider classes to work: scrapy runspider, scrapy settings, scrapy startproject, scrapy version.

SPIDER_MIDDLEWARES

默认:: {}

一个包含您的项目中启用的Spider中间件及其顺序的字典。 有关更多信息,请参阅激活Spider中间件

SPIDER_MIDDLEWARES_BASE

默认:

{
    'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50,
    'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': 500,
    'scrapy.spidermiddlewares.referer.RefererMiddleware': 700,
    'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware': 800,
    'scrapy.spidermiddlewares.depth.DepthMiddleware': 900,
}

包含Scrapy中默认启用的Spider中间件及其顺序的字典。 低顺序靠近引擎,高顺序靠近蜘蛛。 有关更多信息,请参阅激活Spider中间件

SPIDER_MODULES

默认:[]

Scrapy寻找Spider的模块列表。

Example:

SPIDER_MODULES = ['mybot.spiders_prod', 'mybot.spiders_dev']
STATS_CLASS

Default: 'scrapy.statscollectors.MemoryStatsCollector'

The class to use for collecting stats, who must implement the Stats Collector API.

STATS_DUMP

Default: True

Dump the Scrapy stats (to the Scrapy log) once the spider finishes.

For more info see: Stats Collection.

STATSMAILER_RCPTS

Default: [] (empty list)

Send Scrapy stats after spiders finish scraping. See StatsMailer for more info.

TELNETCONSOLE_ENABLED

Default: True

A boolean which specifies if the telnet console will be enabled (provided its extension is also enabled).

TELNETCONSOLE_PORT

Default: [6023, 6073]

The port range to use for the telnet console. If set to None or 0, a dynamically assigned port is used. For more info see Telnet Console.

TEMPLATES_DIR

Default: templates dir inside scrapy module

The directory where to look for templates when creating new projects with startproject command and new spiders with genspider command.

The project name must not conflict with the name of custom files or directories in the project subdirectory.

URLLENGTH_LIMIT

Default: 2083

Scope: spidermiddlewares.urllength

The maximum URL length to allow for crawled URLs. For more information about the default value for this setting see: https://boutell.com/newfaq/misc/urllength.html

USER_AGENT

默认: "Scrapy/VERSION (+https://scrapy.org)"

抓取时使用的默认User-Agent,除非被覆盖。

其他地方记录的设置:

以下是其他地方记录的设置,请检查每个特定情况以了解如何启用和使用它们。

异常处理

内置异常处理参考

以下列出了Scrapy中包含的所有异常及其使用情况。

DropItem
exception scrapy.exceptions.DropItem

Item管道阶段必须抛出的异常以停止处理Item。 有关更多信息,请参阅Item管道

CloseSpider
exception scrapy.exceptions.CloseSpider(reason='cancelled')

这个异常可以从Spider回调中抛出以请求关闭/停止Spider。 支持的参数:

参数:reason (str) – 关闭原因

例如:

def parse_page(self, response):
    if 'Bandwidth exceeded' in response.body:
        raise CloseSpider('bandwidth_exceeded')
DontCloseSpider
exception scrapy.exceptions.DontCloseSpider

这个异常可以在spider_idle信号处理程序中引发,以防止Spider被关闭。

IgnoreRequest
exception scrapy.exceptions.IgnoreRequest

调度程序或任何下载器中间件都可以引发此异常,以表明请求应被忽略。

NotConfigured
exception scrapy.exceptions.NotConfigured

某些组件可能会引发此异常,以表明它们将保持禁用状态。 这些组件包括:

  • 扩展
  • Item管道
  • 下载中间件
  • Spider中间件

必须在组件的__init__方法中引发异常。

NotSupported
exception scrapy.exceptions.NotSupported

引发此异常以表明不受支持的功能。

命令行工具
了解用于管理Scrapy项目的命令行工具。
Spiders
编写规则来抓取您的网站。
Selectors
使用XPath从网页中提取数据。
Scrapy shell
在交互式环境中测试您的提取代码。
Items
定义你想要抓取的数据。
Item Loaders
用提取的数据填充Item。
Item Pipeline
后续处理并存储您的抓取数据。
Feed exports
使用不同的格式和存储输出你的数据。
Requests and Responses
了解用于表示HTTP请求和响应的类。
Link Extractors
用来从页面提取要follow的链接的便捷类。
Settings
了解如何配置Scrapy并查看所有可用设置
Exceptions
查看所有可用的异常及其含义。

Built-in services

Logging

注意

scrapy.log已被弃用,它支持显式调用Python标准日志记录。 继续阅读以了解更多关于新日志记录系统的信息。

Scrapy使用Python内置日志记录系统进行事件日志记录。 我们将提供一些简单的示例来帮助您开始,但对于更高级的用例,强烈建议您仔细阅读其文档。

日志功能可以直接使用,并且可以使用日志记录设置中列出的Scrapy设置进行一定程度的配置。

在运行命令时,Scrapy调用scrapy.utils.log.configure_logging()设置一些合理的默认值并处理日志记录设置中的设置,如果您从脚本运行Scrapy,建议您按照从脚本运行Scrapy中所述手动调用它。

日志级别

Python的内置日志定义了5个日志等级来表示日志信息的严重程度 ,以下按严重程度列出了这5个等级:

  1. logging.CRITICAL - 严重错误 (最高严重程度)
  2. logging.ERROR - 普通错误
  3. logging.WARNING - 警告信息
  4. logging.INFO - 普通信息
  5. logging.DEBUG - 调试信息 (最低严重程度)

如何记录日志

以下是记录日志的一个简单例子,使用了logging.WARNING 等级:

import logging
logging.warning("This is a warning")

There are shortcuts for issuing log messages on any of the standard 5 levels, and there’s also a general logging.log method which takes a given level as argument. If needed, the last example could be rewritten as:

import logging
logging.log(logging.WARNING, "This is a warning")

On top of that, you can create different “loggers” to encapsulate messages. (For example, a common practice is to create different loggers for every module). These loggers can be configured independently, and they allow hierarchical constructions.

The previous examples use the root logger behind the scenes, which is a top level logger where all messages are propagated to (unless otherwise specified). Using logging helpers is merely a shortcut for getting the root logger explicitly, so this is also an equivalent of the last snippets:

import logging
logger = logging.getLogger()
logger.warning("This is a warning")

You can use a different logger just by getting its name with the logging.getLogger function:

import logging
logger = logging.getLogger('mycustomlogger')
logger.warning("This is a warning")

Finally, you can ensure having a custom logger for any module you’re working on by using the __name__ variable, which is populated with current module’s path:

import logging
logger = logging.getLogger(__name__)
logger.warning("This is a warning")

See also

Module logging, HowTo
Basic Logging Tutorial
Module logging, Loggers
Further documentation on loggers

Logging from Spiders

Scrapy provides a logger within each Spider instance, which can be accessed and used like this:

import scrapy

class MySpider(scrapy.Spider):

    name = 'myspider'
    start_urls = ['https://scrapinghub.com']

    def parse(self, response):
        self.logger.info('Parse function called on %s', response.url)

That logger is created using the Spider’s name, but you can use any custom Python logger you want. For example:

import logging
import scrapy

logger = logging.getLogger('mycustomlogger')

class MySpider(scrapy.Spider):

    name = 'myspider'
    start_urls = ['https://scrapinghub.com']

    def parse(self, response):
        logger.info('Parse function called on %s', response.url)

Logging configuration

Loggers on their own don’t manage how messages sent through them are displayed. For this task, different “handlers” can be attached to any logger instance and they will redirect those messages to appropriate destinations, such as the standard output, files, emails, etc.

By default, Scrapy sets and configures a handler for the root logger, based on the settings below.

Logging settings

These settings can be used to configure the logging:

The first couple of settings define a destination for log messages. If LOG_FILE is set, messages sent through the root logger will be redirected to a file named LOG_FILE with encoding LOG_ENCODING. If unset and LOG_ENABLED is True, log messages will be displayed on the standard error. Lastly, if LOG_ENABLED is False, there won’t be any visible log output.

LOG_LEVEL determines the minimum level of severity to display, those messages with lower severity will be filtered out. It ranges through the possible levels listed in Log levels.

LOG_FORMAT and LOG_DATEFORMAT specify formatting strings used as layouts for all messages. Those strings can contain any placeholders listed in logging’s logrecord attributes docs and datetime’s strftime and strptime directives respectively.

If LOG_SHORT_NAMES is set, then the logs will not display the scrapy component that prints the log. It is unset by default, hence logs contain the scrapy component responsible for that log output.

Command-line options

There are command-line arguments, available for all commands, that you can use to override some of the Scrapy settings regarding logging.

See also

Module logging.handlers
Further documentation on available handlers
高级定制

Because Scrapy uses stdlib logging module, you can customize logging using all features of stdlib logging.

For example, let’s say you’re scraping a website which returns many HTTP 404 and 500 responses, and you want to hide all messages like this:

2016-12-16 22:00:06 [scrapy.spidermiddlewares.httperror] INFO: Ignoring
response <500 http://