Scrapy 0.25 文档
本文档涵盖了所有Scrapy的内容。
获得帮助
遇到问题了?我们来帮您!
查看下 FAQ ,这里有些常见的问题的解决办法。
寻找详细的信息?试试 索引 或者 模块索引 。
您可以在 scrapy-users的邮件列表 中寻找内容,或者 提问问题
在 #scrapy IRC channel 提问
在 issue tracker 中提交Scrapy的bug
第一步
初窥Scrapy
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了 页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。
本文档将通过介绍Scrapy背后的概念使您对其工作原理有所了解, 并确定Scrapy是否是您所需要的。
当您准备好开始您的项目后,您可以参考 入门教程 。
选择一个网站
当您需要从某个网站中获取信息,但该网站未提供API或能通过程序获取信息的机制时, Scrapy可以助你一臂之力。
以 Mininova 网站为例,我们想要获取今日添加的所有种子的URL、 名字、描述以及文件大小信息。
今日添加的种子列表可以通过这个页面找到:
http://www.mininova.org/today
定义您想抓取的数据
第一步是定义我们需要爬取的数据。在Scrapy中, 这是通过 Scrapy Items 来完成的。(在本例子中为种子文件)
我们定义的Item:
import scrapy
class TorrentItem(scrapy.Item):
url = scrapy.Field()
name = scrapy.Field()
description = scrapy.Field()
size = scrapy.Field()
编写提取数据的Spider
第二步是编写一个spider。其定义了初始URL(http://www.mininova.org/today)、 针对后续链接的规则以及从页面中提取数据的规则。
通过观察页面的内容可以发现,所有种子的URL都类似 http://www.mininova.org/tor/NUMBER 。 其中, NUMBER 是一个整数。 根据此规律,我们可以定义需要进行跟进的链接的正则表达式: /tor/\d+ 。
我们使用 XPath 来从页面的HTML源码中选择需要提取的数据。 以其中一个种子文件的页面为例:
http://www.mininova.org/tor/2676093
观察HTML页面源码并创建我们需要的数据(种子名字,描述和大小)的XPath表达式。
通过观察,我们可以发现文件名是包含在
标签中的:
Darwin - The Evolution Of An Exhibition
与此对应的XPath表达式:
//h1/text()
种子的描述是被包含在 id=”description” 的
Description:
标签中:
Category: Movies > Documentary
Total size: 150.62 megabyte
选择文件大小的XPath表达式: //div[@id=’specifications’]/p[2]/text()[2] 关于XPath的详细内容请参考 XPath参考 。 最后,结合以上内容给出spider的代码: from scrapy.contrib.spiders import CrawlSpider, Rule from scrapy.contrib.linkextractors import LinkExtractor class MininovaSpider(CrawlSpider): name = ‘mininova’ allowed_domains = [‘mininova.org’] start_urls = [‘http://www.mininova.org/today’] rules = [Rule(LinkExtractor(allow=[‘/tor/\d+’]), ‘parse_torrent’)] def parse_torrent(self, response): torrent = TorrentItem() torrent[‘url’] = response.url torrent[‘name’] = response.xpath(“//h1/text()”).extract() torrent[‘description’] = response.xpath(“//div[@id=’description’]”).extract() torrent[‘size’] = response.xpath(“//div[@id=’info-left’]/p[2]/text()[2]”).extract() return torrent TorrentItem 的定义在 上面 。 执行spider,获取数据 终于,我们可以运行spider来获取网站的数据,并以JSON格式存入到 scraped_data.json 文件中: scrapy crawl mininova -o scraped_data.json 命令中使用了 feed导出 来导出JSON文件。您可以修改导出格式(XML或者CSV)或者存储后端(FTP或者 Amazon S3),这并不困难。 同时,您也可以编写 item管道 将item存储到数据库中。 查看提取到的数据 执行结束后,当您查看 scraped_data.json , 您将看到提取到的item: [{“url”: “http://www.mininova.org/tor/2676093”, “name”: [“Darwin - The Evolution Of An Exhibition”], “description”: [“Short documentary made for Plymouth …”], “size”: [“150.62 megabyte”]},… other items …
]
由于 selectors 返回list, 所以值都是以list存储的(除了 url 是直接赋值之外)。 如果您想要保存单个数据或者对数据执行额外的处理,那将是 Item Loaders 发挥作用的地方。
还有什么?
您已经了解了如何通过Scrapy提取存储网页中的信息,但这仅仅只是冰山一角。Scrapy提供了很多强大的特性来使得爬取更为简单高效, 例如:
HTML, XML源数据 选择及提取 的内置支持
提供了一系列在spider之间共享的可复用的过滤器(即 Item Loaders),对智能处理爬取数据提供了内置支持。
通过 feed导出 提供了多格式(JSON、CSV、XML),多存储后端(FTP、S3、本地文件系统)的内置支持
提供了media pipeline,可以 自动下载 爬取到的数据中的图片(或者其他资源)。
高扩展性。您可以通过使用 signals ,设计好的API(中间件, extensions, pipelines)来定制实现您的功能。
内置的中间件及扩展为下列功能提供了支持:
cookies and session 处理
HTTP 压缩
HTTP 认证
HTTP 缓存
user-agent模拟
robots.txt
爬取深度限制
其他
针对非英语语系中不标准或者错误的编码声明, 提供了自动检测以及健壮的编码支持。
支持根据模板生成爬虫。在加速爬虫创建的同时,保持在大型项目中的代码更为一致。详细内容请参阅 genspider 命令。
针对多爬虫下性能评估、失败检测,提供了可扩展的 状态收集工具 。
提供 交互式shell终端 , 为您测试XPath表达式,编写和调试爬虫提供了极大的方便
提供 System service, 简化在生产环境的部署及运行
内置 Telnet终端 ,通过在Scrapy进程中钩入Python终端,使您可以查看并且调试爬虫
Logging 为您在爬取过程中捕捉错误提供了方便
支持 Sitemaps 爬取
具有缓存的DNS解析器
接下来
下一步当然是 下载Scrapy 了, 您可以阅读 入门教程 并加入 社区 。感谢您的支持!
安装指南
安装Scrapy
注解
请先阅读 平台安装指南.
下列的安装步骤假定您已经安装好下列程序:
Python 2.7
Python Package: pip and setuptools. 现在 pip 依赖 setuptools ,如果未安装,则会自动安装 setuptools 。
lxml. 大多数Linux发行版自带了lxml。如果缺失,请查看http://lxml.de/installation.html
OpenSSL. 除了Windows(请查看 平台安装指南)之外的系统都已经提供。
您可以使用pip来安装Scrapy(推荐使用pip来安装Python package).
使用pip安装:
pip install Scrapy
平台安装指南
Windows
从 http://python.org/download/ 上安装Python 2.7.
您需要修改 PATH 环境变量,将Python的可执行程序及额外的脚本添加到系统路径中。将以下路径添加到 PATH 中:
C:\Python27\;C:\Python27\Scripts\;
请打开命令行,并且运行以下命令来修改 PATH:
c:\python27\python.exe c:\python27\tools\scripts\win_add2path.py
关闭并重新打开命令行窗口,使之生效。运行接下来的命令来确认其输出所期望的Python版本:
python –version
从 http://sourceforge.net/projects/pywin32/ 安装 pywin32
请确认下载符合您系统的版本(win32或者amd64)
从 https://pip.pypa.io/en/latest/installing.html 安装 pip
打开命令行窗口,确认 pip 被正确安装:
pip –version
到目前为止Python 2.7 及 pip 已经可以正确运行了。接下来安装Scrapy:
pip install Scrapy
Ubuntu 9.10及以上版本
不要 使用Ubuntu提供的 python-scrapy ,相较于最新版的Scrapy,该包版本太旧,并且运行速度也较为缓慢。
您可以使用官方提供的 Ubuntu Packages 。该包解决了全部依赖问题,并且与最新的bug修复保持持续更新。
Archlinux
您可以依照通用的方式或者从 AUR Scrapy package 来安装Scrapy:
yaourt -S scrapy
Scrapy入门教程
在本篇教程中,我们假定您已经安装好Scrapy。 如若不然,请参考 安装指南 。
接下来以 Open Directory Project(dmoz) (dmoz) 为例来讲述爬取。
本篇教程中将带您完成下列任务:
创建一个Scrapy项目
定义提取的Item
编写爬取网站的 spider 并提取 Item
编写 Item Pipeline 来存储提取到的Item(即数据)
Scrapy由 Python 编写。如果您刚接触并且好奇这门语言的特性以及Scrapy的详情, 对于已经熟悉其他语言并且想快速学习Python的编程老手, 我们推荐 Learn Python The Hard Way , 对于想从Python开始学习的编程新手, 非程序员的Python学习资料列表 将是您的选择。
创建项目
在开始爬取之前,您必须创建一个新的Scrapy项目。 进入您打算存储代码的目录中,运行下列命令:
scrapy startproject tutorial
该命令将会创建包含下列内容的 tutorial 目录:
tutorial/
scrapy.cfg
tutorial/
init.py
items.py
pipelines.py
settings.py
spiders/
init.py
…
这些文件分别是:
scrapy.cfg: 项目的配置文件
tutorial/: 该项目的python模块。之后您将在此加入代码。
tutorial/items.py: 项目中的item文件.
tutorial/pipelines.py: 项目中的pipelines文件.
tutorial/settings.py: 项目的设置文件.
tutorial/spiders/: 放置spider代码的目录.
定义Item
Item 是保存爬取到的数据的容器;其使用方法和python字典类似, 并且提供了额外保护机制来避免拼写错误导致的未定义字段错误。
类似在ORM中做的一样,您可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field 的类属性来定义一个Item。 (如果不了解ORM, 不用担心,您会发现这个步骤非常简单)
首先根据需要从dmoz.org获取到的数据对item进行建模。 我们需要从dmoz中获取名字,url,以及网站的描述。 对此,在item中定义相应的字段。编辑 tutorial 目录中的 items.py 文件:
import scrapy
class DmozItem(scrapy.Item):
title = scrapy.Field()
link = scrapy.Field()
desc = scrapy.Field()
一开始这看起来可能有点复杂,但是通过定义item, 您可以很方便的使用Scrapy的其他方法。而这些方法需要知道您的item的定义。
编写第一个爬虫(Spider)
Spider是用户编写用于从单个网站(或者一些网站)爬取数据的类。
其包含了一个用于下载的初始URL,如何跟进网页中的链接以及如何分析页面中的内容, 提取生成 item 的方法。
为了创建一个Spider,您必须继承 scrapy.Spider 类, 且定义以下三个属性:
name: 用于区别Spider。 该名字必须是唯一的,您不可以为不同的Spider设定相同的名字。
start_urls: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。
parse() 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。
以下为我们的第一个Spider代码,保存在 tutorial/spiders 目录下的 dmoz_spider.py 文件中:
import scrapy
class DmozSpider(scrapy.spiders.Spider):
name = “dmoz”
allowed_domains = [“dmoz.org”]
start_urls = [
“http://www.dmoz.org/Computers/Programming/Languages/Python/Books/“,
“http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/”
]
def parse(self, response):
filename = response.url.split("/")[-2]
with open(filename, 'wb') as f:
f.write(response.body)
爬取
进入项目的根目录,执行下列命令启动spider:
scrapy crawl dmoz
crawl dmoz 启动用于爬取 dmoz.org 的spider,您将得到类似的输出:
2014-01-23 18:13:07-0400 [scrapy] INFO: Scrapy started (bot: tutorial)
2014-01-23 18:13:07-0400 [scrapy] INFO: Optional features available: …
2014-01-23 18:13:07-0400 [scrapy] INFO: Overridden settings: {}
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled extensions: …
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled downloader middlewares: …
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled spider middlewares: …
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled item pipelines: …
2014-01-23 18:13:07-0400 [dmoz] INFO: Spider opened
2014-01-23 18:13:08-0400 [dmoz] DEBUG: Crawled (200)
Scraped Items ————————————————————
[{‘name’: u’Example item’,
‘category’: u’Furniture’,
‘length’: u’12 cm’}]
Requests —————————————————————–
[]
settings
语法: scrapy settings [options]
是否需要项目: no
获取Scrapy的设定
在项目中运行时,该命令将会输出项目的设定值,否则输出Scrapy默认设定。
例子:
scrapysettings–getBOTNAMEscrapybot
s
c
r
a
p
y
s
e
t
t
i
n
g
s
–
g
e
t
B
O
T
N
A
M
E
s
c
r
a
p
y
b
o
t
scrapy settings –get DOWNLOAD_DELAY
0
runspider
语法: scrapy runspider
HTML snippet:
Color TV
loader.get_xpath(‘//p[@class=”product-name”]’)
HTML snippet:
the price is $1200
loader.get_xpath(‘//p[@id=”price”]’, TakeFirst(), re=’the price is (.*)’)
add_xpath(field_name, xpath, *processors, **kwargs)
Similar to ItemLoader.add_value() but receives an XPath instead of a value, which is used to extract a list of unicode strings from the selector associated with this ItemLoader.
See get_xpath() for kwargs.
参数: xpath (str) – the XPath to extract data from
Examples:
HTML snippet:
Color TV
loader.add_xpath(‘name’, ‘//p[@class=”product-name”]’)
HTML snippet:
the price is $1200
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.
参数:
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:
Color TV
loader.get_css(‘p.product-name’)
HTML snippet:
the price is $1200
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.
参数: css (str) – the CSS selector to extract data from
Examples:
HTML snippet:
Color TV
loader.add_css(‘name’, ‘p.product-name’)
HTML snippet:
the price is $1200
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()
Populate the item with the data collected so far, and return it. The data collected is first passed through the output processors to get the final value to assign to each item field.
get_collected_values(field_name)
Return the collected values for the given field.
get_output_value(field_name)
Return the collected values parsed using the output processor, for the given field. This method doesn’t populate or modify the item at all.
get_input_processor(field_name)
Return the input processor for the given field.
get_output_processor(field_name)
Return the output processor for the given field.
ItemLoader instances have the following attributes:
item
The Item object being parsed by this Item Loader.
context
The currently active Context of this Item Loader.
default_item_class
An Item class (or factory), used to instantiate items when not given in the constructor.
default_input_processor
The default input processor to use for those fields which don’t specify one.
default_output_processor
The default output processor to use for those fields which don’t specify one.
default_selector_class
The class used to construct the selector of this ItemLoader, if only a response is given in the constructor. If a selector is given in the constructor this attribute is ignored. This attribute is sometimes overridden in subclasses.
selector
The Selector object to extract data from. It’s either the selector given in the constructor or one created from the response given in the constructor using the default_selector_class. This attribute is meant to be read-only.
Reusing and extending Item Loaders
As your project grows bigger and acquires more and more spiders, maintenance becomes a fundamental problem, especially when you have to deal with many different parsing rules for each spider, having a lot of exceptions, but also wanting to reuse the common processors.
Item Loaders are designed to ease the maintenance burden of parsing rules, without losing flexibility and, at the same time, providing a convenient mechanism for extending and overriding them. For this reason Item Loaders support traditional Python class inheritance for dealing with differences of specific spiders (or groups of spiders).
Suppose, for example, that some particular site encloses their product names in three dashes (e.g. —Plasma TV—) and you don’t want to end up scraping those dashes in the final product names.
Here’s how you can remove those dashes by reusing and extending the default Product Item Loader (ProductLoader):
from scrapy.contrib.loader.processor 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)
Another case where extending Item Loaders can be very helpful is when you have multiple source formats, for example XML and HTML. In the XML version you may want to remove CDATA occurrences. Here’s an example of how to do it:
from scrapy.contrib.loader.processor 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)
And that’s how you typically extend input processors.
As for output processors, it is more common to declare them in the field metadata, as they usually depend only on the field and not on each specific site parsing rule (as input processors do). See also: Declaring Input and Output Processors.
There are many other possible ways to extend, inherit and override your Item Loaders, and different Item Loaders hierarchies may fit better for different projects. Scrapy only provides the mechanism; it doesn’t impose any specific organization of your Loaders collection - that’s up to you and your project’s needs.
Available built-in processors
Even though you can use any callable function as input and output processors, Scrapy provides some commonly used processors, which are described below. Some of them, like the MapCompose (which is typically used as input processor) compose the output of several functions executed in order, to produce the final parsed value.
Here is a list of all built-in processors:
class scrapy.contrib.loader.processor.Identity
The simplest processor, which doesn’t do anything. It returns the original values unchanged. It doesn’t receive any constructor arguments nor accepts Loader contexts.
Example:
from scrapy.contrib.loader.processor import Identity
proc = Identity()
proc([‘one’, ‘two’, ‘three’])
[‘one’, ‘two’, ‘three’]
class scrapy.contrib.loader.processor.TakeFirst
Returns the first non-null/non-empty value from the values received, so it’s typically used as an output processor to single-valued fields. It doesn’t receive any constructor arguments, nor accept Loader contexts.
Example:
from scrapy.contrib.loader.processor import TakeFirst
proc = TakeFirst()
proc([”, ‘one’, ‘two’, ‘three’])
‘one’
class scrapy.contrib.loader.processor.Join(separator=u’ ‘)
Returns the values joined with the separator given in the constructor, which defaults to u’ ‘. It doesn’t accept Loader contexts.
When using the default separator, this processor is equivalent to the function: u’ ‘.join
Examples:
from scrapy.contrib.loader.processor import Join
proc = Join()
proc([‘one’, ‘two’, ‘three’])
u’one two three’
proc = Join(‘
’)
proc([‘one’, ‘two’, ‘three’])
u’one
two
three’
class scrapy.contrib.loader.processor.Compose(*functions, **default_loader_context)
A processor which is constructed from the composition of the given functions. This means that each input value of this processor is passed to the first function, and the result of that function is passed to the second function, and so on, until the last function returns the output value of this processor.
By default, stop process on None value. This behaviour can be changed by passing keyword argument stop_on_none=False.
Example:
from scrapy.contrib.loader.processor import Compose
proc = Compose(lambda v: v[0], str.upper)
proc([‘hello’, ‘world’])
‘HELLO’
Each function can optionally receive a loader_context parameter. For those which do, this processor will pass the currently active Loader context through that parameter.
The keyword arguments passed in the constructor are used as the default Loader context values passed to each function call. However, the final Loader context values passed to functions are overridden with the currently active Loader context accessible through the ItemLoader.context() attribute.
class scrapy.contrib.loader.processor.MapCompose(*functions, **default_loader_context)
A processor which is constructed from the composition of the given functions, similar to the Compose processor. The difference with this processor is the way internal results are passed among functions, which is as follows:
The input value of this processor is iterated and the first function is applied to each element. The results of these function calls (one for each element) are concatenated to construct a new iterable, which is then used to apply the second function, and so on, until the last function is applied to each value of the list of values collected so far. The output values of the last function are concatenated together to produce the output of this processor.
Each particular function can return a value or a list of values, which is flattened with the list of values returned by the same function applied to the other input values. The functions can also return None in which case the output of that function is ignored for further processing over the chain.
This processor provides a convenient way to compose functions that only work with single values (instead of iterables). For this reason the MapCompose processor is typically used as input processor, since data is often extracted using the extract() method of selectors, which returns a list of unicode strings.
The example below should clarify how it works:
def filter_world(x):
… return None if x == ‘world’ else x
…
from scrapy.contrib.loader.processor 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’]
As with the Compose processor, functions can receive Loader contexts, and constructor keyword arguments are used as default context values. See Compose processor for more info.
Scrapy终端(Scrapy shell)
Scrapy终端是一个交互终端,供您在未启动spider的情况下尝试及调试您的爬取代码。 其本意是用来测试提取数据的代码,不过您可以将其作为正常的Python终端,在上面测试任何的Python代码。
该终端是用来测试XPath或CSS表达式,查看他们的工作方式及从爬取的网页中提取的数据。 在编写您的spider时,该终端提供了交互性测试您的表达式代码的功能,免去了每次修改后运行spider的麻烦。
一旦熟悉了Scrapy终端后,您会发现其在开发和调试spider时发挥的巨大作用。
如果您安装了 IPython ,Scrapy终端将使用 IPython (替代标准Python终端)。 IPython 终端与其他相比更为强大,提供智能的自动补全,高亮输出,及其他特性。
我们强烈推荐您安装 IPython ,特别是如果您使用Unix系统(IPython 在Unix下工作的很好)。 详情请参考 IPython installation guide 。
启动终端
您可以使用 shell 来启动Scrapy终端:
scrapy shell
是您要爬取的网页的地址。
使用终端
Scrapy终端仅仅是一个普通的Python终端(或 IPython )。其提供了一些额外的快捷方式。
可用的快捷命令(shortcut)
shelp() - 打印可用对象及快捷命令的帮助列表
fetch(request_or_url) - 根据给定的请求(request)或URL获取一个新的response,并更新相关的对象
view(response) - 在本机的浏览器打开给定的response。 其会在response的body中添加一个 tag ,使得外部链接(例如图片及css)能正确显示。 注意,该操作会在本地创建一个临时文件,且该文件不会被自动删除。
可用的Scrapy对象
Scrapy终端根据下载的页面会自动创建一些方便使用的对象,例如 Response 对象及 Selector 对象(对HTML及XML内容)。
这些对象有:
crawler - 当前 Crawler 对象.
spider - 处理URL的spider。 对当前URL没有处理的Spider时则为一个 Spider 对象。
request - 最近获取到的页面的 Request 对象。 您可以使用 replace() 修改该request。或者 使用 fetch 快捷方式来获取新的request。
response - 包含最近获取到的页面的 Response 对象。
sel - 根据最近获取到的response构建的 Selector 对象。
settings - 当前的 Scrapy settings
终端会话(shell session)样例
下面给出一个典型的终端会话的例子。 在该例子中,我们首先爬取了 http://scarpy.org 的页面,而后接着爬取 http://slashdot.org 的页面。 最后,我们修改了(Slashdot)的请求,将请求设置为POST并重新获取, 得到HTTP 405(不允许的方法)错误。 之后通过Ctrl-D(Unix)或Ctrl-Z(Windows)关闭会话。
需要注意的是,由于爬取的页面不是静态页,内容会随着时间而修改, 因此例子中提取到的数据可能与您尝试的结果不同。 该例子的唯一目的是让您熟悉Scrapy终端。
首先,我们启动终端:
scrapy shell ‘http://scrapy.org’ –nolog
接着该终端(使用Scrapy下载器(downloader))获取URL内容并打印可用的对象及快捷命令(注意到以 [s] 开头的行):
[s] Available Scrapy objects:
[s] crawler
Scraped Items ————————————————————
[{‘url’: }]
Requests —————————————————————–
[]
使用 –verbose 或 -v 选项,查看各个层次的状态:
$ scrapy parse –spider=myspider -c parse_item -d 2 -v
[ … scrapy log lines crawling example.com spider … ]
DEPTH LEVEL: 1 <<<
Scraped Items ————————————————————
[]
Requests —————————————————————–
[]
DEPTH LEVEL: 2 <<<
Scraped Items ————————————————————
[{‘url’: }]
Requests —————————————————————–
[]
检查从单个start_url爬取到的item也是很简单的:
$ scrapy parse –spider=myspider -d 3 ‘http://example.com/page1’
Scrapy终端(Shell)
尽管 parse 命令对检查spider的效果十分有用,但除了显示收到的response及输出外, 其对检查回调函数内部的过程并没有提供什么便利。 如何调试 parse_detail 没有收到item的情况呢?
幸运的是,救世主 shell 出现了(参考 在spider中启动shell来查看response):
from scrapy.shell import inspect_response
def parse_details(self, response):
item = response.meta.get(‘item’, None)
if item:
# populate more item
fields
return item
else:
inspect_response(response, self)
参考 在spider中启动shell来查看response 。
在浏览器中打开
有时候您想查看某个response在浏览器中显示的效果,这是可以使用 open_in_browser 功能。下面是使用的例子:
from scrapy.utils.response import open_in_browser
def parse_details(self, response):
if “item name” not in response.body:
open_in_browser(response)
open_in_browser 将会使用Scrapy获取到的response来打开浏览器,并且调整 base tag 使得图片及样式(style)能正常显示。
Logging
记录(logging)是另一个获取到spider运行信息的方法。虽然不是那么方便, 但好处是log的内容在以后的运行中也可以看到:
from scrapy import log
def parse_details(self, response):
item = response.meta.get(‘item’, None)
if item:
# populate more item
fields
return item
else:
self.log(‘No item received for %s’ % response.url,
level=log.WARNING)
更多内容请参见 Logging 部分。
Spiders Contracts
0.15 新版功能.
注解
这是一个新引入(Scrapy 0.15)的特性,在后续的功能/API更新中可能有所改变,查看 release notes 来了解更新。
测试spider是一件挺烦人的事情,尤其是只能编写单元测试(unit test)没有其他办法时,就更恼人了。 Scrapy通过合同(contract)的方式来提供了测试spider的集成方法。
您可以硬编码(hardcode)一个样例(sample)url, 设置多个条件来测试回调函数处理repsponse的结果,来测试spider的回调函数。 每个contract包含在文档字符串(docstring)里,以 @ 开头。 查看下面的例子:
def parse(self, response):
“”” This function parses a sample response. Some contracts are mingled
with this docstring.
@url http://www.amazon.com/s?field-keywords=selfish+gene
@returns items 1 16
@returns requests 0 0
@scrapes Title Author Year Price
"""
该回调函数使用了三个内置的contract来测试:
class scrapy.contracts.default.UrlContract
该constract(@url)设置了用于检查spider的其他constract状态的样例url。 该contract是必须的,所有缺失该contract的回调函数在测试时将会被忽略:
@url url
class scrapy.contracts.default.ReturnsContract
该contract(@returns)设置spider返回的items和requests的上界和下界。 上界是可选的:
@returns item(s)|request(s) [min [max]]
class scrapy.contracts.default.ScrapesContract
该contract(@scrapes)检查回调函数返回的所有item是否有特定的fields:
@scrapes field_1 field_2 …
使用 check 命令来运行contract检查。
自定义Contracts
如果您想要比内置scrapy contract更为强大的功能,可以在您的项目里创建并设置您自己的 contract,并使用 SPIDER_CONTRACTS 设置来加载:
SPIDER_CONTRACTS = {
‘myproject.contracts.ResponseCheck’: 10,
‘myproject.contracts.ItemValidate’: 10,
}
每个contract必须继承 scrapy.contracts.Contract 并覆盖下列三个方法:
class scrapy.contracts.Contract(method, *args)
参数:
method (function) – contract所关联的回调函数
args (list) – 传入docstring的(以空格区分的)argument列表(list)
adjust_request_args(args)
接收一个 字典(dict) 作为参数。该参数包含了所有 Request 对象 参数的默认值。该方法必须返回相同或修改过的字典。
pre_process(response)
该函数在sample request接收到response后,传送给回调函数前被调用,运行测试。
post_process(output)
该函数处理回调函数的输出。迭代器(Iterators)在传输给该函数前会被列表化(listified)。
该样例contract在response接收时检查了是否有自定义header。 在失败时Raise scrapy.exceptions.ContractFaild 来展现错误:
from scrapy.contracts import Contract
from scrapy.exceptions import ContractFail
class HasHeaderContract(Contract):
“”” Demo contract which checks the presence of a custom header
@has_header X-CustomHeader
“”“
name = 'has_header'
def pre_process(self, response):
for header in self.args:
if header not in response.headers:
raise ContractFail('X-CustomHeader not present')
实践经验(Common Practices)
本章节记录了使用Scrapy的一些实践经验(common practices)。 这包含了很多使用不会包含在其他特定章节的的内容。
在脚本中运行Scrapy
除了常用的 scrapy crawl 来启动Scrapy,您也可以使用 API 在脚本中启动Scrapy。
需要注意的是,Scrapy是在Twisted异步网络库上构建的, 因此其必须在Twisted reactor里运行。
另外,在spider运行结束后,您必须自行关闭Twisted reactor。 这可以通过在 CrawlerRunner.crawl 所返回的对象中添加回调函数来实现。
下面给出了如何实现的例子,使用 testspiders 项目作为例子。
from twisted.internet import reactor
from scrapy.crawler import CrawlerRunner
from scrapy.utils.project import get_project_settings
runner = CrawlerRunner(get_project_settings())
‘followall’ is the name of one of the spiders of the project.
d = runner.crawl(‘followall’, domain=’scrapinghub.com’)
d.addBoth(lambda _: reactor.stop())
reactor.run() # the script will block here until the crawling is finished
Running spiders outside projects it’s not much different. You have to create a generic Settings object and populate it as needed (See 内置设定参考手册 for the available settings), instead of using the configuration returned by get_project_settings.
Spiders can still be referenced by their name if SPIDER_MODULES is set with the modules where Scrapy should look for spiders. Otherwise, passing the spider class as first argument in the CrawlerRunner.crawl method is enough.
from twisted.internet import reactor
from scrapy.spider import Spider
from scrapy.crawler import CrawlerRunner
from scrapy.settings import Settings
class MySpider(Spider):
# Your spider definition
…
settings = Settings({‘USER_AGENT’: ‘Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)’})
runner = CrawlerRunner(settings)
d = runner.crawl(MySpider)
d.addBoth(lambda _: reactor.stop())
reactor.run() # the script will block here until the crawling is finished
参见
Twisted Reactor Overview.
同一进程运行多个spider
默认情况下,当您执行 scrapy crawl 时,Scrapy每个进程运行一个spider。 当然,Scrapy通过 内部(internal)API 也支持单进程多个spider。
下面以 testspiders 作为例子来说明如何同时运行多个spider:
from twisted.internet import reactor, defer
from scrapy.crawler import CrawlerRunner
from scrapy.utils.project import get_project_settings
runner = CrawlerRunner(get_project_settings())
dfs = set()
for domain in [‘scrapinghub.com’, ‘insophia.com’]:
d = runner.crawl(‘followall’, domain=domain)
dfs.add(d)
defer.DeferredList(dfs).addBoth(lambda _: reactor.stop())
reactor.run() # the script will block here until all crawling jobs are finished
相同的例子,不过通过链接(chaining) deferred来线性运行spider:
from twisted.internet import reactor, defer
from scrapy.crawler import CrawlerRunner
from scrapy.utils.project import get_project_settings
runner = CrawlerRunner(get_project_settings())
@defer.inlineCallbacks
def crawl():
for domain in [‘scrapinghub.com’, ‘insophia.com’]:
yield runner.crawl(‘followall’, domain=domain)
reactor.stop()
crawl()
reactor.run() # the script will block here until the last crawl call is finished
参见
在脚本中运行Scrapy.
分布式爬虫(Distributed crawls)
Scrapy并没有提供内置的机制支持分布式(多服务器)爬取。不过还是有办法进行分布式爬取, 取决于您要怎么分布了。
如果您有很多spider,那分布负载最简单的办法就是启动多个Scrapyd,并分配到不同机器上。
如果想要在多个机器上运行一个单独的spider,那您可以将要爬取的url进行分块,并发送给spider。 例如:
首先,准备要爬取的url列表,并分配到不同的文件url里:
http://somedomain.com/urls-to-crawl/spider1/part1.list
http://somedomain.com/urls-to-crawl/spider1/part2.list
http://somedomain.com/urls-to-crawl/spider1/part3.list
接着在3个不同的Scrapd服务器中启动spider。spider会接收一个(spider)参数 part , 该参数表示要爬取的分块:
curl http://scrapy1.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1 -d part=1
curl http://scrapy2.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1 -d part=2
curl http://scrapy3.mycompany.com:6800/schedule.json -d project=myproject -d spider=spider1 -d part=3
避免被禁止(ban)
有些网站实现了特定的机制,以一定规则来避免被爬虫爬取。 与这些规则打交道并不容易,需要技巧,有时候也需要些特别的基础。 如果有疑问请考虑联系 商业支持 。
下面是些处理这些站点的建议(tips):
使用user agent池,轮流选择之一来作为user agent。池中包含常见的浏览器的user agent(google一下一大堆)
禁止cookies(参考 COOKIES_ENABLED),有些站点会使用cookies来发现爬虫的轨迹。
设置下载延迟(2或更高)。参考 DOWNLOAD_DELAY 设置。
如果可行,使用 Google cache 来爬取数据,而不是直接访问站点。
使用IP池。例如免费的 Tor项目 或付费服务(ProxyMesh)。
使用高度分布式的下载器(downloader)来绕过禁止(ban),您就只需要专注分析处理页面。这样的例子有: Crawlera
如果您仍然无法避免被ban,考虑联系 商业支持.
动态创建Item类
对于有些应用,item的结构由用户输入或者其他变化的情况所控制。您可以动态创建class。
from scrapy.item import DictItem, Field
def create_item_class(class_name, field_list):
fields = {field_name: Field() for field_name in field_list}
return type(class_name, (DictItem,), {‘fields’: fields})
通用爬虫(Broad Crawls)
Scrapy默认对特定爬取进行优化。这些站点一般被一个单独的Scrapy spider进行处理, 不过这并不是必须或要求的(例如,也有通用的爬虫能处理任何给定的站点)。
除了这种爬取完某个站点或没有更多请求就停止的”专注的爬虫”,还有一种通用的爬取类型,其能爬取大量(甚至是无限)的网站, 仅仅受限于时间或其他的限制。 这种爬虫叫做”通用爬虫(broad crawls)”,一般用于搜索引擎。
通用爬虫一般有以下通用特性:
其爬取大量(一般来说是无限)的网站而不是特定的一些网站。
其不会将整个网站都爬取完毕,因为这十分不实际(或者说是不可能)完成的。相反,其会限制爬取的时间及数量。
其在逻辑上十分简单(相较于具有很多提取规则的复杂的spider),数据会在另外的阶段进行后处理(post-processed)
其并行爬取大量网站以避免被某个网站的限制所限制爬取的速度(为表示尊重,每个站点爬取速度很慢但同时爬取很多站点)。
正如上面所述,Scrapy默认设置是对特定爬虫做了优化,而不是通用爬虫。不过, 鉴于其使用了异步架构,Scrapy对通用爬虫也十分适用。 本篇文章总结了一些将Scrapy作为通用爬虫所需要的技巧, 以及相应针对通用爬虫的Scrapy设定的一些建议。
增加并发
并发是指同时处理的request的数量。其有全局限制和局部(每个网站)的限制。
Scrapy默认的全局并发限制对同时爬取大量网站的情况并不适用,因此您需要增加这个值。 增加多少取决于您的爬虫能占用多少CPU。 一般开始可以设置为 100 。不过最好的方式是做一些测试,获得Scrapy进程占取CPU与并发数的关系。 为了优化性能,您应该选择一个能使CPU占用率在80%-90%的并发数。
增加全局并发数:
CONCURRENT_REQUESTS = 100
降低log级别
当进行通用爬取时,一般您所注意的仅仅是爬取的速率以及遇到的错误。 Scrapy使用 INFO log级别来报告这些信息。为了减少CPU使用率(及记录log存储的要求), 在生产环境中进行通用爬取时您不应该使用 DEBUG log级别。 不过在开发的时候使用 DEBUG 应该还能接受。
设置Log级别:
LOG_LEVEL = ‘INFO’
禁止cookies
除非您 真的 需要,否则请禁止cookies。在进行通用爬取时cookies并不需要, (搜索引擎则忽略cookies)。禁止cookies能减少CPU使用率及Scrapy爬虫在内存中记录的踪迹,提高性能。
禁止cookies:
COOKIES_ENABLED = False
禁止重试
对失败的HTTP请求进行重试会减慢爬取的效率,尤其是当站点响应很慢(甚至失败)时, 访问这样的站点会造成超时并重试多次。这是不必要的,同时也占用了爬虫爬取其他站点的能力。
禁止重试:
RETRY_ENABLED = False
减小下载超时
如果您对一个非常慢的连接进行爬取(一般对通用爬虫来说并不重要), 减小下载超时能让卡住的连接能被快速的放弃并解放处理其他站点的能力。
减小下载超时:
DOWNLOAD_TIMEOUT = 15
禁止重定向
除非您对跟进重定向感兴趣,否则请考虑关闭重定向。 当进行通用爬取时,一般的做法是保存重定向的地址,并在之后的爬取进行解析。 这保证了每批爬取的request数目在一定的数量, 否则重定向循环可能会导致爬虫在某个站点耗费过多资源。
关闭重定向:
REDIRECT_ENABLED = False
启用 “Ajax Crawlable Pages” 爬取
有些站点(基于2013年的经验数据,之多有1%)声明其为 ajax crawlable 。 这意味着该网站提供了原本只有ajax获取到的数据的纯HTML版本。 网站通过两种方法声明:
在url中使用 #! - 这是默认的方式;
使用特殊的meta标签 - 这在”main”, “index” 页面中使用。
Scrapy自动解决(1);解决(2)您需要启用 AjaxCrawlMiddleware:
AJAXCRAWL_ENABLED = True
通用爬取经常抓取大量的 “index” 页面; AjaxCrawlMiddleware能帮助您正确地爬取。 由于有些性能问题,且对于特定爬虫没有什么意义,该中间默认关闭。
借助Firefox来爬取
这里介绍一些使用Firefox进行爬取的点子及建议,以及一些帮助爬取的Firefox实用插件。
在浏览器中检查DOM的注意事项
Firefox插件操作的是活动的浏览器DOM(live browser DOM),这意味着当您检查网页源码的时候, 其已经不是原始的HTML,而是经过浏览器清理并执行一些Javascript代码后的结果。 Firefox是个典型的例子,其会在table中添加 元素。 而Scrapy相反,其并不修改原始的HTML,因此如果在XPath表达式中使用 ,您将获取不到任何数据。
所以,当XPath配合Firefox使用时您需要记住以下几点:
当检查DOM来查找Scrapy使用的XPath时,禁用Firefox的Javascrpit。
永远不要用完整的XPath路径。使用相对及基于属性(例如 id , class , width 等)的路径 或者具有区别性的特性例如 contains(@href, ‘image’) 。
永远不要在XPath表达式中加入 元素,除非您知道您在做什么
对爬取有帮助的实用Firefox插件
Firebug
Firebug 是一个在web开发者间很著名的工具,其对抓取也十分有用。 尤其是 检查元素(Inspect Element) 特性对构建抓取数据的XPath十分方便。 当移动鼠标在页面元素时,您能查看相应元素的HTML源码。
查看 使用Firebug进行爬取 ,了解如何配合Scrapy使用Firebug的详细教程。
XPather
XPather 能让你在页面上直接测试XPath表达式。
XPath Checker
XPath Checker 是另一个用于测试XPath表达式的Firefox插件。
Tamper Data
Tamper Data 是一个允许您查看及修改Firefox发送的header的插件。Firebug能查看HTTP header,但无法修改。
Firecookie
Firecookie 使得查看及管理cookie变得简单。您可以使用这个插件来创建新的cookie, 删除存在的cookie,查看当前站点的cookie,管理cookie的权限及其他功能。
使用Firebug进行爬取
注解
本教程所使用的样例站Google Directory已经 被Google关闭 了。不过教程中的概念任然适用。 如果您打算使用一个新的网站来更新本教程,您的贡献是再欢迎不过了。 详细信息请参考 Contributing to Scrapy 。
介绍
本文档介绍了如何适用 Firebug (一个Firefox的插件)来使得爬取更为简单,有趣。 更多有意思的Firefox插件请参考 对爬取有帮助的实用Firefox插件 。 使用Firefox插件检查页面需要有些注意事项: 在浏览器中检查DOM的注意事项 。
在本样例中将展现如何使用 Firebug 从 Google Directory 来爬取数据。 Google Directory 包含了 入门教程 里所使用的 Open Directory Project 中一样的数据,不过有着不同的结构。
Firebug提供了非常实用的 检查元素 功能。该功能允许您将鼠标悬浮在不同的页面元素上, 显示相应元素的HTML代码。否则,您只能十分痛苦的在HTML的body中手动搜索标签。
在下列截图中,您将看到 检查元素 的执行效果。
Inspecting elements with Firebug
首先我们能看到目录根据种类进行分类的同时,还划分了子类。
不过,看起来子类还有更多的子类,而不仅仅是页面显示的这些,所以我们接着查找:
Inspecting elements with Firebug
正如路径的概念那样,子类包含了其他子类的链接,同时也链接到实际的网站中。
获取到跟进(follow)的链接
查看路径的URL,我们可以看到URL的通用模式(pattern):
http://directory.google.com/Category/Subcategory/Another_Subcategory
了解到这个消息,我们可以构建一个跟进的链接的正则表达式:
directory.google.com/[A-Z][a-zA-Z_/]+$
因此,根据这个表达式,我们创建第一个爬取规则:
Rule(LinkExtractor(allow=’directory.google.com/[A-Z][a-zA-Z_/]+$’, ),
‘parse_category’,
follow=True,
),
Rule 对象指导基于 CrawlSpider 的spider如何跟进目录链接。 parse_category 是spider的方法,用于从页面中处理也提取数据。
spider的代码如下:
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
class GoogleDirectorySpider(CrawlSpider):
name = ‘directory.google.com’
allowed_domains = [‘directory.google.com’]
start_urls = [‘http://directory.google.com/‘]
rules = (
Rule(LinkExtractor(allow='directory\.google\.com/[A-Z][a-zA-Z_/]+$'),
'parse_category', follow=True,
),
)
def parse_category(self, response):
# write the category page data extraction code here
pass
提取数据
现在我们来编写提取数据的代码。
在Firebug的帮助下,我们将查看一些包含网站链接的网页(以 http://directory.google.com/Top/Arts/Awards/ 为例), 找到使用 Selectors 提取链接的方法。 我们也将使用 Scrapy shell 来测试得到的XPath表达式,确保表达式工作符合预期。
Inspecting elements with Firebug
正如您所看到的那样,页面的标记并不是十分明显: 元素并不包含 id , class 或任何可以区分的属性。所以我们将使用等级槽(rank bar)作为指示点来选择提取的数据,创建XPath。
使用Firebug,我们可以看到每个链接都在 td 标签中。该标签存在于同时(在另一个 td)包含链接的等级槽(ranking bar)的 tr 中。
所以我们选择等级槽(ranking bar),接着找到其父节点(tr),最后是(包含我们要爬取数据的)链接的 td 。
对应的XPath:
//td[descendant::a[contains(@href, “#pagerank”)]]/following-sibling::td//a
使用 Scrapy终端 来测试这些复杂的XPath表达式,确保其工作符合预期。
简单来说,该表达式会查找等级槽的 td 元素,接着选择所有 td 元素,该元素拥有子孙 a 元素,且 a 元素的属性 href 包含字符串 #pagerank 。
当然,这不是唯一的XPath,也许也不是选择数据的最简单的那个。 其他的方法也可能是,例如,选择灰色的链接的 font 标签。
最终,我们编写 parse_category() 方法:
def parse_category(self, response):
# The path to website links in directory page
links = response.xpath('//td[descendant::a[contains(@href, "#pagerank")]]/following-sibling::td/font')
for link in links:
item = DirectoryItem()
item['name'] = link.xpath('a/text()').extract()
item['url'] = link.xpath('a/@href').extract()
item['description'] = link.xpath('font[2]/text()').extract()
yield item
注意,您可能会遇到有些在Firebug找到,但是在原始HTML中找不到的元素, 例如典型的 元素, 或者Firebug检查活动DOM(live DOM)所看到的元素,但元素由javascript动态生成,并不在HTML源码中。 (原文语句乱了,上面为意译- -: or tags which Therefer in page HTML sources may on Firebug inspects the live DOM )
调试内存溢出
在Scrapy中,类似Requests, Response及Items的对象具有有限的生命周期: 他们被创建,使用,最后被销毁。
这些对象中,Request的生命周期应该是最长的,其会在调度队列(Scheduler queue)中一直等待,直到被处理。 更多内容请参考 架构概览 。
由于这些Scrapy对象拥有很长的生命,因此将这些对象存储在内存而没有正确释放的危险总是存在。 而这导致了所谓的”内存泄露”。
为了帮助调试内存泄露,Scrapy提供了跟踪对象引用的机制,叫做 trackref , 或者您也可以使用第三方提供的更先进内存调试库 Guppy (更多内容请查看下面)。而这都必须在 Telnet终端 中使用。
内存泄露的常见原因
内存泄露经常是由于Scrapy开发者在Requests中(有意或无意)传递对象的引用(例如,使用 meta 属性或request回调函数),使得该对象的生命周期与 Request的生命周期所绑定。这是目前为止最常见的内存泄露的原因, 同时对新手来说也是一个比较难调试的问题。
在大项目中,spider是由不同的人所编写的。而这其中有的spider可能是有”泄露的”, 当所有的爬虫同时运行时,这些影响了其他(写好)的爬虫,最终,影响了整个爬取进程。
与此同时,在不限制框架的功能的同时避免造成这些造成泄露的原因是十分困难的。因此, 我们决定不限制这些功能而是提供调试这些泄露的实用工具。这些工具回答了一个问题: 哪个spider在泄露 。
内存泄露可能存在与一个您编写的中间件,管道(pipeline) 或扩展,在代码中您没有正确释放 (之前分配的)资源。例如,您在 spider_opened 中分配资源但在 spider_closed 中没有释放它们。
使用 trackref 调试内存泄露
trackref 是Scrapy提供用于调试大部分内存泄露情况的模块。 简单来说,其追踪了所有活动(live)的Request, Request, Item及Selector对象的引用。
您可以进入telnet终端并通过 prefs() 功能来检查多少(上面所提到的)活跃(alive)对象。 pref() 是 print_live_refs() 功能的引用:
telnet localhost 6023
prefs()
Live References
ExampleSpider 1 oldest: 15s ago
HtmlResponse 10 oldest: 1s ago
Selector 2 oldest: 0s ago
FormRequest 878 oldest: 7s ago
正如所见,报告也展现了每个类中最老的对象的时间(age)。 If you’re running multiple spiders per process chances are you can figure out which spider is leaking by looking at the oldest request or response. You can get the oldest object of each class using the get_oldest() function (from the telnet console).
如果您有内存泄露,那您能找到哪个spider正在泄露的机会是查看最老的request或response。 您可以使用 get_oldest() 方法来获取每个类中最老的对象, 正如此所示(在终端中)(原文档没有样例)。
哪些对象被追踪了?
trackref 追踪的对象包括以下类(及其子类)的对象:
scrapy.http.Request
scrapy.http.Response
scrapy.item.Item
scrapy.selector.Selector
scrapy.spider.Spider
真实例子
让我们来看一个假设的具有内存泄露的准确例子。
假如我们有些spider的代码中有一行类似于这样的代码:
return Request(“http://www.somenastyspider.com/product.php?pid=%d” % product_id,
callback=self.parse, meta={referer: response}”)
代码中在request中传递了一个response的引用,使得reponse的生命周期与request所绑定, 进而造成了内存泄露。
让我们来看看如何使用 trackref 工具来发现哪一个是有问题的spider(当然是在不知道任何的前提的情况下)。
当crawler运行了一小阵子后,我们发现内存占用增长了很多。 这时候我们进入telnet终端,查看活跃(live)的引用:
prefs()
Live References
SomenastySpider 1 oldest: 15s ago
HtmlResponse 3890 oldest: 265s ago
Selector 2 oldest: 0s ago
Request 3878 oldest: 250s ago
上面具有非常多的活跃(且运行时间很长)的response,而其比Request的时间还要长的现象肯定是有问题的。 因此,查看最老的response:
from scrapy.utils.trackref import get_oldest
r = get_oldest(‘HtmlResponse’)
r.url
‘http://www.somenastyspider.com/product.php?pid=123’
就这样,通过查看最老的response的URL,我们发现其属于 somenastyspider.com spider。 现在我们可以查看该spider的代码并发现导致泄露的那行代码(在request中传递response的引用)。
如果您想要遍历所有而不是最老的对象,您可以使用 iter_all() 方法:
from scrapy.utils.trackref import iter_all
[r.url for r in iter_all(‘HtmlResponse’)]
[‘http://www.somenastyspider.com/product.php?pid=123‘,
‘http://www.somenastyspider.com/product.php?pid=584‘,
…
很多spider?
如果您的项目有很多的spider,prefs() 的输出会变得很难阅读。针对于此, 该方法具有 ignore 参数,用于忽略特定的类(及其子类)。例如:from scrapy.spider import Spider
prefs(ignore=Spider)
将不会展现任何spider的活跃引用。
scrapy.utils.trackref模块
以下是 trackref 模块中可用的方法。
class scrapy.utils.trackref.object_ref
如果您想通过 trackref 模块追踪活跃的实例,继承该类(而不是对象)。
scrapy.utils.trackref.print_live_refs(class_name, ignore=NoneType)
打印活跃引用的报告,以类名分类。
参数: ignore (类或者类的元组) – 如果给定,所有指定类(或者类的元组)的对象将会被忽略。
scrapy.utils.trackref.get_oldest(class_name)
返回给定类名的最老活跃(alive)对象,如果没有则返回 None 。首先使用 print_live_refs() 来获取每个类所跟踪的所有活跃(live)对象的列表。
scrapy.utils.trackref.iter_all(class_name)
返回一个能给定类名的所有活跃对象的迭代器,如果没有则返回 None 。首先使用 print_live_refs() 来获取每个类所跟踪的所有活跃(live)对象的列表。
使用Guppy调试内存泄露
trackref 提供了追踪内存泄露非常方便的机制,其仅仅追踪了比较可能导致内存泄露的对象 (Requests, Response, Items及Selectors)。然而,内存泄露也有可能来自其他(更为隐蔽的)对象。 如果是因为这个原因,通过 trackref 则无法找到泄露点,您仍然有其他工具: Guppy library 。
如果使用 setuptools , 您可以通过下列命令安装Guppy:
easy_install guppy
telnet终端也提供了快捷方式(hpy)来访问Guppy堆对象(heap objects)。 下面给出了查看堆中所有可用的Python对象的例子:
x = hpy.heap()
x.bytype
Partition of a set of 297033 objects. Total size = 52587824 bytes.
Index Count % Size % Cumulative % Type
0 22307 8 16423880 31 16423880 31 dict
1 122285 41 12441544 24 28865424 55 str
2 68346 23 5966696 11 34832120 66 tuple
3 227 0 5836528 11 40668648 77 unicode
4 2461 1 2222272 4 42890920 82 type
5 16870 6 2024400 4 44915320 85 function
6 13949 5 1673880 3 46589200 89 types.CodeType
7 13422 5 1653104 3 48242304 92 list
8 3735 1 1173680 2 49415984 94 _sre.SRE_Pattern
9 1209 0 456936 1 49872920 95 scrapy.http.headers.Headers
<1676 more rows. Type e.g. ‘_.more’ to view.>
您可以看到大部分的空间被字典所使用。接着,如果您想要查看哪些属性引用了这些字典, 您可以:x.bytype[0].byvia
Partition of a set of 22307 objects. Total size = 16423880 bytes.
Index Count % Size % Cumulative % Referred Via:
0 10982 49 9416336 57 9416336 57 ‘.dict’
1 1820 8 2681504 16 12097840 74 ‘.dict‘, ‘.func_globals’
2 3097 14 1122904 7 13220744 80
3 990 4 277200 2 13497944 82 “[‘cookies’]”
4 987 4 276360 2 13774304 84 “[‘cache’]”
5 985 4 275800 2 14050104 86 “[‘meta’]”
6 897 4 251160 2 14301264 87 ‘[2]’
7 1 0 196888 1 14498152 88 “[‘moduleDict’]”, “[‘modules’]”
8 672 3 188160 1 14686312 89 “[‘cb_kwargs’]”
9 27 0 155016 1 14841328 90 ‘[1]’
<333 more rows. Type e.g. ‘_.more’ to view.>
如上所示,Guppy模块十分强大,不过也需要一些关于Python内部的知识。关于Guppy的更多内容请参考 Guppy documentation.
Leaks without leaks
有时候,您可能会注意到Scrapy进程的内存占用只在增长,从不下降。不幸的是, 有时候这并不是Scrapy或者您的项目在泄露内存。这是由于一个已知(但不有名)的Python问题。 Python在某些情况下可能不会返回已经释放的内存到操作系统。关于这个问题的更多内容请看:
Python Memory Management
Python Memory Management Part 2
Python Memory Management Part 3
改进方案由Evan Jones提出,在 这篇文章 中详细介绍,在Python 2.5中合并。 不过这仅仅减小了这个问题,并没有完全修复。引用这片文章:
不幸的是,这个patch仅仅会释放没有在其内部分配对象的区域(arena)。这意味着 碎片化是一个大问题。某个应用可以拥有很多空闲内存,分布在所有的区域(arena)中, 但是没法释放任何一个。这个问题存在于所有内存分配器中。解决这个问题的唯一办法是 转化到一个更为紧凑(compact)的垃圾回收器,其能在内存中移动对象。 这需要对Python解析器做一个显著的修改。
这个问题将会在未来Scrapy发布版本中得到解决。我们打算转化到一个新的进程模型, 并在可回收的子进程池中运行spider。
下载项目图片
Scrapy提供了一个 item pipeline ,来下载属于某个特定项目的图片,比如,当你抓取产品时,也想把它们的图片下载到本地。
这条管道,被称作图片管道,在 ImagesPipeline 类中实现,提供了一个方便并具有额外特性的方法,来下载并本地存储图片:
将所有下载的图片转换成通用的格式(JPG)和模式(RGB)
避免重新下载最近已经下载过的图片
缩略图生成
检测图像的宽/高,确保它们满足最小限制
这个管道也会为那些当前安排好要下载的图片保留一个内部队列,并将那些到达的包含相同图片的项目连接到那个队列中。 这可以避免多次下载几个项目共享的同一个图片。
Pillow 是用来生成缩略图,并将图片归一化为JPEG/RGB格式,因此为了使用图片管道,你需要安装这个库。 Python Imaging Library (PIL) 在大多数情况下是有效的,但众所周知,在一些设置里会出现问题,因此我们推荐使用 Pillow 而不是 PIL.
使用图片管道
当使用 ImagesPipeline ,典型的工作流程如下所示:
在一个爬虫里,你抓取一个项目,把其中图片的URL放入 image_urls 组内。
项目从爬虫内返回,进入项目管道。
当项目进入 ImagesPipeline,image_urls 组内的URLs将被Scrapy的调度器和下载器(这意味着调度器和下载器的中间件可以复用)安排下载,当优先级更高,会在其他页面被抓取前处理。项目会在这个特定的管道阶段保持“locker”的状态,直到完成图片的下载(或者由于某些原因未完成下载)。
当图片下载完,另一个组(images)将被更新到结构中。这个组将包含一个字典列表,其中包括下载图片的信息,比如下载路径、源抓取地址(从 image_urls 组获得)和图片的校验码。 images 列表中的图片顺序将和源 image_urls 组保持一致。如果某个图片下载失败,将会记录下错误信息,图片也不会出现在 images 组中。
使用样例
为了使用图片管道,你仅需要 启动它 并用 image_urls 和 images 定义一个项目:
import scrapy
class MyItem(scrapy.Item):
# ... other item fields ...
image_urls = scrapy.Field()
images = scrapy.Field()
如果你需要更加复杂的功能,想重写定制图片管道行为,参见 实现定制图片管道 。
开启你的图片管道
为了开启你的图片管道,你首先需要在项目中添加它 ITEM_PIPELINES setting:
ITEM_PIPELINES = {‘scrapy.contrib.pipeline.images.ImagesPipeline’: 1}
并将 IMAGES_STORE 设置为一个有效的文件夹,用来存储下载的图片。否则管道将保持禁用状态,即使你在 ITEM_PIPELINES 设置中添加了它。
比如:
IMAGES_STORE = ‘/path/to/valid/dir’
图片存储
文件系统是当前官方唯一支持的存储系统,但也支持(非公开的) Amazon S3 。
文件系统存储
图片存储在文件中(一个图片一个文件),并使用它们URL的 SHA1 hash 作为文件名。
比如,对下面的图片URL:
http://www.example.com/image.jpg
它的 SHA1 hash 值为:
3afec3b4765f8f0a07b78f98c07b83f013567a0a
将被下载并存为下面的文件:
/full/3afec3b4765f8f0a07b78f98c07b83f013567a0a.jpg
其中:
是定义在 IMAGES_STORE 设置里的文件夹
full 是用来区分图片和缩略图(如果使用的话)的一个子文件夹。详情参见 缩略图生成.
额外的特性
图片失效
图像管道避免下载最近已经下载的图片。使用 IMAGES_EXPIRES 设置可以调整失效期限,可以用天数来指定:
90天的图片失效期限
IMAGES_EXPIRES = 90
缩略图生成
图片管道可以自动创建下载图片的缩略图。
为了使用这个特性,你需要设置 IMAGES_THUMBS 字典,其关键字为缩略图名字,值为它们的大小尺寸。
比如:
IMAGES_THUMBS = {
‘small’: (50, 50),
‘big’: (270, 270),
}
当你使用这个特性时,图片管道将使用下面的格式来创建各个特定尺寸的缩略图:
/thumbs//.jpg
其中:
是 IMAGES_THUMBS 字典关键字(small, big ,等)
是图像url的 SHA1 hash
例如使用 small 和 big 缩略图名字的图片文件:
/full/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
/thumbs/small/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
/thumbs/big/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg
第一个是从网站下载的完整图片。
滤出小图片
你可以丢掉那些过小的图片,只需在 IMAGES_MIN_HEIGHT 和 IMAGES_MIN_WIDTH 设置中指定最小允许的尺寸。
比如:
IMAGES_MIN_HEIGHT = 110
IMAGES_MIN_WIDTH = 110
注意:这些尺寸一点也不影响缩略图的生成。
默认情况下,没有尺寸限制,因此所有图片都将处理。
实现定制图片管道
下面是你可以在定制的图片管道里重写的方法:
class scrapy.contrib.pipeline.images.ImagesPipeline
get_media_requests(item, info)
在工作流程中可以看到,管道会得到图片的URL并从项目中下载。为了这么做,你需要重写 get_media_requests() 方法,并对各个图片URL返回一个Request:
def get_media_requests(self, item, info):
for image_url in item[‘image_urls’]:
yield scrapy.Request(image_url)
这些请求将被管道处理,当它们完成下载后,结果将以2-元素的元组列表形式传送到 item_completed() 方法:
success 是一个布尔值,当图片成功下载时为 True ,因为某个原因下载失败为False
image_info_or_error 是一个包含下列关键字的字典(如果成功为 True )或者出问题时为 Twisted Failure 。
url - 图片下载的url。这是从 get_media_requests() 方法返回请求的url。
path - 图片存储的路径(类似 IMAGES_STORE)
checksum - 图片内容的 MD5 hash
item_completed() 接收的元组列表需要保证与 get_media_requests() 方法返回请求的顺序相一致。下面是 results 参数的一个典型值:
[(True,
{‘checksum’: ‘2b00042f7481c7b056c4b410d28f33cf’,
‘path’: ‘full/7d97e98f8af710c7e7fe703abc8f639e0ee507c4.jpg’,
‘url’: ‘http://www.example.com/images/product1.jpg‘}),
(True,
{‘checksum’: ‘b9628c4ab9b595f72f280b90c4fd093d’,
‘path’: ‘full/1ca5879492b8fd606df1964ea3c1e2f4520f076f.jpg’,
‘url’: ‘http://www.example.com/images/product2.jpg‘}),
(False,
Failure(…))]
默认 get_media_requests() 方法返回 None ,这意味着项目中没有图片可下载。
item_completed(results, items, info)
当一个单独项目中的所有图片请求完成时(要么完成下载,要么因为某种原因下载失败), ImagesPipeline.item_completed() 方法将被调用。
item_completed() 方法需要返回一个输出,其将被送到随后的项目管道阶段,因此你需要返回(或者丢弃)项目,如你在任意管道里所做的一样。
这里是一个 item_completed() 方法的例子,其中我们将下载的图片路径(传入到results中)存储到 image_paths 项目组中,如果其中没有图片,我们将丢弃项目:
from scrapy.exceptions import DropItem
def item_completed(self, results, item, info):
image_paths = [x[‘path’] for ok, x in results if ok]
if not image_paths:
raise DropItem(“Item contains no images”)
item[‘image_paths’] = image_paths
return item
默认情况下, item_completed() 方法返回项目。
定制图片管道的例子
下面是一个图片管道的完整例子,其方法如上所示:
import scrapy
from scrapy.contrib.pipeline.images import ImagesPipeline
from scrapy.exceptions import DropItem
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
for image_url in item['image_urls']:
yield scrapy.Request(image_url)
def item_completed(self, results, item, info):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
item['image_paths'] = image_paths
return item
Ubuntu 软件包
0.10 新版功能.
Scrapinghub 发布的apt-get可获取版本通常比Ubuntu里更新,并且在比 Github 仓库 (master & stable branches) 稳定的同时还包括了最新的漏洞修复。
用法:
把Scrapy签名的GPG密钥添加到APT的钥匙环中:
sudo apt-key adv –keyserver hkp://keyserver.ubuntu.com:80 –recv 627220E7
执行如下命令,创建 /etc/apt/sources.list.d/scrapy.list 文件:
echo ‘deb http://archive.scrapy.org/ubuntu scrapy main’ | sudo tee /etc/apt/sources.list.d/scrapy.list
更新包列表并安装 scrapy-0.25:
sudo apt-get update && sudo apt-get install scrapy-0.25
注解
如果你要升级Scrapy,请重复步骤3。
警告
debian官方源提供的 python-scrapy 是一个非常老的版本且不再获得Scrapy团队支持。
Scrapyd
Scrapyd被移动成为一个单独的项目。 其文档当前被托管在:
http://scrapyd.readthedocs.org/
自动限速(AutoThrottle)扩展
该扩展能根据Scrapy服务器及您爬取的网站的负载自动限制爬取速度。
设计目标
更友好的对待网站,而不使用默认的下载延迟0。
自动调整scrapy来优化下载速度,使得用户不用调节下载延迟及并发请求数来找到优化的值。 用户只需指定允许的最大并发请求数,剩下的都交给扩展来完成。
扩展是如何实现的
在Scrapy中,下载延迟是通过计算建立TCP连接到接收到HTTP包头(header)之间的时间来测量的。
注意,由于Scrapy可能在忙着处理spider的回调函数或者无法下载,因此在合作的多任务环境下准确测量这些延迟是十分苦难的。 不过,这些延迟仍然是对Scrapy(甚至是服务器)繁忙程度的合理测量,而这扩展就是以此为前提进行编写的。
限速算法
算法根据以下规则调整下载延迟及并发数:
spider永远以1并发请求数及 AUTOTHROTTLE_START_DELAY 中指定的下载延迟启动。
当接收到回复时,下载延迟会调整到该回复的延迟与之前下载延迟之间的平均值。
注解
AutoThrottle扩展尊重标准Scrapy设置中的并发数及延迟。这意味着其永远不会设置一个比 DOWNLOAD_DELAY 更低的下载延迟或者比 CONCURRENT_REQUESTS_PER_DOMAIN 更高的并发数 (或 CONCURRENT_REQUESTS_PER_IP ,取决于您使用哪一个)。
设置
下面是控制AutoThrottle扩展的设置:
AUTOTHROTTLE_ENABLED
AUTOTHROTTLE_START_DELAY
AUTOTHROTTLE_MAX_DELAY
AUTOTHROTTLE_DEBUG
CONCURRENT_REQUESTS_PER_DOMAIN
CONCURRENT_REQUESTS_PER_IP
DOWNLOAD_DELAY
更多内容请参考 限速算法 。
AUTOTHROTTLE_ENABLED
默认: False
启用AutoThrottle扩展。
AUTOTHROTTLE_START_DELAY
默认: 5.0
初始下载延迟(单位:秒)。
AUTOTHROTTLE_MAX_DELAY
默认: 60.0
在高延迟情况下最大的下载延迟(单位秒)。
AUTOTHROTTLE_DEBUG
默认: False
起用AutoThrottle调试(debug)模式,展示每个接收到的response。 您可以通过此来查看限速参数是如何实时被调整的。
Benchmarking
0.17 新版功能.
Scrapy提供了一个简单的性能测试工具。其创建了一个本地HTTP服务器,并以最大可能的速度进行爬取。 该测试性能工具目的是测试Scrapy在您的硬件上的效率,来获得一个基本的底线用于对比。 其使用了一个简单的spider,仅跟进链接,不做任何处理。
运行:
scrapy bench
您能看到类似的输出:
2013-05-16 13:08:46-0300 [scrapy] INFO: Scrapy 0.17.0 started (bot: scrapybot)
2013-05-16 13:08:47-0300 [follow] INFO: Spider opened
2013-05-16 13:08:47-0300 [follow] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2013-05-16 13:08:48-0300 [follow] INFO: Crawled 74 pages (at 4440 pages/min), scraped 0 items (at 0 items/min)
2013-05-16 13:08:49-0300 [follow] INFO: Crawled 143 pages (at 4140 pages/min), scraped 0 items (at 0 items/min)
2013-05-16 13:08:50-0300 [follow] INFO: Crawled 210 pages (at 4020 pages/min), scraped 0 items (at 0 items/min)
2013-05-16 13:08:51-0300 [follow] INFO: Crawled 274 pages (at 3840 pages/min), scraped 0 items (at 0 items/min)
2013-05-16 13:08:52-0300 [follow] INFO: Crawled 343 pages (at 4140 pages/min), scraped 0 items (at 0 items/min)
2013-05-16 13:08:53-0300 [follow] INFO: Crawled 410 pages (at 4020 pages/min), scraped 0 items (at 0 items/min)
2013-05-16 13:08:54-0300 [follow] INFO: Crawled 474 pages (at 3840 pages/min), scraped 0 items (at 0 items/min)
2013-05-16 13:08:55-0300 [follow] INFO: Crawled 538 pages (at 3840 pages/min), scraped 0 items (at 0 items/min)
2013-05-16 13:08:56-0300 [follow] INFO: Crawled 602 pages (at 3840 pages/min), scraped 0 items (at 0 items/min)
2013-05-16 13:08:57-0300 [follow] INFO: Closing spider (closespider_timeout)
2013-05-16 13:08:57-0300 [follow] INFO: Crawled 666 pages (at 3840 pages/min), scraped 0 items (at 0 items/min)
2013-05-16 13:08:57-0300 [follow] INFO: Dumping Scrapy stats:
{‘downloader/request_bytes’: 231508,
‘downloader/request_count’: 682,
‘downloader/request_method_count/GET’: 682,
‘downloader/response_bytes’: 1172802,
‘downloader/response_count’: 682,
‘downloader/response_status_count/200’: 682,
‘finish_reason’: ‘closespider_timeout’,
‘finish_time’: datetime.datetime(2013, 5, 16, 16, 8, 57, 985539),
‘log_count/INFO’: 14,
‘request_depth_max’: 34,
‘response_received_count’: 682,
‘scheduler/dequeued’: 682,
‘scheduler/dequeued/memory’: 682,
‘scheduler/enqueued’: 12767,
‘scheduler/enqueued/memory’: 12767,
‘start_time’: datetime.datetime(2013, 5, 16, 16, 8, 47, 676539)}
2013-05-16 13:08:57-0300 [follow] INFO: Spider closed (closespider_timeout)
这说明了您的Scrapy能以3900页面/分钟的速度爬取。注意,这是一个非常简单,仅跟进链接的spider。 任何您所编写的spider会做更多处理,从而减慢爬取的速度。 减慢的程度取决于spider做的处理以及其是如何被编写的。
未来会有更多的用例会被加入到性能测试套装中,以覆盖更多常见的情景。
Jobs: 暂停,恢复爬虫
有些情况下,例如爬取大的站点,我们希望能暂停爬取,之后再恢复运行。
Scrapy通过如下工具支持这个功能:
一个把调度请求保存在磁盘的调度器
一个把访问请求保存在磁盘的副本过滤器[duplicates filter]
一个能持续保持爬虫状态(键/值对)的扩展
Job 路径
要启用持久化支持,你只需要通过 JOBDIR 设置 job directory 选项。这个路径将会存储 所有的请求数据来保持一个单独任务的状态(例如:一次spider爬取(a spider run))。必须要注意的是,这个目录不允许被不同的spider 共享,甚至是同一个spider的不同jobs/runs也不行。也就是说,这个目录就是存储一个 单独 job的状态信息。
怎么使用
要启用一个爬虫的持久化,运行以下命令:
scrapy crawl somespider -s JOBDIR=crawls/somespider-1
然后,你就能在任何时候安全地停止爬虫(按Ctrl-C或者发送一个信号)。恢复这个爬虫也是同样的命令:
scrapy crawl somespider -s JOBDIR=crawls/somespider-1
保持状态
有的时候,你希望持续保持一些运行长时间的蜘蛛的状态。这时您可以使用 spider.state 属性, 该属性的类型必须是dict. scrapy提供了内置扩展负责在spider启动或结束时,从工作路径(job directory)中序列化、存储、加载属性。
下面这个例子展示了使用spider state的回调函数(callback)(简洁起见,省略了其他的代码):
def parse_item(self, response):
# parse item here
self.state[‘items_count’] = self.state.get(‘items_count’, 0) + 1
持久化的一些坑
如果你想要使用Scrapy的持久化支持,还有一些东西您需要了解:
Cookies的有效期
Cookies是有有效期的(可能过期)。所以如果你没有把你的爬虫及时恢复,那么他可能在被调度回去的时候 就不能工作了。当然如果你的爬虫不依赖cookies就不会有这个问题了。
请求序列化
请求是由 pickle 进行序列化的,所以你需要确保你的请求是可被pickle序列化的。 这里最常见的问题是在在request回调函数中使用 lambda 方法,导致无法序列化。
例如, 这样就会有问题:
def some_callback(self, response):
somearg = ‘test’
return scrapy.Request(‘http://www.example.com‘, callback=lambda r: self.other_callback(r, somearg))
def other_callback(self, response, somearg):
print “the argument passed is:”, somearg
这样才对:
def some_callback(self, response):
somearg = ‘test’
return scrapy.Request(‘http://www.example.com‘, meta={‘somearg’: somearg})
这里的实例代码有错,应该是(译者注)
return scrapy.Request(‘http://www.example.com‘, meta={‘somearg’: somearg}, callback=self.other_callback)
def other_callback(self, response):
somearg = response.meta[‘somearg’]
print “the argument passed is:”, somearg
DjangoItem
DjangoItem 是一个item的类,其从Django模型中获取字段(field)定义。 您可以简单地创建一个 DjangoItem 并指定其关联的Django模型。
除了获得您item中定义的字段外, DjangoItem 提供了创建并获得一个具有item数据的Django模型实例(Django model instance)的方法。
使用DjangoItem
DjangoItem 使用方法与Django中的ModelForms类似。您创建一个子类, 并定义其 django_model 属性。这样,您就可以得到一个字段与Django模型字段(model field)一一对应的item了。
另外,您可以定义模型中没有的字段,甚至是覆盖模型中已经定义的字段。
让我们来看个例子:
创造一个Django模型:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=255)
age = models.IntegerField()
定义一个基本的 DjangoItem:
from scrapy.contrib.djangoitem import DjangoItem
class PersonItem(DjangoItem):
django_model = Person
DjangoItem 的使用方法和 Item 类似:
p = PersonItem()
p[‘name’] = ‘John’
p[‘age’] = ‘22’
要从item中获取Django模型,调用 DjangoItem 中额外的方法 save():person = p.save()
person.name
‘John’
person.age
‘22’
person.id
1
当我们调用 save() 时,模型已经保存了。我们可以在调用时带上 commit=False 来避免保存, 并获取到一个未保存的模型:person = p.save(commit=False)
person.name
‘John’
person.age
‘22’
person.id
None
正如之前所说的,我们可以在item中加入字段:
import scrapy
from scrapy.contrib.djangoitem import DjangoItem
class PersonItem(DjangoItem):
django_model = Person
sex = scrapy.Field()
p = PersonItem()
p[‘name’] = ‘John’
p[‘age’] = ‘22’
p[‘sex’] = ‘M’
注解
当执行 save() 时添加到item的字段不会有作用(taken into account)。
并且我们可以覆盖模型中的字段:
class PersonItem(DjangoItem):
django_model = Person
name = scrapy.Field(default=’No Name’)
这在提供字段属性时十分有用,例如您项目中使用的默认或者其他属性一样。
DjangoItem注意事项
DjangoItem提供了在Scrapy项目中集成DjangoItem的简便方法,不过需要注意的是, 如果在Scrapy中爬取大量(百万级)的item时,Django ORM扩展得并不是很好(not scale well)。 这是因为关系型后端对于一个密集型(intensive)应用(例如web爬虫)并不是一个很好的选择, 尤其是具有大量的索引的数据库。
配置Django的设置
在Django应用之外使用Django模型(model),您需要设置 DJANGO_SETTINGS_MODULE 环境变量以及 –大多数情况下– 修改 PYTHONPATH 环境变量来导入设置模块。
完成这个配置有很多方法,具体选择取决您的情况及偏好。 下面详细给出了完成这个配置的最简单方法。
假设您项目的名称为 mysite ,位于 /home/projects/mysite 且用 Person 模型创建了一个应用 myapp 。 这意味着您的目录结构类似于:
/home/projects/mysite
├── manage.py
├── myapp
│ ├── init.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── mysite
├── init.py
├── settings.py
├── urls.py
└── wsgi.py
接着您需要将 /home/projects/mysite 加入到 PYTHONPATH 环境变量中并将 mysite.settings 设置为 DJANGO_SETTINGS_MODULE 环境变量。 这可以在Scrapy设置文件中添加下列代码:
import sys
sys.path.append(‘/home/projects/mysite’)
import os
os.environ[‘DJANGO_SETTINGS_MODULE’] = ‘mysite.settings’
注意,由于我们在python运行环境中,所以我们修改 sys.path 变量而不是 PYTHONPATH 环境变量。 如果所有设置正确,您应该可以运行 scrapy shell 命令并且导入 Person 模型(例如 from myapp.models import Person)。
常见问题(FAQ)
常见问题的解决办法。
调试(Debugging)Spiders
学习如何对scrapy spider的常见问题进行debug。
Spiders Contracts
学习如何使用contract来测试您的spider。
实践经验(Common Practices)
熟悉Scrapy的一些惯例做法。
通用爬虫(Broad Crawls)
调整Scrapy来适应并发爬取大量网站(a lot of domains)。
借助Firefox来爬取
了解如何使用Firefox及其他有用的插件来爬取数据。
使用Firebug进行爬取
了解如何使用Firebug来爬取数据。
调试内存溢出
了解如何查找并让您的爬虫避免内存泄露。
下载项目图片
下载爬取的item中的图片。
Ubuntu 软件包
在Ubuntu下下载最新的Scrapy。
Scrapyd
在生产环境中部署您的Scrapy项目。
自动限速(AutoThrottle)扩展
根据负载(load)动态调节爬取速度。
Benchmarking
在您的硬件平台上测试Scrapy的性能。
Jobs: 暂停,恢复爬虫
学习如何停止和恢复爬虫
DjangoItem
使用Django模型编写爬取的item
扩展Scrapy
架构概览
本文档介绍了Scrapy架构及其组件之间的交互。
概述
接下来的图表展现了Scrapy的架构,包括组件及在系统中发生的数据流的概览(绿色箭头所示)。 下面对每个组件都做了简单介绍,并给出了详细内容的链接。数据流如下所描述。
Scrapy architecture
组件
Scrapy Engine
引擎负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。 详细内容查看下面的数据流(Data Flow)部分。
调度器(Scheduler)
调度器从引擎接受request并将他们入队,以便之后引擎请求他们时提供给引擎。
下载器(Downloader)
下载器负责获取页面数据并提供给引擎,而后提供给spider。
Spiders
Spider是Scrapy用户编写用于分析response并提取item(即获取到的item)或额外跟进的URL的类。 每个spider负责处理一个特定(或一些)网站。 更多内容请看 Spiders 。
Item Pipeline
Item Pipeline负责处理被spider提取出来的item。典型的处理有清理、 验证及持久化(例如存取到数据库中)。 更多内容查看 Item Pipeline 。
下载器中间件(Downloader middlewares)
下载器中间件是在引擎及下载器之间的特定钩子(specific hook),处理Downloader传递给引擎的response。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。更多内容请看 下载器中间件(Downloader Middleware) 。
Spider中间件(Spider middlewares)
Spider中间件是在引擎及Spider之间的特定钩子(specific hook),处理spider的输入(response)和输出(items及requests)。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。更多内容请看 Spider中间件(Middleware) 。
数据流(Data flow)
Scrapy中的数据流由执行引擎控制,其过程如下:
引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。
引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。
引擎向调度器请求下一个要爬取的URL。
调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)。
一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎。
引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。
Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。
引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。
(从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。
事件驱动网络(Event-driven networking)
Scrapy基于事件驱动网络框架 Twisted 编写。因此,Scrapy基于并发性考虑由非阻塞(即异步)的实现。
关于异步编程及Twisted更多的内容请查看下列链接:
Introduction to Deferreds in Twisted
Twisted - hello, asynchronous programming
下载器中间件(Downloader Middleware)
下载器中间件是介于Scrapy的request/response处理的钩子框架。 是用于全局修改Scrapy request和response的一个轻量、底层的系统。
激活下载器中间件
要激活下载器中间件组件,将其加入到 DOWNLOADER_MIDDLEWARES 设置中。 该设置是一个字典(dict),键为中间件类的路径,值为其中间件的顺序(order)。
这里是一个例子:
DOWNLOADER_MIDDLEWARES = {
‘myproject.middlewares.CustomDownloaderMiddleware’: 543,
}
DOWNLOADER_MIDDLEWARES 设置会与Scrapy定义的 DOWNLOADER_MIDDLEWARES_BASE 设置合并(但不是覆盖), 而后根据顺序(order)进行排序,最后得到启用中间件的有序列表: 第一个中间件是最靠近引擎的,最后一个中间件是最靠近下载器的。
关于如何分配中间件的顺序请查看 DOWNLOADER_MIDDLEWARES_BASE 设置,而后根据您想要放置中间件的位置选择一个值。 由于每个中间件执行不同的动作,您的中间件可能会依赖于之前(或者之后)执行的中间件,因此顺序是很重要的。
如果您想禁止内置的(在 DOWNLOADER_MIDDLEWARES_BASE 中设置并默认启用的)中间件, 您必须在项目的 DOWNLOADER_MIDDLEWARES 设置中定义该中间件,并将其值赋为 None 。 例如,如果您想要关闭user-agent中间件:
DOWNLOADER_MIDDLEWARES = {
‘myproject.middlewares.CustomDownloaderMiddleware’: 543,
‘scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware’: None,
}
最后,请注意,有些中间件需要通过特定的设置来启用。更多内容请查看相关中间件文档。
编写您自己的下载器中间件
编写下载器中间件十分简单。每个中间件组件是一个定义了以下一个或多个方法的Python类:
class scrapy.contrib.downloadermiddleware.DownloaderMiddleware
process_request(request, spider)
当每个request通过下载中间件时,该方法被调用。
process_request() 必须返回其中之一: 返回 None 、返回一个 Response 对象、返回一个 Request 对象或raise IgnoreRequest 。
如果其返回 None ,Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用, 该request被执行(其response被下载)。
如果其返回 Response 对象,Scrapy将不会调用 任何 其他的 process_request() 或 process_exception() 方法,或相应地下载函数; 其将返回该response。 已安装的中间件的 process_response() 方法则会在每个response返回时被调用。
如果其返回 Request 对象,Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。
如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。
参数:
request (Request 对象) – 处理的request
spider (Spider 对象) – 该request对应的spider
process_response(request, response, spider)
process_request() 必须返回以下之一: 返回一个 Response 对象、 返回一个 Request 对象或raise一个 IgnoreRequest 异常。
如果其返回一个 Response (可以与传入的response相同,也可以是全新的对象), 该response会被在链中的其他中间件的 process_response() 方法处理。
如果其返回一个 Request 对象,则中间件链停止, 返回的request会被重新调度下载。处理类似于 process_request() 返回request所做的那样。
如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。
参数:
request (Request 对象) – response所对应的request
response (Response 对象) – 被处理的response
spider (Spider 对象) – response所对应的spider
process_exception(request, exception, spider)
当下载处理器(download handler)或 process_request() (下载中间件)抛出异常(包括 IgnoreRequest 异常)时, Scrapy调用 process_exception() 。
process_exception() 应该返回以下之一: 返回 None 、 一个 Response 对象、或者一个 Request 对象。
如果其返回 None ,Scrapy将会继续处理该异常,接着调用已安装的其他中间件的 process_exception() 方法,直到所有中间件都被调用完毕,则调用默认的异常处理。
如果其返回一个 Response 对象,则已安装的中间件链的 process_response() 方法被调用。Scrapy将不会调用任何其他中间件的 process_exception() 方法。
如果其返回一个 Request 对象, 则返回的request将会被重新调用下载。这将停止中间件的 process_exception() 方法执行,就如返回一个response的那样。
参数:
request (是 Request 对象) – 产生异常的request
exception (Exception 对象) – 抛出的异常
spider (Spider 对象) – request对应的spider
内置下载中间件参考手册
本页面介绍了Scrapy自带的所有下载中间件。关于如何使用及编写您自己的中间件,请参考 downloader middleware usage guide.
关于默认启用的中间件列表(及其顺序)请参考 DOWNLOADER_MIDDLEWARES_BASE 设置。
CookiesMiddleware
class scrapy.contrib.downloadermiddleware.cookies.CookiesMiddleware
该中间件使得爬取需要cookie(例如使用session)的网站成为了可能。 其追踪了web server发送的cookie,并在之后的request中发送回去, 就如浏览器所做的那样。
以下设置可以用来配置cookie中间件:
COOKIES_ENABLED
COOKIES_DEBUG
单spider多cookie session
0.15 新版功能.
Scrapy通过使用 cookiejar Request meta key来支持单spider追踪多cookie session。 默认情况下其使用一个cookie jar(session),不过您可以传递一个标示符来使用多个。
例如:
for i, url in enumerate(urls):
yield scrapy.Request(“http://www.example.com“, meta={‘cookiejar’: i},
callback=self.parse_page)
需要注意的是 cookiejar meta key不是”黏性的(sticky)”。 您需要在之后的request请求中接着传递。例如:
def parse_page(self, response):
# do some processing
return scrapy.Request(“http://www.example.com/otherpage“,
meta={‘cookiejar’: response.meta[‘cookiejar’]},
callback=self.parse_other_page)
COOKIES_ENABLED
默认: True
是否启用cookies middleware。如果关闭,cookies将不会发送给web server。
COOKIES_DEBUG
默认: False
如果启用,Scrapy将记录所有在request(Cookie 请求头)发送的cookies及response接收到的cookies(Set-Cookie 接收头)。
下边是启用 COOKIES_DEBUG 的记录的样例:
2011-04-06 14:35:10-0300 [diningcity] INFO: Spider opened
2011-04-06 14:35:10-0300 [diningcity] DEBUG: Sending cookies to: