Scrapy: Selectors

Selectors

当你爬取网页的时候,从HTML 中提取数据时最平常的任务。这有几种库帮助你来完成它:

  • BeautifulSoup 在python程序猿中很受欢迎,它根据HTML 代码的结构构建python对象,并且适合处理破损的结果,但是它慢啊。
  • lxml ElementTree. 使用基于 elementtree 的pythonic API来解析XML (也可以解析HTML ), 这个不是python 的标准库

scrapy 有他自己的机制来提取数据。叫做选择器,因为他们使用 xpath or css 表达式来选择指定HTML文件的某些部分。
xpath 是用来选择XML 文档节点的语言,也可以对HTML 使用。CSS是适应HTML文档样式的语言。他定义选择器来与指定的HTML 元素的样式联系

Note

scrapy 选择器是对 parsel 库的薄纸包装,这个包装的目的是提供与 scrapy 响应对象更好的集成。

parsel 是一个标准独立的网络爬取库,可以不依靠scrapy 使用。scrapy使用lxml 库作为引擎,在lxml API上实现了一个简单的API。这意味着scrapy 选择器在速度和解析的精度上与lxml 很相似。

Using selectors

Constructing selectors

响应对象在 .selector 属性上实现了一个 selector 实例。

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

因为经常使用 xpath and css 来查询响应,所以这有两种简写: response.xpath() ,response.css()

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

scrapy 选择器是通过传递 textresponse对象或 作为Unicode字符串的标记(在text参数中)来构建的 selector 实例。通常不需要手动构建scrapy 选择器。因为爬虫的回调函数是可以使用响应对象的,所以大多使用response.css() 这种简写。通过使用 response.selector 或这些简写,你还可以确保响应体只会被解析一次。

但是如果你需要,可以是写使用 selector 。

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

从响应中构建 -HtmlResponse 是textresponse 的一个子类。

>>> 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'

选择器根据输入的格式自动选择最好的解析规则(XML HTML)

Using selectors

我们使用scrapy shell 来演示怎样使用选择器,例子页面在scrapy 文档服务器中。

https://docs.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>

First, let’s open the shell:

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

等sell 加载完成,你会得到一个可以使用的response 变量,和附加的选择器,在 response.selector 属性中。

因为我们要处理HTML,这个选择器就自动使用HTML解析器

So, by looking at the HTML code of that page, let’s construct an XPath for selecting the text inside the title tag:

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

To actually extract the textual data, you must call the selector .get() or .getall() methods, as follows:

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

.get() always returns a single result; if there are several matches, content of a first match is returned; if there are no matches, None is returned. .getall() returns a list with all results.

Notice that CSS selectors can select text or attribute nodes using CSS3 pseudo-elements:

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

As you can see, .xpath() and .css() methods return a SelectorList instance, which is a list of new selectors. This API can be used for quickly selecting nested data: 反正这个对嵌套的数据很好使。。。

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

If you want to extract only the first matched element, you can call the selector .get() (or its alias .extract_first() commonly used in previous Scrapy versions): extrack_first()是 get()的别名啊。。。

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

It returns None if no element was found:

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

A default return value can be provided as an argument, to be used instead of None:

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

Instead of using e.g. '@src' XPath it is possible to query for attributes using .attrib property of a 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']

As a shortcut, .attrib is also available on SelectorList directly; it returns attributes for the first matching element:

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

This is most useful when only a single result is expected, e.g. when selecting by id, or selecting unique elements on a web page: 这对唯一的标签好用,直接提取属性不要再找位置了欸。。

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

Now we’re going to get the base URL and some image links:

>>> 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']

Using selectors with regular expressions

selector 也可以使用正则来提取数据。但是不像xpath css 方法,re() 返回的是Unicode字符串列表,所以你不能嵌套调用 re()

Here’s an example used to extract image names from the HTML code above:

>>> 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']

额外的帮助程序,用于re() 的 get() 方法,叫 re_first(),这个只会匹配第一个字符串

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

extract() and extract_first()

以前就用这个方法,现在还是支持,并且不打算弃用。

