Selectors
在抓取一个web页面的时候,大多数任务在于从HTML源中提取数据。有很多可用的的库支持这些操作,比如:
- BeautifulSoup:这是一个在 Python 编程中非常流行的网页抓取库,它依照HTML代码的结构来构造 Python 对象,并且能正确处理不良标记,但是它有一个弱项:太慢
- lxml:以非常 python 化的 ElementTree 接口为基础,建立 XML 解析库(同时也能解析 HTML)
Scrapy 有自己的提取数据的机制。它们称之为 selectors(选择器),因为从 HTML 文档中筛选特定内容,可以使用XPath
或CSS
表达式。
XPath
是一个筛选 XML 文档节点的语言,也能用于筛选 HTML 文档。
CSS
是一个应用格式到 HTML 文档的语言,它定义选择器与特定 HTML 元素格式之间的关系。
注意:
Scrapy 选择器是 `parsel` 库的轻量级再封装;封装的目的是为了与 Scrapy 响应对象提供更好的整合。
`parsel` 是一个独立的网页抓取库,可以不依赖于 Scrapy。它使用 `lxml` 库作为底层引擎,并且在 lxml 顶层接口再实现了一个简单API。这意味着 Scrapy 选择器的速度与解析准确性与 lxml 十分接近。
一、使用选择器
以 scrapy shell https://docs.scrapy.org/en/latest/_static/selectors-sample1.html
为例
1.1 构建选择器
使用 .selector
暴露 Response 对象的 Selector
实例:
In [4]: response.selector.xpath('//a/text()').getall()
Out[4]:
['Name: My image 1 ',
'Name: My image 2 ',
'Name: My image 3 ',
'Name: My image 4 ',
'Name: My image 5 ']
更简洁的方式查询,Scrapy 提供了两种简写:response.xpath()
和 response.css()
In [5]: response.css('a::text').getall()
In [6]: response.xpath('//a/text()').getall()
Scrapy selectors 是 Selector
类的实例,通过传输 TextResponse
对象或作为 unicode 的补全来构成。
通常不需要手动构造 Scrapy selectors,原因如下:response
对象可以用于 Spider 回调,所以大部分场景下会偏向于使用 response.css()
和 response.xpath()
作为简写。通过使用 response.selector
或 response.xpath() response.css()
可以确保响应体只被解析一次。
todo:这里的 Selector 类 和 Spider 与 response 之间的回调 问题,以后在研究源码的时候着重看看怎么处理的
但是在必要条件下,需要直接使用 Selector
,比如要从如下的内容中构造对象:
>>> from scrapy.selector import Selector
>>> body = 'good'
>>> Selector(text=body).xpath('//span/text()').get()
'good'
Selector
会自动基于输入类型选择最好的解析规则(XML 或 HTML)。
1.2 选择器
接下来先介绍如何使用Scrapy shell
(它提供了可交互的测试)
使用方法scrapy shell https://docs.scrapy.org/en/latest/_static/selectors-sample1.html
在 shell 加载完成后,可以通过 response
直接获取响应,通过 response.selector
属性获取选择器
由于该网页返回的是 HTML 内容,所以选择器会自动使用 HTML 解析模式。
为title标签内的文本构造 XPath 访问方式
In [7]: response.xpath('//title/text()')
Out[7]: []
可以看到返回的是一个选择器对象列表,如果想要提取文本内容,需要使用选择器的 .get()
或者 .getall()
方法,如下所示:
In [8]: response.xpath('//title/text()').get()
Out[8]: 'Example website'
.get()
永远只返回一个结果;
如果选择器有多个匹配,那么只返回匹配的第一个内容;
如果只有没有匹配,将返回 None
.getall()
返回一个结果列表
注意 CSS 选择器可以使用 CSS3 的伪元素(pseudoelements)选择文本内容和属性节点。
In [9]: response.css('title::text')
Out[9]: []
In [10]: response.css('title::text').get()
Out[10]: 'Example website'
可以得知,.xpath() 和 .css() 方法都会返回一个 SelectorList 实例对象,该实例对象是选择器的列表。
这个接口还可以用于快速选择嵌套的数据:
In [11]: response.css('img').xpath('@src').getall()
Out[11]:
['image1_thumb.jpg',
'image2_thumb.jpg',
'image3_thumb.jpg',
'image4_thumb.jpg',
'image5_thumb.jpg']
自定义返回结果
如果在获取元素时,没有发现对应的元素,那么将返回 None;但是也可以自定义返回结果:
In [12]: response.css('ab_post').get('no_element')
Out[12]: 'no_element'
css 获取属性值 .attrib
在上面的例子中,获取 src 属性使用了 ‘@src’ XPath,在 CSS 中通过 .attrib 也可以查询该属性。
In [13]: [img.attrib['src'] for img in response.css('img')]
Out[13]:
['image1_thumb.jpg',
'image2_thumb.jpg',
'image3_thumb.jpg',
'image4_thumb.jpg',
'image5_thumb.jpg']
.attrib 作为简写同样也可以直接获取 SelectorList, 它返回第一个被匹配的元素:
In [14]: response.css('img').attrib['src']
Out[14]: 'image1_thumb.jpg'
这种做法在只需要一个结果时非常有用,比如当根据id或页面上唯一的元素选择时:
In [18]: response.css('base').attrib['href']
Out[18]: 'http://example.com/'
综合用法
获取 base 标签的 href 属性内容
In [18]: response.css('base').attrib['href']
Out[18]: 'http://example.com/'
In [19]: response.xpath('//base/@href').get()
Out[19]: 'http://example.com/'
In [20]: response.css('base::attr(href)').get()
Out[20]: 'http://example.com/'
In [21]: response.css('base').attrib['href']
Out[21]: 'http://example.com/'
获取标签的 href 属性内容(限定内容包含 “image”)
In [24]: response.xpath('//a[contains(@href, "image")]/@href').getall()
Out[24]: ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
In [25]: response.css('a[href*=image]::attr(href)').getall()
Out[25]: ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
获取标签 href 内容包含“image”的子标签 src 属性值
In [26]: response.xpath('//a[contains(@href, "image")]/img/@src').getall()
Out[26]:
['image1_thumb.jpg',
'image2_thumb.jpg',
'image3_thumb.jpg',
'image4_thumb.jpg',
'image5_thumb.jpg']
In [27]: response.css('a[href*=image] img::attr(src)').getall()
Out[27]:
['image1_thumb.jpg',
'image2_thumb.jpg',
'image3_thumb.jpg',
'image4_thumb.jpg',
'image5_thumb.jpg']
1.3 扩展CSS选择器
根据 W3C 标准,CSS 选择器不能提供选择文本节点或属性值的功能。
但是在网页中抓取内容是非常必要的,Scrapy 为此实现了许多非标准的伪元素。
- 选择文本节点,使用 ::text
- 选择属性值,使用 ::attr(name) 这里的 name 属性对应的名称
实例
- title::text 筛选
元素的子节点文本 - *::text 筛选当前选择器上下文的所有子文本节点
- foo::text 如果 foo 元素存在但是不包含文本,那么返回结果为空。
- 这意味着 .css('foo::text').get() 即使元素存在也返回 None,如果需要修改返回值,可以添加 *.get(default='')
- a::attr(href) 筛选 href 属性的值
1.4 嵌套选择器
筛选模式( .xapth() 和 .css() )都返回同一类型选择器的列表,所以你可以对这些筛选列表进行调用,下面是例子:
In [28]: links = response.xpath('//a[contains(@href, "image")]')
In [29]: links.getall()
Out[29]:
['Name: My image 1
',
'Name: My image 2
',
'Name: My image 3
',
'Name: My image 4
',
'Name: My image 5
']
In [30]: for index, link in enumerate(links):
...: args = (index, link.xpath('@href').get(), link.xpath('img/@src').get())
...: print('Link numver %d points to url %r and image %r' % args)
...:
Link numver 0 points to url 'image1.html' and image 'image1_thumb.jpg'
Link numver 1 points to url 'image2.html' and image 'image2_thumb.jpg'
Link numver 2 points to url 'image3.html' and image 'image3_thumb.jpg'
Link numver 3 points to url 'image4.html' and image 'image4_thumb.jpg'
Link numver 4 points to url 'image5.html' and image 'image5_thumb.jpg'
1.5 选择元素属性
有多种方式获取属性的值。
XPath 选择属性
首先,可以使用 XPath 语法:
In [31]: response.xpath('//a/@href').getall()
Out[31]: ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
XPath 语法有多种优势:在标准的 XPath 格式中,@attributes 可以作为 XPath 表达式的一部分,等等,可以通过属性值来筛选内容。
CSS 选择属性
Scrapy 也提供了 CSS 选择器的扩展(::attr(...)),它允许这样获得属性值:
In [32]: response.css('a::attr(href)').getall()
Out[32]: ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
Selector attrib 属性
除了这种做法,还有 Selector 的 .attrib 属性。
如果你更希望使用 Python 代码,而不是通过 XPath 或者 CSS 扩展来获取属性,那么这种方式也比较有用:
In [33]: [a.attrib['href'] for a in response.css('a')]
Out[33]: ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
这种 python 代码直接读取的方式也可作用于 SelectorList 上;
将会将返回字典,包含第一个匹配元素的属性和值。
如果只需要选择器返回一个结果,那么这么做很方便:
In [34]: response.css('base').attrib
Out[34]: {'href': 'http://example.com/'}
In [35]: response.css('base').attrib['href']
Out[35]: 'http://example.com/'
1.6 在选择器中使用正则表达式
Selector 类还有 .re() 方法用于提取正则匹配的数据。
然而,不同于 .xpath() 和 .css() 方法,.re() 返回的是一个字符串列表。
所以 .re() 不能做内嵌的 .re() 调用。
提取图片名字的例子
In [36]: response.xpath('//a[contains(@href, "image")]/text()').re(r'Name:\s*(.*)')
Out[36]: ['My image 1 ', 'My image 2 ', 'My image 3 ', 'My image 4 ', 'My image 5 ']
In [37]: response.xpath('//a[contains(@href, "image")]/text()').re_first(r'Name:\s*(.*)')
Out[37]: 'My image 1 '
二、XPaths 的使用
在 Scrapy 中高效使用 XPath 的一些建议
扩展链接: http://www.zvon.org/comp/r/tut-XPath_1.html
扩展链接2:https://blog.scrapinghub.com/2014/07/17/xpath-tips-from-the-web-scraping-trenches/
2.1 使用 XPath 相对路径
假设你在嵌套选择器中使用 XPath,如果路径以 '/' 开头,那么 Xpath 会指向文档的绝对位置,而不是你现在调用的选择器。
比如,如果想提取