scrapy提取数据之:xpath选择器

css选择器;虽然一招半式足以称霸江湖,但这里老夫还想再传授诸位一招:xpath提取!好好学,技多不压身。还是从几个方面说:一、属性提取;二、内容提取;三、标签内包含标签又包含标签的最外层标签里的所有内容提取;会了这些基本也就会了,反正scrapy用到的就这么几个知识,少之又少,不要想着除了这些还有神马冰上之下,绝对木有,请放心,如果真的有,我会再末尾给诸君写一些例子,依葫芦画瓢就行,那接下来就开始装13;


一、scrapy xpath 属性提取

这里先给大家列出xpath的选择器类型,如下表:XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。 下面列出了最有用的路径表达式:
 

表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

 

上面神马意思或许你还不清楚,没关系,1+1为神马等于2我们也无法证明,会用即可;可以看到上面选取属性用的是:@符号,那我们还是来试试提取最常见的属性:href、src,首先我们试着来提取href,还是用:http://lab.scrapy.cn开刀,就提取首页分页的href,如下图:

 

scrapy提取数据之:xpath选择器


调试的话我们还是在命令行使用下面命令:

scrapy shell lab.scrapyd.cn

这样的话就能成功打开lab.scrapyd.cn这个页面,那我们要如何使用xpath表达式呢?用到了这么一个函数:response.xpath("表达式"),提取属性的话既然使用:@,那我们要提取href就是:@href,试一下:

In [1]: response.xpath("@href")
Out[1]: []

可以看到神马都木有,why?因为我们木有限定从哪里提取,一般我们都需要加个://,再来试试

In [2]: response.xpath("//@href")
Out[2]:
[<Selector xpath='//@href' data='//cdnjscn.b0.upaiyun.com/libs/normalize/'>,
 <Selector xpath='//@href' data='http://lab.scrapyd.cn/usr/themes/default'>,
 ……
 <Selector xpath='//@href' data='http://lab.scrapyd.cn/tag/%E7%8E%8B%E5%B'>,
 <Selector xpath='//@href' data='http://lab.scrapyd.cn/tag/%E6%99%BA%E6%8'>,
 <Selector xpath='//@href' data='http://lab.scrapyd.cn/tag/%E6%B3%B0%E6%8'>,
……
 <Selector xpath='//@href' data='http://lab.scrapyd.cn/tag/%E8%AF%8D/'>,
 <Selector xpath='//@href' data='http://lab.scrapyd.cn'>,
 <Selector xpath='//@href' data='http://bbs.scrapyd.cn'>,
 <Selector xpath='//@href' data='http://www.scrapyd.cn'>,
 <Selector xpath='//@href' data='http://lab.scrapyd.cn/'>]

可以看到已经提取到了,只是这是这个页面所有的href,并非是我们想要的,那和css选择一样我们需要加以限制,如何限制呢?我们可以看到分页href是在一个<ol></ol>标签里面,那我们就可以这样来写:

In [9]: response.xpath("//ol//@href")

Out[9]:
[<Selector xpath='//ol//@href' data='http://lab.scrapyd.cn/page/1/'>,
 <Selector xpath='//ol//@href' data='http://lab.scrapyd.cn/page/2/'>,
 <Selector xpath='//ol//@href' data='http://lab.scrapyd.cn/page/3/'>,
 <Selector xpath='//ol//@href' data='http://lab.scrapyd.cn/page/4/'>,
 <Selector xpath='//ol//@href' data='http://lab.scrapyd.cn/page/6/'>,
 <Selector xpath='//ol//@href' data='http://lab.scrapyd.cn/page/2/'>]


"//ol//@href",这个表达式表示:ol标签下所有的href属性值,可以看到我们这里限定了html的<ol>标签,这里的话页面只有一个<ol>,不会出错,如果页面中有多个<ol>,那就不一定能得到我们想要的结果,那如何是好?这里我们还能限定我们的属性,使用的是:标签[@属性名='属性值']; 例如,我们分页标签:

<ol class="page-navigator">
  ……
</ol>

里面有个:class=“page-navigator”,那我们就可以这样限制://ol[@class="page-navigator"]//@href 好了,这样的话就能让<ol>尽量缩小范围,我们再来试试这个代码效果:

