肝了三天三夜,一文道尽 Python的 Xpath 解析!

开发 前端
大家在写爬虫时,往往获取到网页之后,需要从网页中提取我们需要的信息。这时候就需要用到 xpath 或者 css 选择器来定位页面元素信息。

[[434465]]

大家在写爬虫时,往往获取到网页之后,需要从网页中提取我们需要的信息。这时候就需要用到 xpath 或者 css 选择器来定位页面元素信息。但是,由于这两者都是非人性化的语法,导致好多人望而生畏,经常为这个发愁。

今天我就尝试用一篇文章来道尽 xpath 解析 HTML 的方方面面,希望大家看完这篇文章后,从此不再害怕 xpath 解析。

路径表达式

  • nodename:选取此节点的所有子节点
  • /:从当前节点选取直接子节点
  • //:从当前接点选取子孙节点
  • .:选取当前节点
  • ..:选取当前接点的父节点
  • @:选取属性

我们先放上一段 HTML 代码:

  1. <html> 
  2.   <head> 
  3.     <title> 
  4.       Xpath test page 
  5.     </title> 
  6.   </head> 
  7.   <body> 
  8.     <div class="navli"
  9.       <span class="nav_tit"
  10.         <a href="https://www.baidu.com/"
  11.           百度 
  12.         </a> 
  13.         <i class="group" /> 
  14.       </span> 
  15.     </div> 
  16.     <div class="navli"
  17.       <span class="nav_tit"
  18.         <a href="https://news.cctv.com/"
  19.           新闻频道 
  20.         </a> 
  21.       </span> 
  22.     </div> 
  23.     <div class="navli"
  24.       <span class="nav_tit"
  25.         <a href="https://sports.cctv.com/"
  26.           体育频道 
  27.         </a> 
  28.       </span> 
  29.     </div> 
  30.   </body> 
  31. </html> 

接下来,我们针对这段 HTML 代码来进行 xpath 解析。

要进行 xpath 解析,我们先要将 HTML 文本转化成对象:

  1. from lxml import etree 
  2.  
  3. text = ''
  4. <div> 
  5.             <ul id='ultest'
  6.                  <li class="item-0"><a href="link1.html">first item</a></li> 
  7.                  <li class="item-1"><a href="link2.html">second item</a></li> 
  8.                  <li class="item-inactive"><a href="link3.html">third item</a></li> 
  9.                  <li class="item-1"><a href="link4.html"><span>fourth item</span></a></li> 
  10.                  <li class="item-0"><a href="link5.html">fifth item</a> # 注意,此处缺少一个 </li> 闭合标签 
  11.              </ul> 
  12.          </div> 
  13. ''
  14. # 调用HTML类进行初始化,这样就成功构造了一个XPath解析对象。 
  15. page = etree.HTML(text)     
  16. print(type(page)) 

我们可以看到打印的结果:

  1. <class 'lxml.etree._Element'

nodename

nodename 表示根据标签名字选取标签,注意只会选择子标签!比如:如果是儿子的儿子则选取不到。

  1. print(page.xpath("body")) 
  2.  
  3. //[<Element body at 0x1966d1c48c0>] 
  4.  
  5. print(page.xpath("ul")) 
  6.  
  7. // [] 

这个 nodename 我有点不是太清楚,当我使用 body 时,可以找到出 body 节点元素,但是使用 ul 时,找不到 ul 节点元素,打印的是空。这个网上搜索也没有什么准确的答案,如果你知道这里面的原理,还请告诉我。

/

/ 表示从根节点选取一级一级筛选(不能跳)。

  1. print(page.xpath("/html")) 
  2.  
  3. // [<Element html at 0x27107f41100>] 
  4.  
  5. print(page.xpath("/body")) 
  6.  
  7. // [] 

可以看到,我选取根节点 html ,可以打印出根节点元素,而我选取 body 打印时,是找不到的,这个符号只能从根节点开始找。

//

// 表示从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。注意:是所有符合条件的。

  1. print(page.xpath("//li")) 
  2.  
  3. // [<Element li at 0x1cd2a325780>, <Element li at 0x1cd2a325840>, <Element li at 0x1cd2a3259c0>, <Element li at 0x1cd2a325b00>, <Element li at 0x1cd2a325ac0>] 

