Selectors

抓取网页时,最需要执行的任务是从HTML源中提取数据. 有几种库可以实现此目的,例如:

  • BeautifulSoup是Python程序员中非常流行的Web抓取库,它基于HTML代码的结构构造一个Python对象,并且还可以很好地处理不良标记,但是它有一个缺点:速度慢.
  • lxml是具有基于ElementTree的pythonic API的XML解析库(也可以解析HTML). (lxml不是Python标准库的一部分.)

Scrapy带有自己的数据提取机制. 之所以称为选择器,是因为它们"选择"了XPathCSS表达式指定的HTML文档的某些部分.

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

Note

Scrapy Selectors是Parsel库的薄包装. 包装器的目的是提供与Scrapy Response对象的更好的集成.

parsel is a stand-alone web scraping library which can be used without Scrapy. It uses lxml library under the hood, and implements an easy API on top of lxml API. It means Scrapy selectors are very similar in speed and parsing accuracy to lxml.

Using selectors

Constructing selectors

响应对象在.selector属性上公开一个Selector实例:

>>> response.selector.xpath('//span/text()').get()
'good'

使用XPath和CSS查询响应非常普遍,以至于响应还包括两个快捷键: response.xpath()response.css()

>>> response.xpath('//span/text()').get()
'good'
>>> response.css('span::text').get()
'good'

Scrapy选择器是通过将TextResponse对象或标记作为unicode字符串(在text参数中)传递而构造的Selector类的实例. 通常,不需要手动构造Scrapy选择器:Spider回调中提供了response对象,因此在大多数情况下,使用response.css()response.xpath()快捷方式更为方便. 通过使用response.selector或这些快捷方式之一,您还可以确保响应正文仅被解析一次.

但是,如果需要,可以直接使用Selector . 从文本构造:

>>> from scrapy.selector import Selector
>>> body = '<html><body><span>good</span></body></html>'
>>> Selector(text=body).xpath('//span/text()').get()
'good'

从响应构造HtmlResponseTextResponse子类之一:

>>> from scrapy.selector import Selector
>>> from scrapy.http import HtmlResponse
>>> response = HtmlResponse(url='http://example.com', body=body)
>>> Selector(response=response).xpath('//span/text()').get()
'good'

Selector根据输入类型自动选择最佳的解析规则(XML与HTML).

Using selectors

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

为了完整起见,以下是其完整的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>

首先,让我们打开外壳:

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

然后,在加载外壳程序之后,您可以将响应用作response外壳程序变量,并将其附加的选择器添加到response.selector属性中.

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

因此,通过查看该页面的HTML代码 ,让我们构造一个XPath来选择title标记内的文本:

>>> response.xpath('//title/text()')
[<Selector xpath='//title/text()' data='Example website'>]

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

>>> response.xpath('//title/text()').getall()
['Example website']
>>> response.xpath('//title/text()').get()
'Example website'

.get()总是返回单个结果; 如果有多个匹配项,则返回第一个匹配项的内容; 如果没有匹配项,则返回None. .getall()返回带有所有结果的列表.

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

>>> response.css('title::text').get()
'Example website'

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

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

如果只想提取第一个匹配的元素,则可以调用选择器.get() (或其别名在以前的Scrapy版本中常用的别名.extract_first() ):

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

如果未找到任何元素,则返回None

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

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

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

而不是使用的例如'@src'的XPath就可以查询使用属性.attrib一个物业Selector

>>> [img.attrib['src'] for img in response.css('img')]
['image1_thumb.jpg',
 'image2_thumb.jpg',
 'image3_thumb.jpg',
 'image4_thumb.jpg',
 'image5_thumb.jpg']

作为一种快捷方式, .attrib也可以直接在SelectorList上使用. 它返回第一个匹配元素的属性:

>>> response.css('img').attrib['src']
'image1_thumb.jpg'

