选择器(Selectors)
当抓取网页时,你做的最常见的任务是从HTML源码中提取数据。现有的一些库可以达到这个目的:
- BeautifulSoup 是在程序员间非常流行的网页分析库,它基于HTML代码的结构来构造一个Python对象, 对不良标记的处理也非常合理,但它有一个缺点:慢。
- lxml 是一个基于 ElementTree (不是Python标准库的一部分)的python化的XML解析库(也可以解析HTML)。
Scrapy提取数据有自己的一套机制。它们被称作选择器(seletors),因为他们通过特定的 XPath 或者 CSS 表达式来“选择” HTML文件中的某个部分。
XPath 是一门用来在XML文件中选择节点的语言,也可以用在HTML上。 CSS 是一门将HTML文档样式化的语言。选择器由它定义,并与特定的HTML元素的样式相关连。
Scrapy选择器构建于 lxml 库之上,这意味着它们在速度和解析准确性上非常相似。
本页面解释了选择器如何工作,并描述了相应的API。不同于 lxml API的臃肿,该API短小而简洁。这是因为 lxml 库除了用来选择标记化文档外,还可以用到许多任务上。
选择器API的完全参考详见 Selector reference
使用选择器(selectors)
构造选择器(selectors)
Scrapy selector是以 文字(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构造:
>>> response = HtmlResponse(url='http://example.com', body=body) >>> Selector(response=response).xpath('//span/text()').extract() [u'good']
为了方便起见,response对象以 .selector 属性提供了一个selector, 您可以随时使用该快捷方法:
>>> response.selector.xpath('//span/text()').extract() [u'good']
使用选择器(selectors)
我们将使用 Scrapy shell (提供交互测试)和位于Scrapy文档服务器的一个样例页面,来解释如何使用选择器:
http://doc.scrapy.org/en/latest/_static/selectors-sample1.html
这里是它的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, 并且在其 response.selector
属性上绑定了一个selector。
因为我们处理的是HTML,选择器将自动使用HTML语法分析。
那么,通过查看 HTML code 该页面的源码,我们构建一个XPath来选择title标签内的文字:
>>> response.selector.xpath('//title/text()') [<Selector (text) xpath=//title/text()>]
由于在response中使用XPath、CSS查询十分普遍,因此,Scrapy提供了两个实用的快捷方式: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可以用来快速的提取嵌套数据。
为了提取真实的原文数据,你需要调用 .extract()
方法如下:
>>> response.xpath('//title/text()').extract() [u'Example website']
注意CSS选择器可以使用CSS3伪元素(pseudo-elements)来选择文字或者属性节点:
>>> response.css('title::text').extract() [u'Example website']
现在我们将得到根URL(base 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']
嵌套选择器(selectors)
选择器方法( .xpath()
or .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']
结合正则表达式使用选择器(selectors)
Selector
也有一个 .re()
方法,用来通过正则表达式来提取数据。然而,不同于使用 .xpath()
或者 .css()
方法, .re()
方法返回unicode字符串的列表。所以你无法构造嵌套式的 .re()
调用。
下面是一个例子,从上面的 HTML code 中提取图像名字:
>>> 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']
使用相对XPaths
记住如果你使用嵌套的选择器,并使用起始为 /
的XPath,那么该XPath将对文档使用绝对路径,而且对于你调用的 Selector
不是相对路径。
比如,假设你想提取在 <div>
元素中的所有 <p>
元素。首先,你将先得到所有的 <div>
元素:
>>> divs = response.xpath('//div')
开始时,你可能会尝试使用下面的错误的方法,因为它其实是从整篇文档中,而不仅仅是从那些 <div>
元素内部提取所有的 <p>
元素:
>>> for p in divs.xpath('//p'): # this is wrong - gets all <p> from the whole document ... print p.extract()
下面是比较合适的处理方法(注意 .//p
XPath的点前缀):
>>> for p in divs.xpath('.//p'): # extracts all <p> inside ... print p.extract()
另一种常见的情况将是提取所有直系 <p>
的结果:
>>> for p in divs.xpath('p'): ... print p.extract()
更多关于相对XPaths的细节详见XPath说明中的 Location Paths 部分。
使用EXSLT扩展
因建于 lxml 之上, Scrapy选择器也支持一些 EXSLT 扩展,可以在XPath表达式中使用这些预先制定的命名空间:
前缀 | 命名空间 | 用途 |
---|---|---|
re | http://exslt.org/regular-expressions | 正则表达式 |
set | http://exslt.org/sets | 集合操作 |
正则表达式
例如在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'] >>>