但是,现在使用更加简洁,阅读性高的get() getall() 方法。

The following examples show how these methods map to each other.

  1. SelectorList.get() is the same as SelectorList.extract_first():

    >>> response.css('a::attr(href)').get()
    'image1.html'
    >>> response.css('a::attr(href)').extract_first()
    'image1.html'
    
  2. SelectorList.getall() is the same as 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() is the same as Selector.extract():

    >>> response.css('a::attr(href)')[0].get()
    'image1.html'
    >>> response.css('a::attr(href)')[0].extract()
    'image1.html'
    
  4. For consistency, there is also Selector.getall(), which returns a list:

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

主要的区别就是:get() 返回一个单一的结果,getall() 返回一个列表。extract()方法结果就不一定,selectorlist 对象 就获得列表, selector 对象就是一个数据

Working with XPaths

就是一些提示,教你怎么更高效的使用xpath

Note

Some of the tips are based on this post from ScrapingHub’s blog.# 参考这个博客,,确实高深。。。

Working with relative XPaths

请记住,如果你使用嵌套选择器并使用 / 来做你的xpath 的开头,这个xpath 是对于文档来说是绝对的,而不是相对于你调用的选择器。

例如,你想提取所有的 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())

这才是对的。

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

这个命令是提取直接的子 p 元素。。。

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

For more details about relative XPaths see the Location Paths section in the XPath specification.

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>']

And this gets the first <li> element under an <ul> parent in the whole document:

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

Using text nodes in a condition

当你打算使用文本内容作为xpath 字符串函数的参数时,以 . 代替 .//text()

这是因为表达式 .//text() 生成的是对文本元素节点集的采集。当节点集作为参数传递给字符串函数像contains() or starts-with() 时,节点集会转变成字符串,这会导致只会产生第一个元素的文本。

Example:

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

Converting a node-set to string:

>>> 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()
[]

But using the . to mean the node, works:

>>> 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 可以让你在xpath 语法中参考变量,使用 $somevariable 语法。这与SQL 世界中的参数化查询或预备语句有些类似,你将会使用一些占位符像 ?来替换查询中的一些参数。然后与查询中传递的参数替换。

>>> # `$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 的子标签的div 下的 id 属性(这里我们传递值5作为整数。

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

所有的变量参考在调用时必须有绑定的值(否则就会报错),这是通过传递所需数量的命名参数来完成的。

parsel, 这个库为scrapy 选择器提供动力,有更多关于xpath 变量的细节和示例。

Removing namespaces

当处理scrapy 项目时,摆脱命名空间并仅使用元素名,可以编写更加简单/方便的xpath,你可以使用 selector.remove_namespaces() 方法。

使用python insider 博客的文章来说明这个例子。

First, we open the shell with the url we want to scrape:# 使用 shell 来打开我们想抓取的url

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

This is how the file starts:

<?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”和另一个使用gd 前缀的“http://schemas.google.com/g/2005”

进入sell 后我们尝试选择所有的 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

建立在lxml 之上,scrapy 选择器支持一些 EXSLT 扩展,并带有在xpath 表达式中使用的预先注册的命名空间。

prefixnamespaceusage
rehttp://exslt.org/regular-expressionsregular expressions
sethttp://exslt.org/setsset manipulation
Regular expressions

当 xpath 的 startswith() or contains() 不够用时,text() 函数很有用了

选择列表项目中的链接,带有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 模块。因此使用 regexp 函数在你的xpath 表达式时性能有点低。。

Built-in Selectors reference

Selector objects

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

Selector 的示例是对相应的包装用来选择内容的某些部分。

·response 是一个 HtmlResponse or XmlResponse 对象,用来选择提取数据

text 是一个Unicode或utf-8编码的文本,在响应不可用时使用。同时使用 text and response 是没有定义的行为。

type定义选择器的种类,像 html xml 或None (默认)

如果 type 是None,选择器就会根据响应自动选择最好的种类,或默认在 text 情况下使用 html

如果type is none 并且一个 response 传递,选择器的种类通过响应推断