当仅期望单个结果时(例如,按ID选择或在网页上选择唯一元素时),这是最有用的:

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

现在,我们将获得基本URL和一些图像链接:

>>> response.xpath('//base/@href').get()
'http://example.com/'

>>> response.css('base::attr(href)').get()
'http://example.com/'

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

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

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

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

>>> response.css('a[href*=image] img::attr(src)').getall()
['image1_thumb.jpg',
 'image2_thumb.jpg',
 'image3_thumb.jpg',
 'image4_thumb.jpg',
 'image5_thumb.jpg']

Extensions to CSS Selectors

根据W3C标准, CSS选择器不支持选择文本节点或属性值. 但是在Web抓取上下文中选择这些元素非常重要,以至于Scrapy(parsel)实现了两个非标准的伪元素

  • to select text nodes, use ::text
  • 要选择属性值,请使用::attr(name) ,其中name是您想要其值的属性的名称

Warning

这些伪元素是Scrapy / Parsel特定的. 它们很可能无法与lxmlPyQuery之类的其他库一起使用 .

Examples:

  • title::text选择后代<title>元素的子级文本节点:

    >>> response.css('title::text').get()
    'Example website'
    
  • *::text选择当前选择器上下文的所有后代文本节点:

    >>> response.css('#images *::text').getall()
    ['\n   ',
     'Name: My image 1 ',
     '\n   ',
     'Name: My image 2 ',
     '\n   ',
     'Name: My image 3 ',
     '\n   ',
     'Name: My image 4 ',
     '\n   ',
     'Name: My image 5 ',
     '\n  ']
    
  • 如果foo元素存在,但foo::text返回任何结果,但不包含任何文本(即text为空):

    >>> response.css('img::text').getall()
    []
    

    这意味着.css('foo::text').get()可能返回None,即使元素存在. 如果您始终需要一个字符串,请使用default=''

    >>> response.css('img::text').get()
    >>> response.css('img::text').get(default='')
    ''
    
  • a::attr(href)选择后代链接的href属性值:

    >>> response.css('a::attr(href)').getall()
    ['image1.html',
     'image2.html',
     'image3.html',
     'image4.html',
     'image5.html']
    

Note

另请参阅: 选择元素属性 .

Note

You cannot chain these pseudo-elements. But in practice it would not make much sense: text nodes do not have attributes, and attribute values are string values already and do not have children nodes.

Nesting selectors

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

>>> links = response.xpath('//a[contains(@href, "image")]')
>>> links.getall()
['<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>']

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

Link number 0 points to url 'image1.html' and image 'image1_thumb.jpg'
Link number 1 points to url 'image2.html' and image 'image2_thumb.jpg'
Link number 2 points to url 'image3.html' and image 'image3_thumb.jpg'
Link number 3 points to url 'image4.html' and image 'image4_thumb.jpg'
Link number 4 points to url 'image5.html' and image 'image5_thumb.jpg'

Selecting element attributes

有几种获取属性值的方法. 首先,可以使用XPath语法:

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

XPath语法具有一些优点:这是标准的XPath功能,并且@attributes可以在XPath表达式的其他部分使用-例如,可以按属性值进行过滤.

Scrapy还提供了CSS选择器( ::attr(...) )的扩展,它允许获取属性值:

>>> response.css('a::attr(href)').getall()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']

除此之外,还有Selector的.attrib属性. 如果您希望在Python代码中查找属性,而无需使用XPaths或CSS扩展,则可以使用它:

>>> [a.attrib['href'] for a in response.css('a')]
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']

该属性也可以在SelectorList上使用. 它返回具有第一个匹配元素的属性的字典. 当期望选择器给出单个结果时(例如,按元素ID选择或在页面上选择唯一元素时),使用起来很方便:

>>> response.css('base').attrib
{'href': 'http://example.com/'}
>>> response.css('base').attrib['href']
'http://example.com/'

