Scrapy 1.5 文档¶
这篇文档包含了所有你需要了解的关于Scrapy的知识。
获得帮助¶
遇到麻烦? 我们能提供帮助!
- 试试常见问题解答 - 它有一些常见问题的答案。
- 寻找具体信息? 试试索引或模块索引。
- 使用scrapy标记在StackOverflow中询问或搜索问题。
- 在Scrapy subreddit中询问或搜索问题。
- 搜索scrapy-users邮件列表的存档问题。
- 在#scrapy IRC channel中提问
- 在我们的问题跟踪器中用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,extensions和pipelines)。
- 广泛的内置扩展和中间件处理:
- cookies和会话处理
- HTTP特性,如压缩,认证,缓存
- 用户代理欺骗
- robots.txt
- 爬行深度限制
- 更多
- 一个Telnet控制台,用于连接Scrapy进程中运行的Python控制台,以反编译和调试您的爬虫程序
- 还有其他有用的东西,如可重复使用的用于抓取站点地图和XML/CSV中网站的spider,与抓取的信息关联的自动下载图片(或任何其他媒体)的媒体管道,缓存DNS解析器,等等!
安装指南¶
安装Scrapy ¶
Scrapy在Python 2.7和Python 3.4以上运行,在CPython(默认Python实现)和PyPy(从PyPy 5.9开始)下运行。
如果您使用Anaconda或Miniconda,可以从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,一个异步网络框架
- cryptography和pyOpenSSL,用来处理各种网络级安全需求
已测试过Scrapy的最低版本是:
- Twisted 14.0
- lxml 3.4
- pyOpenSSL 0.14
Scrapy可能会使用这些软件包的旧版本,但不能保证它会继续工作,因为它没有经过测试。
其中一些软件包本身依赖于非Python包,这可能需要额外的安装步骤,具体取决于您的平台。 请查阅平台特性指南。
如果出现依赖关系相关的任何问题,请参阅其各自的安装说明:
使用虚拟环境(推荐)¶
TL; DR:我们建议将Scrapy安装在所有平台的虚拟环境中。
Python软件包既可以全局安装(也可以是系统范围),也可以安装在用户空间中。 我们不建议在系统范围安装scrapy。
相反,我们建议您在所谓的“虚拟环境”(virtualenv)中安装scrapy。
Virtualenvs可以使Scrapy不会与已安装的Python系统软件包发生冲突(这可能会破坏您的一些系统工具和脚本),仍然可以正常使用pip
(不需要sudo
等)。
为从虚拟环境开始,参照虚拟环境指南. 要全局安装它(全局安装在这里实际上有帮助),将会被运行:
$ [sudo] pip install virtualenv
请查看用户指南了解如何创建您的virtualenv。
Note
如果您使用Linux或OS X,virtoalenvwrapper是创建virtualenvs的便利工具。
一旦你创建了一个virtualenv,你就可以像安装任何其他Python包一样,通过pip
安装scrapy。
(您可能需要事先安装非Python依赖关系,请参阅下面的平台特定指南)。
创建Python virtualenvs可以使用Python 2或Python 3作为默认。
- 如果你想用Python 3安装scrapy,请在Python 3 virtualenv中安装scrapy。
- 如果你想用Python 2安装scrapy,请在Python 2 virtualenv中安装scrapy。
平台特定的安装说明¶
Windows¶
尽管可以使用pip在Windows上安装Scrapy,但我们建议您安装Anaconda或Miniconda,并使用conda-forge渠道中的软件包,这将避免大多数安装问题。
当你安装Anaconda或Miniconda完成之后,安装Scrapy:
conda install -c conda-forge scrapy
Ubuntu 14.04或更高版本¶
Scrapy 目前在测试最新版本的lxml,twisted和pyOpenSSL,并且在与最新的Ubuntu发行版本兼容 同时也支持老版本的Ubuntu,比如Ubuntu 14.04,尽管TLS有潜在问题
不要 使用 python-scrapy
提供自Ubuntu, 太老同时速度太慢,无法赶上最新的Scrapy
为了安装scrapy在Ubuntu或者基于Ubuntu的系统,你需要安装以下依赖包
sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev
python-dev
,zlib1g-dev
,libxml2-dev
和libxslt1-dev
依赖于lxml
libssl-dev
和libffi-dev
要求安装cryptography
如果你想在python 3上安装scrapy,你同时需要安装python 3
sudo apt-get install python3 python3-dev
在 虚拟环境中:virtualenv, 你可以用以下 pip
安装Scrapy:
pip install scrapy
Note
同样无python依赖包可被用于安装Scrapy在Debian Jessie8.0及更高版本
Mac OS X¶
构建Scrapy的依赖需要存在C编译器和开发头文件。 在OS X上,这通常由Apple的Xcode开发工具提供。 要安装Xcode命令行工具,请打开一个终端窗口并运行:
xcode-select --install
已知问题可能会阻止pip
更新系统包。 必须解决这个问题才能成功安装Scrapy及其依赖项。 以下是一些建议的解决方案
(推荐) 不要使用系统python,安装一个不会与系统其余部分冲突的新的更新版本。 下面介绍了如何使用 homebrew 软件包管理器:
按照http://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未经测试。
你可以通过运行以下指令来检查scrapy是否安装 scrapy bench
.
如果这条指令给与以下错误
TypeError: ... got 2 unexpected keyword arguments
,这意味着安装工具不能获取 PyPy-specific依赖包。
为解决此问题,运行以下指令pip install 'PyPyDispatcher>=2.1.0'
.
Scrapy教程¶
在本教程中,我们假定您的系统上已经安装了Scrapy。 如果不是这种情况,请参阅安装指南。
我们将爬取quota.toscrape.com,一个列出著名作家语录的网站。
本教程将引导您完成这些任务:
- 创建一个新的Scrapy项目
- 编写一个spider来抓取网站并提取数据
- 使用命令行导出爬取的数据
- 更改spider递归地跟随链接
- 使用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.html和quotes-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:简要介绍¶
>>> 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
对象中提取title
,author
和tags
>>> 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:
scrapy crawl quotes -o quotes.jl
JSON行格式非常有用,因为它类似于流,您可以轻松地向其添加新记录。 当您运行两次时,它不会发生和JSON相同的问题。 另外,由于每条记录都是一条独立的行,因此可以处理大文件而不必将所有内容都放在内存中,因此有些工具(如JQ)可帮助在命令行执行此操作。
在小型项目中(如本教程中的),应该足够了。
但是,如果您想使用爬虫项目执行更复杂的事情,可以编写Item Pipeline。 项目创建时,已经为您设置了Item Pipelines的预留文件,位于tutorial/pipelines.py
中。 尽管如果你只是想存储抓取的数据,你不需要实现任何的item pipeline。
找到下个链接¶
比方说,您不需要从http://quotes.toscrape.com中的前两个页面中抓取内容,而是需要来自网站中所有页面的格言。
现在您已经知道如何从网页中提取数据,我们来看看如何从它们中找到链接。
首先要提取我们想要继续处理的页面的链接。 检视我们的页面,我们可以看到有一个带有标记的下一页的链接:
<ul class="pager">
<li class="next">
<a href="/page/2/">Next <span aria-hidden="true">→</span></a>
</li>
</ul>
我们可以尝试在shell中提取它:
>>> response.css('li.next a').extract_first()
'<a href="/page/2/">Next <span aria-hidden="true">→</span></a>'
这会获取锚点元素,但我们需要属性href
。 为此,Scrapy支持一个CSS扩展,让您选择属性内容,如下所示:
>>> response.css('li.next a::attr(href)').extract_first()
'/page/2/'
现在让我们看看将Spider修改为递归地follow到下一页的链接,从中提取数据:
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
]
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(),
}
next_page = response.css('li.next a::attr(href)').extract_first()
if next_page is not None:
next_page = response.urljoin(next_page)
yield scrapy.Request(next_page, callback=self.parse)
现在,在提取数据后,parse()
方法查找到下一页的链接,使用urljoin()
方法构建完整的绝对URL(因为链接可以是相对的),并产生一个新的请求到下一个页面,将自己作为回调函数来处理下一页的数据提取,并保持遍历所有页面的抓取。
在这里您将看到Scrapy的跟随链接机制:当您在回调方法中产生请求时,Scrapy会安排发送请求并注册一个回调方法,以便在请求结束时执行。
使用这种方法,您可以根据您定义的规则构建复杂的抓取工具,并根据所访问的页面提取不同类型的数据。
在我们的例子中,它创建了一个循环,找下一页的所有链接,直到它找不到。这种做法对于抓取分页的博客,论坛和其他网站的链接是很方便的。
创建请求的快捷方式¶
您可以使用response.follow
作为创建Request对象的快捷方式:
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
]
def parse(self, response):
for quote in response.css('div.quote'):
yield {
'text': quote.css('span.text::text').extract_first(),
'author': quote.css('span small::text').extract_first(),
'tags': quote.css('div.tags a.tag::text').extract(),
}
next_page = response.css('li.next a::attr(href)').extract_first()
if next_page is not None:
yield response.follow(next_page, callback=self.parse)
与scrapy.Request不同,response.follow
直接支持相对URL - 无需调用urljoin。 请注意,response.follow
只是返回一个Request实例;你仍然需要产生这个请求。
您也可以将选择器传递给response.follow
代替字符串;该选择器应该提取必要的属性:
for href in response.css('li.next a::attr(href)'):
yield response.follow(href, callback=self.parse)
对于<a>
元素有一个快捷方式:response.follow
会自动使用它们的href属性。 因此代码可以进一步缩短:
for a in response.css('li.next a'):
yield response.follow(a, callback=self.parse)
注意
response.follow(response.css('li.next a'))
无效,因为response.css
返回一个包含所有结果选择器的类似列表的对象,而不是单个选择器。 如上例所示的for
循环,或response.follow(response.css('li.nexta')[0])
是可以的。
更多的例子和模式¶
这有另一个spider,它演示了回调和跟随链接,这次是为了抓取作者信息:
import scrapy
class AuthorSpider(scrapy.Spider):
name = 'author'
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
# follow links to author pages
for href in response.css('.author + a::attr(href)'):
yield response.follow(href, self.parse_author)
# follow pagination links
for href in response.css('li.next a::attr(href)'):
yield response.follow(href, self.parse)
def parse_author(self, response):
def extract_with_css(query):
return response.css(query).extract_first().strip()
yield {
'name': extract_with_css('h3.author-title::text'),
'birthdate': extract_with_css('.author-born-date::text'),
'bio': extract_with_css('.author-description::text'),
}
这个spider将从主页面开始,它将follow所有作者页面的链接,对每个页面调用parse_author
回调函数,同时也像我们之前看到的那样对页面链接使用parse
回调。
在这里,我们将回调传递给response.follow
作为位置参数以缩短代码长度;也可以使用scrapy.Request
。
parse_author
回调函数定义了一个从CSS查询中提取和清除数据的辅助函数,生成带有作者数据的Python字典。
这个spider演示的另一个有趣的事情是,即使多个语录有同一作者,我们也不必担心多次访问相同的作者页面。 默认情况下,Scrapy会将重复的请求过滤到已访问的URL中,避免因同一编程错误而导致服务器遇到过多的重复问题。 这可以通过设置DUPEFILTER_CLASS
进行配置。
希望现在您已经对如何使用Scrapy的follow链接和回调机制有了足够的了解。
作为利用follow链接机制的另一个示例spider,请查看CrawlSpider
类,以获得一个实现了简单规则引擎的通用蜘蛛,您可以在其上编写爬虫程序。
此外,常见模式是使用将附加数据传递到回调函数的技巧,对多个页面构建一个包含数据的item。
使用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也不例外。 出于这个原因,这里有名为quotesbot的Scrapy项目的例子,您可以使用它来玩和学习更多关于Scrapy的知识。 它包含两个http://quotes.toscrape.com的spider,一个使用CSS选择器,另一个使用XPath表达式。
quotesbot项目位于:http://github.com/scrapy/quotesbot。 您可以在项目的README中找到更多关于它的信息。
如果你熟悉git,你可以签出代码。 否则,您可以通过单击此处下载该项目的zip文件。
基本概念¶
命令行工具¶
0.10版本中的新功能。
Scrapy通过scrapy
命令行工具进行控制,在这里被称为“Scrapy工具”,以区别于我们称之为“命令”或“Scrapy命令”的子命令。
Scrapy工具提供了多种命令,用于多种目的,并且每个命令都接受一组不同的参数和选项。
(scrapy deploy
命令已在1.0版中被移除,变为独立的scrapyd-deploy
。 请参阅部署项目。)
配置设置¶
Scrapy将在标准位置的ini类型scrapy.cfg
文件中查找配置参数:
/etc/scrapy.cfg
或c:\scrapy\scrapy.cfg
(系统范围),〜/.config/scrapy.cfg
($XDG_CONFIG_HOME
)和〜/ .scrapy.cfg
($HOME
)用于全局(用户范围)设置和scrapy.cfg
在scrapy项目的根目录中(请参阅下一节)。
这些文件中的设置按所列出的优先顺序进行合并:用户定义的值具有比系统范围内的默认值更高的优先级,并且在定义时,项目范围的设置将覆盖所有其他文件。
Scrapy也可以通过一些环境变量进行配置。 目前可设置:
SCRAPY_SETTINGS_MODULE
(请参阅指定设置)SCRAPY_PROJECT
SCRAPY_PYTHON_SHELL
(请参阅Scrapy shell)
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 <command> -h
你可以看到所有可用的命令:
scrapy -h
有两种类型的命令,一些只能在Scrapy项目中使用的命令(特定于项目的命令);另外一些可以在没有活动的Scrapy项目的情况下使用(全局命令),尽管它们在项目中运行时可能略有不同因为他们会使用项目重写设置)。
全局命令:
仅限项目的命令:
startproject命令¶
- 语法:
scrapy startproject <project_name> [project_dir]
- 需要项目:不需要
在project_dir
目录下创建一个名为project_name
的新Scrapy项目。
如果未指定project_dir
,project_dir
将与project_name
相同。
用法示例:
$ scrapy startproject myproject
genspider¶
- 语法:
scrapy genspider [-t template] <name> <domain>
- 需要项目:不需要
如果在项目中调用,则在当前文件夹或当前项目的spiders
文件夹中创建一个新Spider。 <name>
参数用来设置Spider的 name
, <domain>
设置allowed_domains
和 start_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
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信息,这对于错误报告很有用。
自定义项目命令¶
您还可以使用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是您定义的为抓取和解析特定网站(在某些情况下是一组网站)页面的自定义行为的地方。
对于蜘蛛来说,抓取周期会经历这样一些事情:
首先生成抓取第一个URL的初始请求,为这些请求下载的响应来调用指定回调函数。
通过调用
start_requests()
方法来获得第一个执行请求,该方法默认为start_urls
中指定的URL生成Request
,parse
方法作为请求的回调函数。在回调函数中,解析响应(网页)并返回带有提取数据的字典,
Item
对象,Request
对象或这些对象的迭代。 这些请求还将包含一个回调(可能是相同的),然后由Scrapy下载,通过指定的回调处理它们的响应。在回调函数中,通常使用Selectors(您也可以使用BeautifulSoup,lxml或您喜欢的任何机制)解析页面内容,并使用解析的数据生成Item。
最后,从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的请求。假设您的目标网址为
http://www.example.com/1.html
,然后将'example.com'
添加到列表中。
-
start_urls
¶ 当没有特别指定的网址时,Spider将从哪个网址开始抓取的网址列表。 所以,下载的第一个页面将在这里列出。 随后的URL将从包含在起始URL中的数据中连续生成。
-
crawler
¶ 该属性在初始化类后由
from_crawler()
类方法设置,并链接到此spider实例绑定的Crawler
对象。Crawler在项目中封装了大量组件,以便进行单一入口访问(例如扩展,中间件,信号管理器等)。 请参阅Crawler API以了解更多关于它们的信息。
-
from_crawler
(crawler, *args, **kwargs)¶ 这是Scrapy用来创建Spider的类方法。
您可能不需要直接覆盖它,因为它默认实现是充当
__init__()
方法的代理,用给定参数args和命名参数 kwargs 。尽管如此,该方法会在新实例中设置
crawler
和settings
属性,以便稍后可以在Spider代码中访问它们。Parameters:
-
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_eval或json.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继承的属性(必须指定)之外,该类还支持一个新的属性:
这个Spider也暴露了一个可覆盖的方法:
抓取规则¶
-
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提要。 迭代器可以从
iternodes
,xml
和html
中选择。 出于性能原因,建议使用iternodes
迭代器,因为xml
和html
迭代器会一次生成整个DOM以解析它。 但是,使用html
作为迭代器时,可能会在解析具有错误标记的XML时很有用。要设置迭代器和标签名称,您必须定义以下类属性:
-
iterator
¶ 一个定义要使用的迭代器的字符串。 它可以是:
默认为:
'iternodes'
。
-
itertag
¶ 一个字符串,其中包含要迭代的节点(或元素)的名称。 例:
itertag = 'product'
-
namespaces
¶ 定义spider将要处理的文档中可用命名空间的
(prefix, uri)
元组列表。prefix
和uri
将用于使用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_response
和process_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_alternate_links
¶ 指定是否应该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自带提取数据的机制。 它们被称为选择器,因为它们“选择”由XPath或CSS表达式指定的HTML文档的某些部分。
XPath是用于选择XML文档中的节点的语言,也可以用于HTML。 CSS是一种将样式应用于HTML文档的语言。 它定义选择器将这些样式与特定的HTML元素相关联。
Scrapy选择器是建立在lxml库上的,这意味着它们在速度和解析精度上非常相似。
这个页面解释了选择器是如何工作的,并描述了它们小且简单的API,不像lxml API那样大,因为lxml库除了选择标记文件还可以用于许多其他任务,。
有关选择器API的完整参考,请参阅选择器参考
使用选择器¶
构造选择器¶
Scrapy选择器是被构造用来传递text或TextResponse
对象的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 http://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:
异常)。
这是通过根据需要传递许多命名参数来完成的。
使用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
是将被用于选择和提取数据的HtmlResponse
或XmlResponse
对象.text
是unicode字符串或utf-8编码文本,当response
不可用时使用。 同时使用text
和response
是未定义的行为。type
定义了选择器类型,它可以是“html”
,“xml”
或None
(默认)。如果
type
是None
,那么选择器将自动根据response
类型选择最佳类型(请参见下文),如果与text
一起使用则默认为“html “
。如果
type
为None
且传递了response
,则从响应类型推断选择器类型关系如下:"html"
forHtmlResponse
type"xml"
forXmlResponse
type"html"
for anything else
否则,如果设置了
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实体(除了<
和&
)
-
remove_namespaces
()¶ 删除所有名称空间,允许使用不含名称空间的xpaths来遍历文档。 见下面的例子。
SelectorList objects¶
-
class
scrapy.selector.
SelectorList
¶ SelectorList
类是内置的list
类的一个子类,它提供了一些额外的方法。-
xpath
(query)¶ 为此列表中的每个元素调用
.xpath()
方法,并将其结果展平为另一个SelectorList
。query
与Selector.xpath()
中的参数相同
-
css
(query)¶ 对此列表中每个元素调用
.css()
方法,并将其结果展平为另一个SelectorList
。query
与Selector.css()
中的参数相同
-
extract
()¶ 为此列表中的每个元素调用
.extract()
方法,并将其结果展平,作为unicode字符串列表。
-
re
()¶ 对此列表中每个元素调用
.re()
方法,并将其结果展平,作为unicode字符串列表。
-
HTML响应选择器示例¶
这里有几个Selector
的例子用来说明几个概念。
在所有情况下,我们都假设已经有一个Selector
用HtmlResponse
对象实例化,如下所示:
sel = Selector(html_response)
从HTML响应主体中选择全部
<h1>
元素,返回一个Selector
对象列表(即 一个SelectorList
对象):sel.xpath("//h1")
从HTML响应主体中提取所有
<h1>
文本,返回一个Unicode字符串列表:sel.xpath("//h1").extract() # this includes the h1 tag sel.xpath("//h1/text()").extract() # this excludes the h1 tag
遍历所有
<p>
标签,打印出他们的类属性:for node in sel.xpath("//p"): print node.xpath("@class").extract()
XML响应选择器示例¶
这里有几个例子来说明几个概念。 在这两种情况下,我们都假定已经有一个Selector
用XmlResponse
对象实例化,如下所示:
sel = Selector(xml_response)
从XML响应主体中选择全部
<product>
元素,返回一个Selector
对象列表,(即 一个SelectorList
对象):sel.xpath("//product")
从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 http://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'>,
...
如果您想知道为什么命名空间删除过程并不总是被默认调用从而而不必手动调用它,这是因为两个原因,按照相关性顺序,这两个原因是:
- 删除名称空间需要迭代和修改文档中的所有节点,这对于Scrapy搜索的所有文档来说是相当昂贵的操作
- 在某些情况下,实际上需要使用名称空间,以防某些元素名称在名称空间之间发生冲突。 虽然这种情况非常罕见。
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¶
这是使用上面声明的Product
Item展现对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对象¶
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位置提取的:
//div[@class="product_name"]
//div[@class="product_title"]
换句话说,通过使用add_xpath()
方法从两个XPath位置提取来收集数据。 这是稍后将分配给name
字段的数据。
之后,类似的调用用于price
和stock
字段(后者使用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)
将会发生:
- 来自
xpath1
的数据被提取并通过name
字段的输入处理器传递。 输入处理器的结果被收集并保存在Item Loader中(但尚未分配给Item)。 - 来自
xpath2
的数据被提取,并通过(1)中使用的相同的输入处理器传递。 输入处理器的结果附加到(1)中收集的数据(如果有的话)。 - 除了使用CSS选择器从
css
提取数据,这种情况与之前的类似,通过使用与(1)和(2)中相同输入处理器。 输入处理器的结果附加到(1)和(2)中收集的数据(如果有的话)。 - 这种情况也类似于以前的情况,不同之处在于要收集的值是直接分配的,而不是从XPath表达式或CSS选择器中提取。 但是,该值仍然通过输入处理器传递。 在这种情况下,由于该值不可迭代,在将其传递给输入处理器之前将其转换为单个元素的迭代器,因为输入处理器总是接收迭代器。
- 在步骤(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_processor
和ItemLoader.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'€', u'<span>1000</span>'])
>>> il.load_item()
{'name': u'Welcome to my website', 'price': u'1000'}
输入和输出处理器的优先顺序如下:
- Item加载器字段特定的属性:
field_in
和field_out
(最优先) - 字段元数据(
input_processor
和output_processor
键) - 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加载器上下文值:
通过修改当前活动的Item加载器上下文(
context
属性):loader = ItemLoader(product) loader.context['unit'] = 'cm'
在Item Loader实例化时(Item加载器构造函数的关键字参数存储在Item Loader上下文中):
loader = ItemLoader(product, unit='cm')
在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
中的类自动实例化。当使用selector或response参数实例化时,
ItemLoader
类提供了使用selectors从网页中提取数据的方便机制。参数: - item(
Item
对象) - 填入Item实例随后调用add_xpath()
,add_css()
或add_value()
。 - selector(
Selector
对象) - 提取数据的选择器,使用add_xpath()
(或add_css()
)或replace_xpath()
(或replace_css()
)方法。 - response(
Response
对象) - response使用default_selector_class
构造选择器,当selector参数给定时,response参数被忽略。
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()
传递给processors
和kwargs
,然后通过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: - xpath(str) - 提取数据的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字符串列表。参考
get_xpath()
为获取kwargs
.Parameters: xpath (str) – 为提取数据的XPath 例子:
# HTML 片段: <p class="product-name">Color TV</p> loader.add_xpath('name', '//p[@class="product-name"]') # HTML 片段: <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)¶ 类似
add_xpath()
但是会替换数据而非添加
-
get_css
(css, *processors, **kwargs)¶ 类似
ItemLoader.get_value()
接受一个可从列表中选择unicode字符串的css选择器替代特定的值ItemLoader
.Parameters: - css (str) – 用于提取数据的CSS选择器
- re (str or compiled regex) – 用于从所选CSS区域提取数据的正则表达式
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)¶ 类似
ItemLoader.add_value()
但是获取CSS选择器,代替一个用于提取从选择器连接的ItemLoader
unicode字符串列表的值See
get_css()
forkwargs
.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 (.*)')
-
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
实例具有以下属性:-
default_item_class
¶ Item类(或工厂),用于在构造函数中未给出Item时,实例化Item。
-
default_input_processor
¶ 默认输入处理器,用于没有指定输入处理器的字段。
-
default_output_processor
¶ 默认输出处理器,用于那些没有指定输出处理器的字段。
-
default_selector_class
¶ 如果在构造函数中只给出response,则该类用于构造
ItemLoader
的selector
。 如果在构造函数中给出了选择器,则忽略此属性。 该属性有时在子类中被覆盖。
-
selector
¶ 要从中提取数据的
Selector
对象。 它可以是构造函数中给出的,也可以是有response的构造函数使用default_selector_class
创建的选择器。 该属性是只读的。
- item(
嵌套Loader¶
从文档的子部分解析相关值时,创建嵌套的Loader可能很有用。 想象一下,您正在从页面的页脚中提取详细信息,如下所示:
例:
<footer>
<a class="social" href="http://facebook.com/whatever">Like Us</a>
<a class="social" href="http://twitter.com/whatever">Follow Us</a>
<a class="email" href="mailto:[email protected]">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
处理器通常用作输入处理器,因为通常使用 selectors的extract()
方法提取数据,该方法返回一个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(http://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的设置,不管安装哪个,您都可以将其配置为使用ipython
,bpython
或标准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控制台),它提供了一些额外的快捷方式功方便使用。
shell会话示例¶
以下是一个典型的shell会话示例,我们首先通过抓取http://scrapy.org页面开始,然后继续抓取http://reddit.com页面。 最后,我们将(Reddit)请求方法修改为POST并重新获取将会发生错误。 我们通过键入Ctrl-D(在Unix系统中)或Ctrl-Z(在Windows中)来结束会话。
请注意,这里提取的数据在你尝试时可能不尽相同,因为这些页面不是静态的,在测试时可能会发生变化。 这个例子的唯一目的是让你熟悉Scrapy shell的工作原理。
首先,我们启动shell:
scrapy shell 'http://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("http://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组件处理。参数:
另外,它们还可以实现以下方法:
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¶
FEED_FORMAT
:json
- 使用的导出器:
JsonItemExporter
- 如果您在对大型文件流使用JSON,请参阅警告。
JSON lines¶
FEED_FORMAT
:jsonlines
- 使用的导出器:
JsonLinesItemExporter
CSV¶
FEED_FORMAT
:csv
- 使用的导出器:
CsvItemExporter
- 要指定要导出的列及其顺序,请使用
FEED_EXPORT_FIELDS
。 其他文件导出器也可以使用此选项,但它对CSV很重要,因为与许多其他导出格式不同,CSV使用固定标题。
XML¶
FEED_FORMAT
:xml
- 使用的导出器:
XmlItemExporter
Pickle¶
FEED_FORMAT
:pickle
- 使用的导出器:
PickleItemExporter
Marshal¶
FEED_FORMAT
:marshal
- 使用的导出器:
MarshalItemExporter
存储器¶
使用导出文件时,您可以使用URI(通过 FEED_URI
设置)定义存储文件的位置。 文件导出支持由URI方案定义的多个存储后端类型。
支持的存储后端是:
- Local filesystem
- FTP
- S3 (requires botocore or boto)
- Standard output
如果所需的外部库不存在,某些存储后端可能不可用。 例如,只有安装了botocore或boto库(Scrapy仅支持Python 2上的boto)时,S3后端才可用。
存储URI参数¶
存储URI还可以包含在创建文件时被替换的参数。 这些参数是:
%(time)s
- 在创建文件时替换时间戳%(name)s
- 替换Spider名
任何其他命名参数将被相同名称的spider属性替换。 例如,在文件被创建的那一刻,%(site_id)s
将被替换为spider.site_id
属性。
以下是一些例子来说明:
- 每个Spider使用单独一个目录存储在FTP中:
ftp://user:[email protected]/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系统。
设置¶
这些是用于配置文件输出的设置:
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
选择最紧凑的表示。
目前仅通过JsonItemExporter
和XmlItemExporter
实现,即当您导出到.json
或.xml
时。
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_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使用Request
和Response
对象来抓取网站。
通常,在Spider中生成Request
对象,跨系统传递直到它们到达Downloader,Downloader执行请求并返回一个Response
对象给发出请求的Spider。
Request
和Response
类都有子类,它们添加了基类中非必需的功能。 这些在请求子类和响应子类中描述。
请求对象¶
-
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
(不会是unicode
或None
)。 - headers (dict) - 请求的头文件。 字典值可以是字符串(对于单值标题)或列表(对于多值标题)。 如果将
None
作为值传递,则不会发送HTTP头文件。 - cookies (dict or list) –
请求的cookies。 可以以两种形式发送。
- 使用字典:
request_with_cookies = Request(url="http://www.example.com", cookies={'currency': 'USD', 'country': 'UY'})
- 使用字典列表:
request_with_cookies = Request(url="http://www.example.com", cookies=[{'name': 'currency', 'value': 'USD', 'domain': 'example.com', 'path': '/currency'}])
后一种形式允许定制cookie的
domain
和path
属性。 这仅在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) - 发送到请求的标志,可用于日志记录或类似目的。
-
method
¶ 表示请求中的HTTP方法的字符串。 确保它是大写的。 例:
"GET"
,"POST"
,"PUT"
等
-
headers
¶ 一个包含请求头文件的类似字典的对象。
-
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及其内置扩展可识别一些特殊的键。
那些是:
dont_redirect
dont_retry
handle_httpstatus_list
handle_httpstatus_all
dont_merge_cookies
(seecookies
parameter ofRequest
constructor)cookiejar
dont_cache
redirect_urls
bindaddress
dont_obey_robotstxt
download_timeout
download_maxsize
download_latency
download_fail_on_dataloss
proxy
ftp_user
(更多信息参见FTP_USER
)ftp_password
(有关更多信息,请参阅FTP_PASSWORD
)referrer_policy
max_retry_times
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
参数。- response (
-
classmethod
请求使用示例¶
使用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
。
-
status
¶ 表示响应的HTTP状态的整数。 例如:
200
,404
。
-
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
数据。也可以看看
-
flags
¶ 包含此响应标识的列表。 标识是用于标记响应的标签。 例如:'cached'、'redirected’等。它们(被用在)显示Response的字符串表示形式(__str__ 方法)上,由logging引擎用于日志记录。
-
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主体创建一个对象,它将使用此 encoding
进行编码(记住body属性始终是一个字符串)。 如果encoding
为None
(默认值),则将在响应头文件和正文中查找编码。除了标准的
Response
对象外,TextResponse
对象还支持以下属性:-
text
¶ 响应主体,如unicode。
与
response.body.decode(response.encoding)
相同,但结果在第一次调用后被缓存,因此您可以多次访问response.text
而无需额外的开销。注意
unicode(response.body)
不是将响应主体转换为unicode的正确方法:您将使用系统默认编码(通常为ascii)代替响应编码。
-
encoding
¶ 一个包含响应编码的字符串。 通过尝试以下机制解决编码问题,顺序如下:
- 传递给构造函数encoding参数的编码
- 在Content-Type HTTP头中声明的编码。 如果此编码无效(即. 未知),它将被被忽略,尝试下一个解决机制。
- 在响应正文中声明的编码。 TextResponse类没有为此提供任何特殊功能。 但是,
HtmlResponse
和XmlResponse
类可以。 - 通过查看响应主体来推断编码。 这是更脆弱的方法,但也是最后的尝试。
除了标准的
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]
.
有关用法示例,请参阅创建请求的快捷方式。
-
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.http.Response
对象)提取将要被follow的链接的对象。
Scrapy中提供了scrapy.linkextractors.LinkExtractor
,但您可以通过实现简单的接口来创建自定义链接提取器以满足您的需求。
每个链接提取器所具有的唯一公共方法是extract_links
,它接收Response
对象并返回scrapy.link.Link
对象的列表。 这意味着链接提取器只需要被实例化一次,它的extract_links
方法被不同的响应多次调用提取要follow的链接。
通过一组规则,链接提取器被用在CrawlSpider
类(在Scrapy中可用)中,即使您没有继承CrawlSpider
类,您也可以在您的Spider中使用它,因为它的目的非常简单:提取链接。
内置链接提取器参考¶
scrapy.linkextractors
模块提供了与Scrapy捆绑在一起的链接提取器类。
默认链接提取器是LinkExtractor
,它与LxmlLinkExtractor
相同:
from scrapy.linkextractors import LinkExtractor
以前的Scrapy版本中曾经有其他链接提取器类,但现在已弃用。
LxmlLinkExtractor¶
-
class
scrapy.linkextractors.lxmlhtml.
LxmlLinkExtractor
(allow=(), deny=(), allow_domains=(), deny_domains=(), deny_extensions=None, restrict_xpaths=(), restrict_css=(), tags=('a', 'area'), attrs=('href', ), canonicalize=False, unique=True, process_value=None, strip=True)¶ LxmlLinkExtractor是推荐的链接提取器,具有方便的过滤选项。 它使用lxml的健壮HTMLParser实现。
参数: - allow (a regular expression (or list of)) - 与正则表达式(或正则表达式列表)相匹配的(绝对)url才能被提取。 如果没有给出(或空),它将匹配所有链接。
- deny (a regular expression (or 正则的列表)) – 与正则表达式(或正则表达式的列表)相匹配的(绝对)url将被排除(即, 不被提取)。 它优先于
allow
参数。 如果没有给出(或空),它不会排除任何链接。 - allow_domains (str or list) - 包含将被考虑用于提取链接的域的单个值或字符串列表
- deny_domains (str or list) - 包含不会被考虑用于提取链接的域的单个值或字符串列表
- deny_extensions (list) - 包含在提取链接时应该忽略的单个值或字符串列表扩展。
如果没有给出,它将默认为scrapy.linkextractors包中定义的
IGNORED_EXTENSIONS
列表。 - restrict_xpaths (str or list) - 是一个XPath(或XPath的列表),它定义了应该从中提取链接的响应内区域。 如果给定,只有那些XPath选择的文本才会被扫描查找链接。 详见下面的例子。
- restrict_css (str or list) - 一个CSS选择器(或者选择器列表),它定义了应该从中提取链接的响应内区域。
具有与
restrict_xpaths
相同的行为。 - tags (str or list) - 提取链接时要考虑的标记或标记列表。
默认为
('a', 'area')
。 - attrs(list) - 查找提取链接时应考虑的属性或属性列表(仅适用于
tags
参数中指定的标签)。 默认为('href',)
- canonicalize (boolean) - 规范化每个提取的url(使用w3lib.url.canonicalize_url)。 默认为
False
. 请注意,canonicalize_url用于重复检查;它可以更改服务器端可见的URL,因此对于使用规范化和原始URL的请求,响应可能会有所不同。 如果您使用LinkExtractor来follow链接,那么保持默认canonicalize=False
会使程序更强健。 - 唯一的(布尔值) - 是否应对抓取的链接应用重复筛选。
- process_value (callable) –
用于接收从标签和所扫描的属性中提取的每个值,并可以修改这些值返回一个新值或对于忽略的链接返回
None
。 如果没有给出,则process_value
默认为lambda x: x
.例如,要从此代码中提取链接:
<a href="javascript:goToPage('../other/page.html'); return false">Link text</a>
您可以在
process_value
中使用以下方法:def process_value(value): m = re.search("javascript:goToPage\('(.*?)'", value) if m: return m.group(1)
- strip (boolean) - 是否从提取的属性中去除空格。
根据HTML5标准,
<a>
和<area>
的href
属性,和其他元素如<img>
和<iframe>
的src
属性等等的前导空白和尾随空白必须去除,因此LinkExtractor默认去除空白字符。 设置strip=False
可将其关闭(例如,如果您从允许前/后空格的元素或属性中提取url)。
设置¶
Scrapy设置允许您自定义所有Scrapy组件的行为,包括核心,扩展,管道和Spider本身。
设置的基础结构提供了代码可用于从中提取配置值的键值映射的全局名称空间。 这些设置可以通过不同的机制进行填充,下面将对此进行介绍。
这些设置也是选择当前活动Scrapy项目的机制(假设你有很多)。
有关可用内置设置的列表,请参阅:内置设置参考。
指定设置¶
当你使用Scrapy时,你必须告诉它你要使用哪些设置。 您可以通过使用环境变量SCRAPY_SETTINGS_MODULE
来完成此操作。
SCRAPY_SETTINGS_MODULE
的值应该是Python路径语法,myproject.settings
。 请注意,设置模块应该位于Python 导入搜索路径中。
填充设置¶
可以使用不同的机制来填充设置,每种机制都有不同的优先级。 以下是按优先级降序排列的列表:
- 命令行选项(最优先)
- 每个Spider的设置
- 项目设置模块
- 每个命令的默认设置
- 默认的全局设置(优先级最低)
这些设置源总体在内部得到了处理,但使用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
文件中的设置。
如何访问设置¶
在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_ENABLED
,ROBOTSTXT_OBEY
,ROBOTSTXT_CACHEDIR
等。
内置设置参考¶
以下按字母顺序列出了所有可用的Scrapy设置,以及它们的默认值和应用范围。
范围(如果可用)显示设置的使用位置,是否与任何特定组件绑定。 在这种情况下,将显示该组件的模块,通常是扩展,中间件或管道。 这也意味着必须启用组件才能使设置发挥作用。
BOT_NAME¶
默认: 'scrapybot'
此Scrapy项目实现的bot的名称(也称为项目名称)。 这将用于默认构建User-Agent,也用于记录。
当您使用startproject
命令创建项目时,它会自动填充项目名称。
CONCURRENT_REQUESTS_PER_IP¶
默认: 0
最大并发(即. 同时)将对任何单个IP执行的请求数。 如果非零,则忽略CONCURRENT_REQUESTS_PER_DOMAIN
设置,并使用此设置。 换言之,并发限制将应用于每个IP,而不是每个域。
此设置还会影响DOWNLOAD_DELAY
和AutoThrottle扩展:如果CONCURRENT_REQUESTS_PER_IP
不为零,则下载延迟会针对每个IP强制执行,而不是每个域强制执行。
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¶
默认: 0
从属: scrapy.spidermiddlewares.depth.DepthMiddleware
允许对任何站点爬取的最大深度。 如果为0,则不会施加任何限制。
DEPTH_PRIORITY¶
默认: 0
Scope: scrapy.spidermiddlewares.depth.DepthMiddleware
一个整数,用于根据请求的深度调整其优先级。
- 默认0, 没有优先调整深度:request.priority = request.priority - ( depth * DEPTH_PRIORITY )
- 正值将降低优先级,即稍后将处理更低的深度请求; 这通常在执行广度优先爬取(BFO)时使用;
- 负值将增加优先级,即更快地处理更高深度的请求(即:执行深度优先爬取--DFO);
See also: Does Scrapy crawl in breadth-first or depth-first order? about tuning Scrapy for BFO or DFO.
Note
与其他优先级设置相比,此设置以相反的方式调整REDIRECT_PRIORITY_ADJUST
和RETRY_PRIORITY_ADJUST
优先级.
DEPTH_STATS_VERBOSE¶
Default: False
Scope: scrapy.spidermiddlewares.depth.DepthMiddleware
是否收集详细的深度统计信息。 如果启用此选项,则在统计信息中收集每个深度的请求数。
DOWNLOADER_HTTPCLIENTFACTORY¶
Default: 'scrapy.core.downloader.webclient.ScrapyHTTPClientFactory'
定义用于HTTP/1.0连接的Twistedprotocol.ClientFactory
类(用于HTTP10DownloadHandler
).
Note
现在很少使用HTTP/1.0,因此您可以安全地忽略此设置,除非您使用Twisted<11.1,或者如果您确实想使用HTTP/1.0并相应地重写http(s)
方案的DOWNLOAD_HANDLERS_BASE
,即'scrapy.core.downloader.handlers.http.HTTP10DownloadHandler'
.
DOWNLOADER_CLIENTCONTEXTFACTORY¶
Default: 'scrapy.core.downloader.contextfactory.ScrapyClientContextFactory'
表示要使用的ContextFactory的类路径。
这里,“ContextFactory”是SSL/TLS上下文的一个Twisted术语,它定义了要使用的TLS/SSL协议版本、是否进行证书验证,甚至启用客户端身份验证(以及其他各种功能)。
Note
Scrapy默认上下文工厂不执行远程服务器证书验证。 这通常很适合于爬网。
如果您确实需要启用远程服务器证书验证,则Scrapy还有另一个上下文工厂类,您可以将其设置为'scrapy.core.downloader.contextfactory.BrowserLikeContextFactory'
,该类使用平台的证书来验证远程终端点。
仅当您使用Twisted>=14.0时,此选项才可用。
如果确实使用自定义ContextFactory,请确保其__init__方法接受method
参数(这是OpenSSL.SSL
方法映射 DOWNLOADER_CLIENT_TLS_METHOD
)。
DOWNLOADER_CLIENT_TLS_METHOD¶
Default: 'TLS'
使用此设置可自定义默认HTTP/1.1下载器使用的TLS/SSL方法。
此设置必须是以下字符串值之一:
'TLS'
: 映射到OpenSSL的TLS_method()
(也称为SSLv23_method()
),它允许协议协商,从平台支持的最高值开始;默认值,推荐'TLSv1.0'
: 此值强制HTTPS连接使用TLS版本1.0; 如果希望Scrapy的行为<1.1,请设置此值'TLSv1.1'
: forces TLS version 1.1'TLSv1.2'
: forces TLS version 1.2'SSLv3'
: forces SSL version 3 (不推荐)
Note
我们建议您使用PyOpenSSL>=0.13 和 Twisted>=0.13 或更高版本(如果可以,Twisted>=14.0).
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
。 有关更多信息,请参阅激活下载中间件。
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地址而不是每个域强制执行。
您还可以通过设置spider属性download_delay
来更改每个Spider的此项设置。
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
下载器在超时之前等待的时间(以秒为单位)。
Note
此超时可以使用spider属性download_timeout
为每个spider设置,也可以使用Request.meta 键的download_timeout
为每个请求设置。
DOWNLOAD_MAXSIZE¶
Default: 1073741824 (1024MB)
下载器将下载的最大响应大小(以字节为单位)。
如果要禁用它,请将其设置为0。
Note
此大小可以使用spider属性download_maxsize
为每个spider设置,也可以使用Request.meta键的download_maxsize
为每个请求设置。
此功能需要Twisted >= 11.1.
DOWNLOAD_WARNSIZE¶
Default: 33554432 (32MB)
下载器将开始警告的响应大小(以字节为单位)。
如果要禁用它,请将其设置为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
对于中断的响应是否失败,即声明的内容长度( Content-Length
)与服务器发送的内容不匹配,或者分块响应未正确完成。 如果为True
, 这些响应将引发ResponseFailed([_DataLoss])
错误。 如果为False
, 则传递这些响应并将 dataloss
标记添加到响应中,即: 'dataloss' 中的 response.flags
为True
.
可选地,这可以通过使用Request.meta键的download_fail_on_dataloss
将每个请求设置为False
。
Note
响应中断或数据丢失错误,从服务器错误配置到网络错误再到数据损坏,在多种情况下都可能发生。 考虑到断开的响应可能包含部分或不完整的内容,应由用户决定处理断开的响应是否有意义。
如果RETRY_ENABLED
为True
,并且此设置也设置为True
,则ResponseFailed([_DataLoss])
会像往常一样重试。
DUPEFILTER_CLASS¶
Default: 'scrapy.dupefilters.RFPDupeFilter'
用于检测和筛选重复请求的类。
默认(RFPDupeFilter
) 使用scrapy.utils.request.request_fingerprint
函数根据请求指纹进行筛选。 要想改变检查重复项的方式,可以将RFPDupeFilter
子类化,并重写其request_fingerprint
方法。 此方法应接受scrapy的Request
对象并返回其指纹(一个字符串)。
通过设置DUPEFILTER_CLASS
为'scrapy.dupefilters.BaseDupeFilter'
,可以禁用重复请求的筛选。
不过,要非常小心,因为你可能会陷入爬网死循环。
对于不应该过滤的特定Request
对象,通常最好将dont_filter
参数设置为True
。
DUPEFILTER_DEBUG¶
Default: False
默认情况下,RFPDupeFilter
只记录第一个重复的请求。
设置DUPEFILTER_DEBUG
为True
将使它记录所有重复的请求。
EDITOR¶
Default: vi
(on Unix systems) or the IDLE editor (on Windows)
用于使用edit
命令来编辑爬虫的编辑器。
此外,如果设置了EDITOR
环境变量,则edit
命令将首选它而不是默认设置。
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¶
Feed Temp目录允许您设置一个自定义文件夹,以便在使用FTP feed storage和Amazon S3上传之前保存爬虫临时文件。
FTP_PASSWORD¶
Default: "guest"
Request
元中没有"ftp_password"
时用于FTP连接的密码。
Note
套用RFC 1635的话来说,尽管匿名FTP通常使用密码“guest”或某人的电子邮件地址,但一些FTP服务器明确要求用户的电子邮件地址,并且不允许使用“guest”密码登录。
ITEM_PIPELINES¶
默认值:{}
包含要使用的Item管道及其顺序的字典。 顺序值是任意的,但习惯上将它们定义在0-1000范围内。 低顺序在高顺序之前处理。
例:
ITEM_PIPELINES = {
'mybot.pipelines.validate.ValidateMyItem': 300,
'mybot.pipelines.validate.StoreMyItem': 800,
}
LOG_FORMAT¶
Default: '%(asctime)s [%(name)s] %(levelname)s: %(message)s'
用于格式化日志消息的字符串。 有关可用占位符的完整列表,请参阅 Python logging documentation
LOG_DATEFORMAT¶
Default: '%Y-%m-%d %H:%M:%S'
格式化日期/时间的字符串,以LOG_FORMAT
扩展%(asctime)s
的占位符。 有关可用占位符的完整列表,请参阅 Python datetime documentation。
LOG_LEVEL¶
Default: 'DEBUG'
要记录日志的最低级别。 可用级别有:CRITICAL, ERROR, WARNING, INFO, DEBUG。 有关详细信息,请参阅Logging.
MEMDEBUG_NOTIFY¶
Default: []
启用内存调试时,如果此设置不为空,则会将内存报告发送到指定的地址,否则会将该报告写入日志。
Example:
MEMDEBUG_NOTIFY = ['[email protected]']
MEMUSAGE_ENABLED¶
Default: True
Scope: scrapy.extensions.memusage
是否启用内存使用扩展。 这个扩展跟踪进程使用的峰值内存(它将其写入stats)。 它还可以选择在超过内存限制时关闭Scrapy进程(请参阅MEMUSAGE_LIMIT_MB
),并在发生此情况时通过电子邮件通知(请参阅 MEMUSAGE_NOTIFY_MAIL
)。
MEMUSAGE_LIMIT_MB¶
Default: 0
Scope: scrapy.extensions.memusage
关闭Scrapy之前允许的最大内存量(以MB为单位)(如果MEMUSAGE_ENABLED为True)。 如果此设置为零,则不执行检查。
MEMUSAGE_CHECK_INTERVAL_SECONDS¶
New in version 1.1.
Default: 60.0
Scope: scrapy.extensions.memusage
Memory usage extension 以固定的时间间隔检查当前内存使用情况,与MEMUSAGE_LIMIT_MB
和MEMUSAGE_WARNING_MB
设置的限制进行比较。
这将设置这些间隔的长度,以秒为单位。
MEMUSAGE_NOTIFY_MAIL¶
Default: False
Scope: scrapy.extensions.memusage
要通知是否已达到内存限制的电子邮件列表。
Example:
MEMUSAGE_NOTIFY_MAIL = ['[email protected]']
MEMUSAGE_WARNING_MB¶
Default: 0
Scope: scrapy.extensions.memusage
在发送警告电子邮件通知之前允许的最大内存量(以MB为单位)。 如果为零,则不会产生警告。
NEWSPIDER_MODULE¶
Default: ''
使用genspider
命令创建新爬虫的模块。
Example:
NEWSPIDER_MODULE = 'mybot.spiders_dev'
RANDOMIZE_DOWNLOAD_DELAY¶
Default: True
如果启用,Scrapy将在从同一网站获取请求时随机等待一段时间(介于0.5 * DOWNLOAD_DELAY
和1.5 * DOWNLOAD_DELAY
之间)。
这种随机化减少了爬虫程序被分析请求的站点检测到(并随后被阻止)的机会,这些站点在请求之间寻找统计上显著的相似性。
随机化策略与wget --random-wait
选项使用的策略相同。
如果DOWNLOAD_DELAY
(默认),则此选项无效。
REACTOR_THREADPOOL_MAXSIZE¶
Default: 10
Twisted Reactor线程池大小的最大限制。 这是各种Scrapy组件使用的通用多用途线程池。 线程DNS解析器,BlockingFeedStorage,S3FilesStore,等等。 如果遇到阻塞IO不足的问题,请增加此值。
REDIRECT_PRIORITY_ADJUST¶
Default: +2
Scope: scrapy.downloadermiddlewares.redirect.RedirectMiddleware
调整相对于原始请求的重定向请求优先级:
- 正优先级调整(默认)意味着更高的优先级。
- 负优先级调整意味着低优先级。
RETRY_PRIORITY_ADJUST¶
Default: -1
Scope: scrapy.downloadermiddlewares.retry.RetryMiddleware
调整相对于原始请求的重试请求优先级:
- a positive priority adjust means higher priority.
- a negative priority adjust (default) means lower priority.
ROBOTSTXT_OBEY¶
Default: False
Scope: scrapy.downloadermiddlewares.robotstxt
如果启用,Scrapy将遵守robots.txt策略。 有关详细信息,请参阅 RobotsTxtMiddleware.
Note
由于历史原因,默认值为False
,但此选项在由scrapy startproject
命令生成的settings.py文件中默认启用。
SCHEDULER_DEBUG¶
Default: False
设置为True
将记录有关请求计划程序的调试信息。
如果无法将请求序列化到磁盘,则此操作当前只记录一次。
统计计数器(Stats counter)(scheduler/unserializable
)跟踪发生这种情况的次数。
日志中的示例条目:
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'
调度程序将使用的磁盘队列的类型。 其他可用类型是scrapy.squeues.PickleFifoDiskQueue
, scrapy.squeues.MarshalFifoDiskQueue
, scrapy.squeues.MarshalLifoDiskQueue
.
SCHEDULER_MEMORY_QUEUE¶
Default: 'scrapy.squeues.LifoMemoryQueue'
调度程序使用的内存中队列的类型。 其他可用类型是:scrapy.squeues.FifoMemoryQueue
.
SPIDER_CONTRACTS_BASE¶
Default:
{
'scrapy.contracts.default.UrlContract' : 1,
'scrapy.contracts.default.ReturnsContract': 2,
'scrapy.contracts.default.ScrapesContract': 3,
}
包含在Scrapy中默认启用的Scrapy协定的dict。 您不应该在项目中修改此设置,而应修改 SPIDER_CONTRACTS
。 有关详细信息,请参阅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_WARN_ONLY¶
New in version 1.3.3.
Default: False
默认情况下,当Scrapy尝试从SPIDER_MODULES
中导入spider类时,如果出现任何ImportError
异常,它将大大地失败。
但是,您可以选择通过设置SPIDER_LOADER_WARN_ONLY = True
来消除此异常,并将其转换为一个简单的警告。
Note
有些 scrapy命令 运行时已经将此设置为True
(即,它们只发出警告并不会失败),因为它们实际上不需要加载spider类就可以工作: scrapy runspider
, scrapy settings
, scrapy startproject
, scrapy version
。
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'
用于收集统计信息的类,该类必须实现Stats Collector API。
TELNETCONSOLE_PORT¶
Default: [6023, 6073]
用于telnet控制台的端口范围。 如果设置为 None
或0
,则使用动态分配的端口。 有关详细信息,请参阅Telnet Console.
TEMPLATES_DIR¶
Default: templates
dir inside scrapy module
使用命令startproject
(创建新项目)和使用命令genspider
(创建新爬虫)时查找模板的目录。
项目名称不得与project
子目录中自定义文件或目录的名称冲突。
URLLENGTH_LIMIT¶
Default: 2083
Scope: spidermiddlewares.urllength
允许爬取URL的最大URL长度。 有关此设置的默认值的详细信息,请参见:http://boutell.com/newfaq/misc/urllength.html。
其他地方记录的设置:¶
以下是其他地方记录的设置,请检查每个特定情况以了解如何启用和使用它们。
- AJAXCRAWL_ENABLED
- AUTOTHROTTLE_DEBUG
- AUTOTHROTTLE_ENABLED
- AUTOTHROTTLE_MAX_DELAY
- AUTOTHROTTLE_START_DELAY
- AUTOTHROTTLE_TARGET_CONCURRENCY
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- BOT_NAME
- CLOSESPIDER_ERRORCOUNT
- CLOSESPIDER_ITEMCOUNT
- CLOSESPIDER_PAGECOUNT
- CLOSESPIDER_TIMEOUT
- COMMANDS_MODULE
- COMPRESSION_ENABLED
- CONCURRENT_ITEMS
- CONCURRENT_REQUESTS
- CONCURRENT_REQUESTS_PER_DOMAIN
- CONCURRENT_REQUESTS_PER_IP
- COOKIES_DEBUG
- COOKIES_ENABLED
- DEFAULT_ITEM_CLASS
- DEFAULT_REQUEST_HEADERS
- DEPTH_LIMIT
- DEPTH_PRIORITY
- DEPTH_STATS
- DEPTH_STATS_VERBOSE
- DNSCACHE_ENABLED
- DNSCACHE_SIZE
- DNS_TIMEOUT
- DOWNLOADER
- DOWNLOADER_CLIENTCONTEXTFACTORY
- DOWNLOADER_CLIENT_TLS_METHOD
- DOWNLOADER_HTTPCLIENTFACTORY
- DOWNLOADER_MIDDLEWARES
- DOWNLOADER_MIDDLEWARES_BASE
- DOWNLOADER_STATS
- DOWNLOAD_DELAY
- DOWNLOAD_FAIL_ON_DATALOSS
- DOWNLOAD_HANDLERS
- DOWNLOAD_HANDLERS_BASE
- DOWNLOAD_MAXSIZE
- DOWNLOAD_TIMEOUT
- DOWNLOAD_WARNSIZE
- DUPEFILTER_CLASS
- DUPEFILTER_DEBUG
- EDITOR
- EXTENSIONS
- EXTENSIONS_BASE
- FEED_EXPORTERS
- FEED_EXPORTERS_BASE
- FEED_EXPORT_ENCODING
- FEED_EXPORT_FIELDS
- FEED_EXPORT_INDENT
- FEED_FORMAT
- FEED_STORAGES
- FEED_STORAGES_BASE
- FEED_STORE_EMPTY
- FEED_TEMPDIR
- FEED_URI
- FILES_EXPIRES
- FILES_RESULT_FIELD
- FILES_STORE
- FILES_STORE_S3_ACL
- FILES_URLS_FIELD
- FTP_PASSIVE_MODE
- FTP_PASSWORD
- FTP_USER
- GCS_PROJECT_ID
- HTTPCACHE_ALWAYS_STORE
- HTTPCACHE_DBM_MODULE
- HTTPCACHE_DIR
- HTTPCACHE_ENABLED
- HTTPCACHE_EXPIRATION_SECS
- HTTPCACHE_GZIP
- HTTPCACHE_IGNORE_HTTP_CODES
- HTTPCACHE_IGNORE_MISSING
- HTTPCACHE_IGNORE_RESPONSE_CACHE_CONTROLS
- HTTPCACHE_IGNORE_SCHEMES
- HTTPCACHE_POLICY
- HTTPCACHE_STORAGE
- HTTPERROR_ALLOWED_CODES
- HTTPERROR_ALLOW_ALL
- HTTPPROXY_AUTH_ENCODING
- HTTPPROXY_ENABLED
- IMAGES_EXPIRES
- IMAGES_MIN_HEIGHT
- IMAGES_MIN_WIDTH
- IMAGES_RESULT_FIELD
- IMAGES_STORE
- IMAGES_STORE_S3_ACL
- IMAGES_THUMBS
- IMAGES_URLS_FIELD
- ITEM_PIPELINES
- ITEM_PIPELINES_BASE
- LOG_DATEFORMAT
- LOG_ENABLED
- LOG_ENCODING
- LOG_FILE
- LOG_FORMAT
- LOG_LEVEL
- LOG_SHORT_NAMES
- LOG_STDOUT
- MAIL_FROM
- MAIL_HOST
- MAIL_PASS
- MAIL_PORT
- MAIL_SSL
- MAIL_TLS
- MAIL_USER
- MEDIA_ALLOW_REDIRECTS
- MEMDEBUG_ENABLED
- MEMDEBUG_NOTIFY
- MEMUSAGE_CHECK_INTERVAL_SECONDS
- MEMUSAGE_ENABLED
- MEMUSAGE_LIMIT_MB
- MEMUSAGE_NOTIFY_MAIL
- MEMUSAGE_WARNING_MB
- METAREFRESH_ENABLED
- METAREFRESH_MAXDELAY
- NEWSPIDER_MODULE
- RANDOMIZE_DOWNLOAD_DELAY
- REACTOR_THREADPOOL_MAXSIZE
- REDIRECT_ENABLED
- REDIRECT_MAX_TIMES
- REDIRECT_MAX_TIMES
- REDIRECT_PRIORITY_ADJUST
- REFERER_ENABLED
- REFERRER_POLICY
- RETRY_ENABLED
- RETRY_HTTP_CODES
- RETRY_PRIORITY_ADJUST
- RETRY_TIMES
- ROBOTSTXT_OBEY
- SCHEDULER
- SCHEDULER_DEBUG
- SCHEDULER_DISK_QUEUE
- SCHEDULER_MEMORY_QUEUE
- SCHEDULER_PRIORITY_QUEUE
- SPIDER_CONTRACTS
- SPIDER_CONTRACTS_BASE
- SPIDER_LOADER_CLASS
- SPIDER_LOADER_WARN_ONLY
- SPIDER_MIDDLEWARES
- SPIDER_MIDDLEWARES_BASE
- SPIDER_MODULES
- STATSMAILER_RCPTS
- STATS_CLASS
- STATS_DUMP
- TELNETCONSOLE_ENABLED
- TELNETCONSOLE_HOST
- TELNETCONSOLE_PORT
- TELNETCONSOLE_PORT
- TEMPLATES_DIR
- URLLENGTH_LIMIT
- USER_AGENT
异常处理¶
内置异常处理参考¶
以下列出了Scrapy中包含的所有异常及其使用情况。
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被关闭。
- 命令行工具
- 了解用于管理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个等级:
logging.CRITICAL
- 严重错误 (最高严重程度)logging.ERROR
- 普通错误logging.WARNING
- 警告信息logging.INFO
- 普通信息logging.DEBUG
- 调试信息 (最低严重程度)
如何记录日志¶
以下是记录日志的一个简单例子,使用了logging.WARNING
等级:
import logging
logging.warning("This is a warning")
在任何标准的5个级别上都有发出日志消息的快捷方式,还有一个通用的logging.log
方法,它以给定的级别作为参数。 如果需要,上一个示例可以重写为:
import logging
logging.log(logging.WARNING, "This is a warning")
除此之外,您还可以创建不同的“记录器”来封装消息。 (例如,一种常见的做法是为每个模块创建不同的记录器)。 这些记录器可以独立配置,并且允许层次结构。
前面的示例在后台使用根记录器,这是一个顶级记录器,所有消息都将传播到该记录器(除非另有指定)。 使用logging
助手只是显式获取根日志记录器的快捷方式,因此这也相当于最后一个片段:
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")
最后,您可以使用当前模块路径填充的__name__
变量,确保为正在处理的任何模块提供自定义记录器:
import logging
logger = logging.getLogger(__name__)
logger.warning("This is a warning")
Logging from Spiders¶
Scrapy在每个Spider实例中提供了一个logger
,可以这样访问和使用:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
start_urls = ['http://scrapinghub.com']
def parse(self, response):
self.logger.info('Parse function called on %s', response.url)
该记录器是使用Spider的name创建的,但是您可以使用任何想要的自定义Python记录器。 For example:
import logging
import scrapy
logger = logging.getLogger('mycustomlogger')
class MySpider(scrapy.Spider):
name = 'myspider'
start_urls = ['http://scrapinghub.com']
def parse(self, response):
logger.info('Parse function called on %s', response.url)
Logging configuration¶
记录器自己无法管理通过它们发送的消息的显示方式。 对于此任务,可以将不同的“处理程序”附加到任何记录器实例,它们会将这些消息重定向到适当的目标,例如标准输出、文件、电子邮件等。
默认情况下,Scrapy根据以下设置,设置并配置根记录器的处理程序。
Logging settings¶
这些设置可用于配置日志记录:
前两个设置定义日志消息的目标。 如果设置了LOG_FILE
,则通过根日志记录器发送的消息将重定向到名为LOG_FILE
、使用编码LOG_ENCODING
的文件。 如果没有设置并且LOG_ENABLED
为True
,则在标准错误(standard error)上显示日志消息。 最后,如果LOG_ENABLED
为False
,则不会有任何可见的日志输出。
LOG_LEVEL
确定要显示的最低严重性级别,严重性较低的消息将被过滤掉。 它涵盖了Log levels中列出的可能级别。
LOG_FORMAT
和LOG_DATEFORMAT
指定用作所有消息布局的格式字符串。 这些字符串可以包含由logging’s logrecord attributes docs和datetime’s strftime and strptime directives 列出的任何占位符。
如果设置了LOG_SHORT_NAMES
,打印日志时将不会显示日志的Scrapy组件。 默认情况下是未设置的,因此日志包含负责该日志输出的Scrapy组件。
Command-line options¶
有一些命令行参数可用于所有命令,您可以使用这些参数来覆盖Scrapy中有关于日志记录的设置。
--logfile FILE
- Overrides
LOG_FILE
--loglevel/-L LEVEL
- Overrides
LOG_LEVEL
--nolog
- Sets
LOG_ENABLED
toFalse
See also
- Module logging.handlers
- 可用处理程序的进一步文档
高级定制¶
因为Scrapy使用stdlib日志模块,您可以使用所有的stdlib日志功能来自定义日志。
例如,假设您正在抓取一个返回许多HTTP 404和500响应的网站,并且您希望隐藏以下所有消息:
2016-12-16 22:00:06 [scrapy.spidermiddlewares.httperror] INFO: Ignoring
response <500 http://quotes.toscrape.<