如果 type 被设置,选择器种类就强制使用这个,而且不会 在进行检测

  • xpath(query, namespaces=None, **kwargs)[source]

    查询匹配xpaht query的节点,返回一个 selectorlist 示例由扁平化的所有元素。列表元素也实行 selector 接口。

    query 查询的语法

    namespaces 是一个可以选的前缀:命名空间-url 映射(字典),用于为在register_namespace(prefix,url)注册的前缀中添加格外的前缀,跟register_namespace()相反,这些前缀不会保存以备将来使用。

    任何格外的命名了的参数都可以在xpath 表达式中传递xpath变量的值

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

    Note

这个方法可以简写成 response.xpath()

使用CSS来查询,在幕后会转变成xpaht查询,使用cssselect 库来转换,然后运行 xpath 方法。可以简写成 response.css()

  • get()[source]

    序列化并且以单个unicode 字符串的形式返回匹配的节点,参考 extract() extravt_first()

  • attrib

    返回底层元素的属性字典,

  • re(regex, replace_entities=True)[source]

    应用所给的正则表达式并返回匹配的unicode 字符串列表。

    regex 可以是编译好的正则或是可以使用 re.compile(regex) 编译的正则。

    默认情况下,字符的实体会被替换成他们对应的字符(除了&amp; 和 &lt;) 。replace_entities =False 可以关闭这些替换。

  • re_first(regex, default=None, replace_entities=True)[source]

    返回第一个匹配的字符串,如果没有匹配,返回默认的值(None 如果没有指定那个参数的话)。replace_entities 跟上面一样。。

  • register_namespace(prefix, uri)[source]

    注册在这个selector 中要用的命名空间,如果没有你就不能从这个非标准的命名空间中选择或提取数据。

  • remove_namespaces()[source]

    删除所有的命名空间,运行使用无名xpath 历遍文档

  • __bool__()[source]

    如果选择了实际的内容就返回True,否则就是False。就是说,选择器的布尔值由其选择的内容给出。

  • getall()[source]

    不多说了。。。

SelectorList objects

  • classscrapy.selector.``SelectorList[source]

selectorlist 类是内置的 list 类的子类,提供了一些额外的方法,

  • xpath(xpath, namespaces=None, **kwargs)[source]

    Call the .xpath() method for each element in this list and return their results flattened as another SelectorList.# 对列表中每个元素调用xpaht() 方法,返回扁平化的结果作为另一个selectorlist

    query 跟上面那个一样

    namespaces 同上,

  • css(query)[source]

    为每一个元素调用 css() 方法。。

  • getall()[source]

    为每一个元素调用 get() 方法。

  • get(default=None)[source]

    得到第一个结果,如果是空的返回默认的值

  • re(regex, replace_entities=True)[source]

    同上

  • re_first(regex, default=None, replace_entities=True)[source]

    同上

  • attrib

    Return the attributes dictionary for the first element. If the list is empty, return an empty dict.See also: Selecting element attributes.

Examples

Selector examples on HTML response

这些示例用来说明一些概念,所有例子中,我们假设已经有了一个用htmlresponse 对象实例化的选择器,

sel = Selector(html_response)
  1. Select all <h1> elements from an HTML response body, returning a list of Selector objects (i.e. a SelectorList object):

    sel.xpath("//h1")
    
  2. Extract the text of all <h1> elements from an HTML response body, returning a list of unicode strings:

    sel.xpath("//h1").getall()         # this includes the h1 tag
    sel.xpath("//h1/text()").getall()  # this excludes the h1 tag
    
  3. Iterate over all <p> tags and print their class attribute:

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

Selector examples on XML response

Here are some examples to illustrate concepts for Selector objects instantiated with an XmlResponse object:

sel = Selector(xml_response)
  1. Select all <product> elements from an XML response body, returning a list of Selector objects (i.e. a SelectorList object):

    sel.xpath("//product")
    
  2. Extract all prices from a Google Base XML feed which requires registering a namespace:

    sel.register_namespace("g", "http://base.google.com/ns/1.0")
    sel.xpath("//g:price").getall()
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值