空的SelectorList的.attrib属性为空:

>>> response.css('foo').attrib
{}

Using selectors with regular expressions

Selector还具有.re()方法,用于使用正则表达式提取数据. 但是,与使用.xpath().css()方法不同, .re()返回unicode字符串的列表. 因此,您无法构造嵌套的.re()调用.

这是一个用于从上面的HTML代码提取图像名称的示例:

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

There’s an additional helper reciprocating .get() (and its alias .extract_first()) for .re(), named .re_first(). Use it to extract just the first matching string:

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

extract() and extract_first()

如果您是Scrapy的长期用户,则可能熟悉.extract().extract_first()选择器方法. 许多博客文章和教程也正在使用它们. Scrapy仍支持这些方法, 没有计划弃用它们.

但是,现在使用.get().getall()方法编写Scrapy用法文档. 我们认为这些新方法可以使代码更简洁易读.

以下示例显示了这些方法如何相互映射.

  1. SelectorList.get()SelectorList.extract_first()相同:

    >>> response.css('a::attr(href)').get()
    'image1.html'
    >>> response.css('a::attr(href)').extract_first()
    'image1.html'
    
  2. SelectorList.getall()SelectorList.extract()相同:

    >>> response.css('a::attr(href)').getall()
    ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
    >>> response.css('a::attr(href)').extract()
    ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
    
  3. Selector.get()Selector.extract()相同:

    >>> response.css('a::attr(href)')[0].get()
    'image1.html'
    >>> response.css('a::attr(href)')[0].extract()
    'image1.html'
    
  4. 为了保持一致性,还有Selector.getall() ,它返回一个列表:

    >>> response.css('a::attr(href)')[0].getall()
    ['image1.html']
    

因此,主要区别在于.get().getall()方法的输出更可预测: .get()始终返回单个结果, .getall()始终返回所有提取结果的列表. 使用.extract()方法,结果是否为列表并不总是很明显; 要获得单个结果,应调用.extract().extract_first() .

Working with XPaths

这里有一些技巧,可以帮助您有效地将XPath与Scrapy选择器一起使用. 如果您对XPath不太熟悉,则可以先阅读本XPath教程 .

Note

其中一些技巧是基于ScrapingHub博客上的这篇文章 .

Working with relative XPaths

请记住,如果要嵌套选择器并使用以/开头的XPath,则该XPath对文档而言是绝对的,而不是相对于您从中调用它的Selector .

例如,假设您要提取<div>元素内的所有<p> <div>元素. 首先,您将获得所有<div>元素:

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

最初,您可能会想使用以下方法,这是错误的,因为它实际上从文档中提取了所有<p>元素,而不仅是<div>元素中的那些元素:

>>> for p in divs.xpath('//p'):  # this is wrong - gets all <p> from the whole document
...     print(p.get())

这是执行此操作的正确方法(请注意在.//p XPath前面加点):

>>> for p in divs.xpath('.//p'):  # extracts all <p> inside
...     print(p.get())

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

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

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

When querying by class, consider using CSS

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

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

If you use @class='someclass' you may end up missing elements that have other classes, and if you just use contains(@class, 'someclass') to make up for that you may end up with more elements that you want, if they have a different class name that shares the string 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').getall()
['2014-07-23 19:00']

这比使用上面显示的详细XPath技巧更干净. 只记得使用. 在随后的XPath表达式中.

Beware of the difference between //node[1] and (//node)[1]

//node[1]选择在其各自的父节点下首先出现的所有节点.

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

Example:

>>> 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).getall()

这将所有第一个<li>元素置于其父元素之下:

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

这将获得整个文档中的第一个<li>元素:

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

这将在<ul>父项下获取所有第一个<li>元素:

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

这将获得整个文档中<ul>父项下的第一个<li>元素:

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

Using text nodes in a condition

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