.

. 表示选取当前标签。

  1. ul = page.xpath("//ul"
  2. print(ul) 
  3. print(ul[0].xpath(".")) 
  4. print(ul[0].xpath("./li")) 
  5.  
  6. // [<Element ul at 0x1cd2a325840>] 
  7. // [<Element ul at 0x1cd2a325840>] 
  8. // [<Element li at 0x1cd2a325700>, <Element li at 0x1cd2a325b00>, <Element li at 0x1cd2a325640>, <Element li at 0x1cd2a325ac0>, <Element li at 0x1cd2a325c00>] 

我们先定位到 ul 元素节点,这里的结果是一个列表,然后再打印当前节点列表的第一个 ul,接着我们打印这个 ul 节点的子节点 li。

..

.. 表示选取当前标签的父节点。

  1. print(ul[0].xpath("..")) 
  2.  
  3. // [] 

这里打印第一个 ul 节点的父元素,也就是 div 。

@

@ 表示获取标签的属性值。

  1. print(ul[0].xpath("@id")) 
  2.  
  3. // ['ultest'

我们打印第一个 ul 节点的 id 属性,可以看到结果是 ‘ultest’。

谓语

谓语用来查找某个或某些特定的节点或者包含某个指定值的节点。谓语被嵌在方括号中。

  1. //a[n] n为大于零的整数,代表子元素排在第n个位置的<a>元素 
  2. //a[last()]   last()  代表子元素排在最后个位置的<a>元素 
  3. //a[last()-]  和上面同理,代表倒数第二个 
  4. //a[position()<3] 位置序号小于3,也就是前两个,这里我们可以看出xpath中的序列是从1开始 
  5. //a[@href]    拥有href的<a>元素 
  6. //a[@href='www.baidu.com']    href属性值为'www.baidu.com'的<a>元素 
  7. //book[@price>2]  price值大于2的<book>元素 

同样的,我们来举一些例子:

  1. # 第三个li标签 
  2. print(page.xpath('//ul/li[3]')) 
  3. # 最后一个li标签 
  4. print(page.xpath('//ul/li[last()]')) 
  5. # 倒数第二个li标签 
  6. print(page.xpath('//ul/li[last()-1]')) 
  7. # 序号小于3的li标签 
  8. print(page.xpath('//ul/li[position()<3]')) 
  9. # 有class属性的li标签 
  10. print(page.xpath('//li[@class]')) 
  11. # class属性为item-inactive的li标签 
  12. print(page.xpath("//li[@class='item-inactive']")) 

获取文本

text()

我们用text()获取某个节点下的文本:

  1. print(page.xpath('//ul/li/a/text()')) 
  2.  
  3. // ['first item''second item''third item''fourth item''fifth item'

string()

我们用string()获取某个节点下所有的文本:

  1. print(page.xpath('string(//ul)')) 

输出内容为:

  1. first item 
  2. second item 
  3. third item 
  4. fourth item 
  5. fifth item # 注意,此处缺少一个  闭合标签 

fifth item # 注意,此处缺少一个 闭合标签

通配符

  • * 任意元素
  • @* 任意属性

* 表示匹配任何元素节点:

  1. print(page.xpath('//li/*')) 
  2.  
  3. // [<Element a at 0x208931f0f00>, <Element a at 0x208931f0f40>, <Element a at 0x208931f0c40>, <Element a at 0x208931f0d80>, <Element a at 0x208931ff080>] 

@* 表示匹配任何属性节点:

  1. print(page.xpath('//li/@*')) 
  2.  
  3. // ['item-0''item-1''item-inactive''item-1''item-0'

或运算

通过在路径表达式中使用"|"运算符,可以实现选取若干个路径。

  1. # 选取所有的li和a节点 
  2. print(page.xpath("//li|//a")) 
  3.  
  4. // [<Element li at 0x29bb7190ac0>, <Element a at 0x29bb7190b00>, <Element li at 0x29bb7190f00>, <Element a at 0x29bb7190dc0>, <Element li at 0x29bb7190fc0>, <Element a at 0x29bb7190e00>, <Element li at 0x29bb7190f80>, <Element a at 0x29bb71b1080>, <Element li at 0x29bb71b1040>, <Element a at 0x29bb7190cc0>] 

函数

xpath内置很多函数。更多函数查看https://www.w3school.com.cn/xpath/xpath_functions.asp。

  • contains(string1,string2)
  • starts-with(string1,string2)
  • text()
  • last()
  • position()
  • node()

contains

有的时候,class作为选择条件的时候不合适@class='....' 这个是完全匹配,当网页样式发生变化时,class或许会增加或减少像active的class。用contains就能很方便。

  1. print(page.xpath("//*[contains(@class, 'item-inactive')]")) 
  2.  
  3. // [] 

starts-with

  1. print(page.xpath("//*[starts-with(@class, 'item-inactive')]")) 
  2. // [<Element li at 0x1a297641d00>] 

其他几个函数,我们在上面使用过。注意,并不是所有的 xpath 函数python都会支持,比如 ends-with(string1,string2) 和 upper-case(string) 就不支持。

节点轴选择

ancestor轴

调用 ancestor 轴,获取所有祖先节点。其后需要跟两个冒号,然后是节点的选择器。返回结果:第一个li节点的所有祖先节点。

  1. print(page.xpath('//li[1]/ancestor::*')) 
  2.  
  3. // [, , , ] 

attribute轴

调用 attribute 轴,获取所有属性值。返回结果:li节点的所有属性值。

  1. print(page.xpath('//li[1]/attribute::*')) 
  2.  
  3. // ['item-0'

child轴

调用 child 轴,获取所有直接子节点。返回结果:选取 href 属性为 link1.html 的 a 子节点。

  1. print(page.xpath('//li[1]/child::a[@href="link1.html"]')) 
  2.  
  3. // [<Element a at 0x13972af5b40>] 

descendant轴

调用 descendant 轴,获取所有子孙节点。同时加了限定条件。返回结果:选取 li 节点下的子孙节点里的 span 节点。

  1. print(page.xpath('//li[4]/descendant::span')) 
  2.  
  3. // [<Element span at 0x1a4d5700d00>] 

following轴

调用 following 轴,获取当前节点之后的所有节点。

  1. print(page.xpath('//li[4]/following::*[2]')) 
  2.  
  3. // [<Element a at 0x1583f8c0d00>] 

following-sibling轴

调用 following-sibling 轴,获取当前节点之后的所有同级节点。

  1. print(page.xpath('//li[4]/following-sibling::*')) 
  2.  
  3. // [] 

总结

到这里,我们的 xpath 学习之路就结束了,文章中基本涵盖了大家需要用的的 xpath 解析方法。大家看一遍没记住不要紧,以后遇到此类解析直接搬出这篇文章对照着写就行。

 

责任编辑:武晓燕 来源: Python技术
相关推荐

2014-08-04 13:56:42

安卓概念

2022-01-12 10:57:59

网络

2021-11-23 23:13:11

数据库安全工具

2021-04-30 05:45:41

多线程Java基础面试题

2020-11-16 09:02:38

Python开发工具

2020-11-24 10:13:02

Redis集群数据库

2020-01-30 10:30:32

AI 数据人工智能

2021-01-04 14:59:50

AIAI技术机器学习

2020-01-07 14:24:18

人工智能机器学习技术

2021-05-14 14:01:31

加密货币网络安全加密钱包

2024-01-29 09:04:15

Wi-Fi数据设备

2011-09-30 09:29:19

TechCruch创业2010年

2020-06-30 08:27:56

Python开发工具

2024-03-28 10:08:31

自动驾驶工具

2021-04-04 22:58:20

互联网IP网络协议

2019-08-28 08:58:18

技术物联网云计算

2020-04-21 10:37:41

Apply数据参数

2023-02-24 09:55:17

自动驾驶神经网络

2022-08-31 09:39:32

自动驾驶芯片技术

2012-12-20 12:22:03

点赞
收藏

51CTO技术栈公众号