正则表达式(re)
在 xpath 中的 starts-with() 或者 contians() 无效时 ,可以采用 re 的 test() 函数。
例如:
>>> 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表达式中使用regexp函数可能会牺牲少量的性能。
集合操作 (Set)
集合操作可以方便地用于在提取文字元素前从文档树中去除一些部分。 例如使用itemscopes组和对应的itemprops来提取微数据(microdata)(来自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]'): #获取每一个 div 域,共有七个 div 域
... print "current scope:", scope.xpath('@itemtype').extract() # 提取 div 中的 itemtype 元素
... props = scope.xpath('''
... set:difference(./descendant::*/@itemprop,
... .//*[@itemscope]/*/@itemprop)''') # 提取相对 div 域中下一层 标签 中的 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 内的元素。
Some XPath tips
1. 谨慎的使用text nodes
当你想要使用文本内容作为XPath函数的参数时,避免使用 .//text() ,采用 . 来替代 这是因为.//text()会产生一个text元素的集合——一个节点集合。当一个node-set被转换成一个string(例如,当它被当做参数传递给contains()或者start-with()函数的时候),它只返回第一个元素。
示例如下:
>>> from scrapy import Selector
>>> sel = Selector(text='<a href="#">Click here to go to the <strong>Next Page</strong></a>')
把一个node-set转化成string:
>>> sel.xpath('//a//text()').extract() # 查看一下node-set
[u'Click here to go to the ', u'Next Page']
>>> sel.xpath("string(//a[1]//text())").extract() #转换成string
[u'Click here to go to the ']
节点被转化成了string,但是,它本身以及子节点都放在了一起。
>>> 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()node-set ** 不会得到任何结果:
>>> 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>']
2. 注意 //node[1] 和(//node)[1]的区别
- //node[1] 选择它们的父节点的第一个子节点(occurring first under their respective parents)
- (//node)[1] 选择文档中的所有node,然后选取其中的第一个
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).extract()
获得父节点中的第一个 <li> 标签
>>> xp("//li[1]")
[u'<li>1</li>', u'<li>4</li>']
获得document中所有 <li> 标签的第一个 <li>
>>> xp("(//li)[1]")
[u'<li>1</li>']
获得在以 <ul> 作为父节点的 所有的第一个 <li>
>>> xp("//ul/li[1]")
[u'<li>1</li>', u'<li>4</li>']
获得document 中所有以 <ul> 作为父节点的 <li> 节点的第一个 <li>
>>> xp("(//ul/li)[1]")
[u'<li>1</li>']
3. 当通过class查询的时候, 考虑使用CSS
因为一个元素可能含有多个CSS class,用XPath的方式选择元素会很冗长:
*[contains(concat(' ', normalize-space(@class), ' '), ' someclass ')]
如果使用@class='someclass'可能会遗漏含有其他class的元素,如果使用contains(@class, 'someclass')去补偿的话,会发现其中包含了多余的含有相同的someclass的元素。
因此,scrapy允许链式使用选择器,因此多数情况下你可以先用CSS选择class,再使用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']