这是因为表达式.//text()产生文本元素的集合-一个节点集 . 当节点集转换为字符串时(将其作为参数传递给诸如contains()starts-with()的字符串函数时,会发生这种情况starts-with() ,它仅产生第一个元素的文本.

Example:

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

节点集转换为字符串:

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

但是,转换为字符串的节点会将其自身的文本及其所有后代组合在一起:

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

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

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

但是使用. 表示该节点有效:

>>> sel.xpath("//a[contains(., 'Next Page')]").getall()
['<a href="#">Click here to go to the <strong>Next Page</strong></a>']

Variables in XPath expressions

XPath允许您使用$somevariable语法在XPath表达式中引用变量. 这有点类似于SQL世界中的参数化查询或预备语句,在查询中您用占位符(如?替换了查询中的某些参数? ,然后将其替换为查询传递的值.

下面是一个示例,该示例基于元素的" id"属性值对其进行匹配,而无需对其进行硬编码(如前所示):

>>> # `$val` used in the expression, a `val` argument needs to be passed
>>> response.xpath('//div[@id=$val]/a/text()', val='images').get()
'Name: My image 1 '

这是另一个示例,查找包含五个<a><a><div>标记的" id"属性(此处我们将值5传递为整数):

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

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

parsel是为Scrapy选择器提供动力的库,提供了有关XPath变量的更多详细信息和示例.

Removing namespaces

在处理抓取项目时,通常完全删除命名空间并仅使用元素名称来编写更简单/方便的XPath,通常很方便. 您可以为此使用Selector.remove_namespaces()方法.

让我们展示一个示例,该示例通过Python Insider博客原子feed进行说明.

首先,使用要抓取的网址打开外壳:

$ scrapy shell https://feeds.feedburner.com/PythonInsider

这是文件启动的方式:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet ...
<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/"
      xmlns:blogger="http://schemas.google.com/blogger/2008"
      xmlns:georss="http://www.georss.org/georss"
      xmlns:gd="http://schemas.google.com/g/2005"
      xmlns:thr="http://purl.org/syndication/thread/1.0"
      xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
  ...

您会看到几个名称空间声明,包括默认的" http://www.w3.org/2005/Atom "和另一个使用" http://schemas.google.com/g/2005 "前缀的" gd:"声明.

进入外壳后,我们可以尝试选择所有<link>对象,并查看其是否无效(因为Atom XML名称空间混淆了这些节点):

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

但是,一旦调用Selector.remove_namespaces()方法,便可以直接通过其名称访问所有节点:

>>> response.selector.remove_namespaces()
>>> response.xpath("//link")
[<Selector xpath='//link' data='<link rel="alternate" type="text/html" h'>,
 <Selector xpath='//link' data='<link rel="next" type="application/atom+'>,
 ...

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

  1. 删除名称空间需要迭代和修改文档中的所有节点,这对于在默认情况下对Scrapy爬网的所有文档执行是一项相当昂贵的操作
  2. 在某些情况下,实际上某些情况下需要使用名称空间,以防某些元素名称在名称空间之间冲突. 这些情况很少见.

Using EXSLT extensions

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

prefix namespace usage
re http://exslt.org/regular-expressions regular expressions
set http://exslt.org/sets set manipulation

Regular expressions

例如,当XPath的starts-with()contains()不够用时, test()函数可能会非常有用.

在列表项中选择链接的示例,该链接的" class"属性以数字结尾:

>>> from scrapy import Selector
>>> doc = u"""
... <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').getall()
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
>>> sel.xpath('//li[re:test(@class, "item-\d$")]//@href').getall()
['link1.html', 'link2.html', 'link4.html', 'link5.html']
>>>

Warning

C库libxslt本机不支持EXSLT正则表达式,因此lxml的实现使用钩子连接到Python的re模块. 因此,在XPath表达式中使用regexp函数可能会增加一点性能损失.

Set operations

例如,在提取文本元素之前,将它们排除在文档树的某些部分之外可能很方便.

使用项目范围和相应项目属性组提取微数据(示例内容来自http://schema.org/Product )的示例:

>>> doc = u"""
... <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').getall())
...     props = scope.xpath('''
...                 set:difference(./descendant::*/@itemprop,
...                                .//*[@itemscope]/*/@itemprop)''')
...     print("    properties: %s" % (props.getall()))
...     print("")

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

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

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

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

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

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

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

>>>

在这里,我们首先遍历itemscope元素,对于每个元素,我们查找所有itemprops元素,并排除那些本身在另一个itemscope元素.

Other XPath extensions

Scrapy选择器还提供了一个XPath扩展函数has-class ,它非常丢失has-class为具有所有指定HTML类的节点返回True .

对于以下HTML:

<p class="foo bar-baz">First</p>
<p class="foo">Second</p>
<p class="bar">Third</p>
<p>Fourth</p>

您可以像这样使用它:

>>> response.xpath('//p[has-class("foo")]')
[<Selector xpath='//p[has-class("foo")]' data='<p class="foo bar-baz">First</p>'>,
 <Selector xpath='//p[has-class("foo")]' data='<p class="foo">Second</p>'>]
>>> response.xpath('//p[has-class("foo", "bar-baz")]')
[<Selector xpath='//p[has-class("foo", "bar-baz")]' data='<p class="foo bar-baz">First</p>'>]
>>> response.xpath('//p[has-class("foo", "bar")]')
[]

所以XPath //p[has-class("foo", "bar-baz")]大致等同于CSS p.foo.bar-baz . 请注意,在大多数情况下,它的速度较慢,因为它是针对每个有问题的节点调用的纯Python函数,而CSS查找被转换为XPath,因此运行效率更高,因此在性能方面,其使用仅限于CSS选择器不容易描述的情况.

Parsel还简化了添加自己的XPath扩展的过程.

parsel.xpathfuncs.set_xpathfunc(fname, func)

Register a custom extension function to use in XPath expressions.

将为每个匹配的节点调用在fname标识符下注册的函数func ,并传递一个context参数以及从相应XPath表达式传递的任何参数.

如果funcNone ,扩展功能将被删除.

请参阅lxml文档中的更多内容 .

Built-in Selectors reference

Selector objects

class scrapy.selector.Selector(response=None, text=None, type=None, root=None, **kwargs)

Selector实例是对选择其内容某些部分的响应的包装.

response是一个HtmlResponseXmlResponse对象,将用于选择和提取数据.

如果没有responsetext是unicode字符串或utf-8编码的文本. 一起使用textresponse是未定义的行为.

type定义选择器类型,可以是"html""xml"None (默认).

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

If type is None and a response is passed, the selector type is inferred from the response type as follows:

否则,如果type被设置,则选择类型将被迫并且不会发生检测.

xpath(query, namespaces=None, **kwargs)

查找与xpath query匹配的节点,并以所有元素展平的SelectorList实例的形式返回结果. 列表元素也实现Selector接口.

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

namespaces是一个可选的prefix: namespace-uri映射(dict),用于向那些在register_namespace(prefix, uri)提供附加前缀. 与register_namespace()相反,这些前缀不会保存供以后调用.

任何其他命名参数都可以用于在XPath表达式中传递XPath变量的值,例如:

selector.xpath('//a[href=$url]', url="http://www.example.com")

Note

For convenience, this method can be called as response.xpath()

css(query)

应用给定的CSS选择器并返回SelectorList实例.

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

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

Note

为了方便起见,此方法可以称为response.css()

get()

序列化并以单个unicode字符串返回匹配的节点. 编码内容的百分比未引用.

另请参见: extract()和extract_first()

attrib

返回基础元素的属性字典.

另请参阅: 选择元素属性 .

re(regex, replace_entities=True)

应用给定的正则表达式,并返回具有匹配项的unicode字符串列表.

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

默认情况下,字符实体引用由其对应的字符替换( &amp;&lt;除外). 传递replace_entitiesFalse关闭这些替换.

re_first(regex, default=None, replace_entities=True)

应用给定的正则表达式并返回匹配的第一个unicode字符串. 如果不匹配,则返回默认值(如果未提供参数,则返回None ).

默认情况下,字符实体引用由其对应的字符替换( &amp;&lt;除外). 传递replace_entitiesFalse关闭这些替换.

register_namespace(prefix, uri)

注册要在此Selector使用的给定名称空间. 如果不注册名称空间,则无法从非标准名称空间选择或提取数据. 请参阅有关XML响应的选择器示例 .

remove_namespaces()

删除所有名称空间,允许使用无名称空间的xpath遍历文档. 请参阅删除名称空间 .

__bool__()

如果选择了任何实际内容,则返回True否则返回False . 换句话说, Selector的布尔值由其选择的内容给出.

getall()

序列化并以1元素的unicode字符串列表返回匹配的节点.

为了保持一致性,此方法已添加到Selector中. 与SelectorList一起使用时更有用. 另请参见: extract()和extract_first()

SelectorList objects

class scrapy.selector.SelectorList

SelectorList类是内置list类的子类,它提供了一些其他方法.

xpath(xpath, namespaces=None, **kwargs)

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

querySelector.xpath()参数相同

namespaces是一个可选的prefix: namespace-uri映射(dict),用于向那些在register_namespace(prefix, uri)提供附加前缀. 与register_namespace()相反,这些前缀不会保存供以后调用.

任何其他命名参数都可以用于在XPath表达式中传递XPath变量的值,例如:

selector.xpath('//a[href=$url]', url="http://www.example.com")
css(query)

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

querySelector.css()参数相同

getall()

调用此列表的每个元素的.get()方法,并将其结果展平,作为Unicode字符串列表返回.

另请参见: extract()和extract_first()

get(default=None)

返回此列表中第一个元素的.get()结果. 如果列表为空,则返回默认值.

另请参见: extract()和extract_first()

re(regex, replace_entities=True)

对该列表中的每个元素调用.re()方法,并将其结果展平,作为Unicode字符串列表返回.

默认情况下,字符实体引用将替换为其对应的字符( &amp;&lt;除外.如果False传递replace_entities关闭这些替换.

re_first(regex, default=None, replace_entities=True)

对该列表中的第一个元素调用.re()方法,并以Unicode字符串返回结果. 如果列表为空或正则表达式不匹配任何内容,则返回默认值(如果未提供参数,则返回None ).

默认情况下,字符实体引用将替换为其对应的字符( &amp;&lt;除外.如果False传递replace_entities关闭这些替换.

attrib

返回第一个元素的属性字典. 如果列表为空,则返回空dict.

另请参阅: 选择元素属性 .

Examples

Selector examples on HTML response

这是一些Selector示例,用于说明几个概念. 在所有情况下,我们都假定已经有一个用HtmlResponse对象实例化的Selector ,如下所示:

sel = Selector(html_response)
  1. 从HTML响应正文中选择所有<h1>元素,并返回Selector对象(即SelectorList对象)的列表:

    sel.xpath("//h1")
    
  2. 从HTML响应正文中提取所有<h1>元素的文本,并返回Unicode字符串列表:

    sel.xpath("//h1").getall()         # this includes the h1 tag
    sel.xpath("//h1/text()").getall()  # this excludes the h1 tag
    
  3. 遍历所有<p>标记并打印其class属性:

    for node in sel.xpath("//p"):
        print(node.attrib['class'])
    

Selector examples on XML response

以下是一些示例,这些示例说明了使用XmlResponse对象实例化的Selector对象的概念:

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

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

    sel.register_namespace("g", "http://base.google.com/ns/1.0")
    sel.xpath("//g:price").getall()