In [10]: response.xpath("//ol[@class='page-navigator']//@href")
Out[10]:
[<Selector xpath="//ol[@class='page-navigator']//@href" data='http://lab.scrapyd
.cn/page/1/'>,
 <Selector xpath="//ol[@class='page-navigator']//@href" data='http://lab.scrapyd
.cn/page/2/'>,
 <Selector xpath="//ol[@class='page-navigator']//@href" data='http://lab.scrapyd
.cn/page/3/'>,
 <Selector xpath="//ol[@class='page-navigator']//@href" data='http://lab.scrapyd
.cn/page/4/'>,
 <Selector xpath="//ol[@class='page-navigator']//@href" data='http://lab.scrapyd
.cn/page/6/'>,
 <Selector xpath="//ol[@class='page-navigator']//@href" data='http://lab.scrapyd
.cn/page/2/'>]

可以看到也是同样得到想要的效果,以此类推,诸位根据自己的情况来缩小范围,如果这里的属性是id那就:ol[@id='page-navigator'],灵活点!那这里我们还没有得出href,前后都有些括号,和css选择器一样我们还是用到了:extract()、extract_first()这两个函数,加上就可以提取纯粹的href值了,如下:

In [11]: response.xpath("//ol[@class='page-navigator']//@href").extract()
Out[11]:
['http://lab.scrapyd.cn/page/1/',
 'http://lab.scrapyd.cn/page/2/',
 'http://lab.scrapyd.cn/page/3/',
 'http://lab.scrapyd.cn/page/4/',
 'http://lab.scrapyd.cn/page/6/',
 'http://lab.scrapyd.cn/page/2/']

好了,这就是属性的提取,表达式就是://@属性名 这里的话我们还给诸君说了另外一些思路,也就是:如何缩小范围?我们使用的是:缩小标签范围、限定属性的方式,希望诸君学到的是思路!


二、接下来我们提取标签里面的内容,表达式: //text()

这个没多少好讲的,关键是以上的思路,我们需要层次限制,把范围缩小到最小,这样提取便很准确了,诸位需要在实践中多练习,我们来提取一下:lab.scrapy.cn 的标题,看用法:
 

In [4]: response.xpath("//title//text()").extract()
Out[4]: ['SCRAPY爬虫实验室 - SCRAPY中文网提供']

可以看到,表达式为://title//text() ,就是用到了://text() 这个表达式,前面的://title是限定你要提取的范围,同理如果你要提取首页右侧的标签,如下:

<ul class="tags-list">
        <li><a style="color:rgb(101,86,
           72)" href="http://lab.scrapyd.cn/tag/%E4%BA%BA%E7%94%9F/">
         人生</a></li>
        <li><a style="color:rgb(214,236,
           5)" href="http://lab.scrapyd.cn/tag/%E5%8A%B1%E5%BF%97/">
         励志</a></li>
         ……
        <li><a style="color:rgb(4,4,
           41)" href="http://lab.scrapyd.cn/tag/%E7%BB%9D%E4%B8%96%E5%A5%BD%E8%AF%8D/">
         绝世好词</a></li>
        <li><a style="color:rgb(204,12,
           225)" href="http://lab.scrapyd.cn/tag/%E6%9C%A8%E5%BF%83/">
         木心</a></li>
        ……
     	<li><a href="http://lab.scrapyd.cn">返回首页</a></li>
<li><a href="http://bbs.scrapyd.cn" target="_blank">SCRAPY中文社区</a></li>
	<li><a href="http://www.scrapyd.cn" target="_blank">SCRAPY中文网</a></li>
</ul>

可以看到标签文字是在 "class=tags-list" 的<ul>里面,那我们就可以这样写表达式://ul[@class='tags-list']//a//text(),具体代码如下:

In [5]: response.xpath("//ul[@class='tags-list']//a//text()").extract()
Out[5]:
['\r\n         人生',
 '\r\n         励志',
 '\r\n         爱情',
 '\r\n         王尔德',
 '\r\n         智慧',
 '\r\n         泰戈尔',
 '\r\n         绝世好词',
 '\r\n         木心',
 '\r\n         艺术',
 '\r\n         名画',
 '\r\n         生活',
 '\r\n         词',
 '返回首页',
 'SCRAPY中文社区',
 'SCRAPY中文网']

可以看到经过这样的限制,我们就定位到了;

三、包含HTML标签的所有文字内容提取:string()

这种用法主要是提取一些内容页,里面标签夹杂着文字,但我们只要文字!比如我们随便点击进:http://lab.scrapyd.cn/archives/28.html 这个详情页,我们要提取div里面的所有文字:

<div class="post-content" itemprop="articleBody">
       <p>如果你因失去了太阳而流泪,那么你也将失去群星了。 
      <br>If you shed tears when you miss the sun, you also miss the stars. 
      </p>
      <p><a href="http://www.scrapyd.cn">scrapy中文网(</a><a href="http://www.scrapyd.cn">http://www.scrapyd.cn</a>)整理</p>        
</div>


如果我们用表达式://div[@class='post-content']//text(),你会发现虽然能提取但是一个列表,不是整段文字:
 

In [4]: response.xpath("//div[@class='post-content']//text()").extract()
Out[4]:
['\n            ',
 '如果你因失去了太阳而流泪,那么你也将失去群星了。 ',
 'If you shed tears when you miss the sun, you also miss the stars. ',
 'scrapy中文网(',
 'http://www.scrapyd.cn',
 ')整理',
 '        ']

那这里我们就用到一个xpath函数:string(),我们可以把表达式这样写:response.xpath("string(//div[@class='post-content'])").extract(),可看到我们没有使用:text(),而是用:string(要提取内容的标签),这样的话就能把数据都提取出来了,而且都合成为一条,并非一个列表,如下:

In [5]: response.xpath("string(//div[@class='post-content'])").extract()
Out[5]: ['\n            如果你因失去了太阳而流泪,那么你也将失去群星了。 If you
shed tears when you miss the sun, you also miss the stars. scrapy中文网(http://
www.scrapyd.cn)整理        ']

这一种用法在我们提取商品详情、小说内容的时候经常用到,望诸君好好学习、天天赚钱!

四、xpath实例:
 

路径表达式 结果
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()<3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang='eng'] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。

 

路径表达式 结果
bookstore 选取 bookstore 元素的所有子节点。
/bookstore

选取根元素 bookstore。

注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!

bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
//book 选取所有 book 子元素,而不管它们在文档中的位置。
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
//@lang 选取名为 lang 的所有属性。

 

XPath 通配符可用来选取未知的 HTML元素。

通配符 描述
* 匹配任何元素节点。
@* 匹配任何属性节点。
node() 匹配任何类型的节点。

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
/bookstore/* 选取 bookstore 元素的所有子元素。
//* 选取文档中的所有元素。
//title[@*] 选取所有带有属性的 title 元素。
 


差不多就这些,如果诸君还想深入,百度xpath有很多教程,我也懒得复制粘贴了,以上内容scrapy够用即可!