- Published on
xpath入门文档
- Authors
- Name
- Pony Ma
xpath入门文档
xpath 是什么
XPath(XML Path Language)全名 XML 路径语言,他是一种用来查询 XML 或 HTML 文档中指定位置数据的语言。
xpath 的选择功能十分强大,他提供了很多简单明了的路径选择表达式,还提供了 100 多个内建的函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。
我们做抓取任务时,完全可以使用 xpath 实现相应的信息抽取。
Tips: 我们通常说的 xpath 其实是 xpath 表达式
xpath 基础使用
示例 HTML 文本:
<!DOCTYPE html>
<html lang="en">
<body>
<div>
<ul>
<li class="zhao">
<a href="link1.html">link1</a>
<span>title1</span>
</li>
<li class="zhao">
<a href="link2.html">link2</a>
<span>title2</span>
</li>
<li class="zhong">
<a href="link3.html">link3</a>
<span>title3</span>
</li>
</ul>
</div>
</body>
</html>
我们需要先了解一下网页结构,了解一下节点的概念
网页结构
这是一个非常简单的 HTML 示例
<!DOCTYPE html>
<html lang="en">
<body>
<div>
<ul>
<li class="zhao">
<a href="link1.html">link1</a>
<span>title1</span>
</li>
<li class="zhao">
<a href="link2.html">link2</a>
<span>title2</span>
</li>
<li class="zhong">
<a href="link3.html">link3</a>
<span>title3</span>
</li>
</ul>
</div>
</body>
</html>
在 HTML 中,所有标签定义的内容都是节点,这些节点构成了一个 HTML 节点树,也叫做 HTML DOM 树,
- 整个网站文档是一个文档节点
- 每个
html
标签对应一个根节点 - 每个节点内的文本是文本节点
- 每个节点的属性是属性节点
- 注释是注释节点
暂时无法在飞书文档外展示此内容
整个 HTML 文档就类似于一棵树,这种结构也被称为节点树,每一个分叉都是一个节点。
节点树中的节点彼此拥有层级关系,我们常用父(parent)、子(child)、兄弟(sibling)等来描述这些关系。父节点拥有子节点,同级的子节点被称为兄弟节点。
<a href="link1.html" id=1, class="link">link1</a>
href
id
class
都是a
节点的属性
节点的属性也是一种节点,用于提供有关节点的额外信息或特性。您可以将节点属性看作是节点的附加描述,这些描述可以帮助您更好地理解和处理节点
所有节点
我们可以使用 //*
来选取所有的节点,也就是整个 HTML 文本中的所有节点,*
表示任意节点
可以通过指定节点名称来获取想要的节点,例如要获取所有的 li
节点,则是 //li
。
子节点
<li>
<a>子链接</a>
</li>
<li>
<a>子链接</a>
<div>
<a>孙链接</a>
</div>
</li>
通过 /
或者//
来查找节点的子节点和子孙节点
例如要获取li
节点的所有直接的子节点a
,则是//li/a
如果要获取li
节点的所有的子孙节点a
,则是//li//a
要注意/
和//
的区别,/
用于获取所有直接的子节点,//
用于获取所有的子孙节点
父节点
<li>
<a>子链接</a>
</li>
<li>
<a>子链接</a>
<div>子div</div>
</li>
我们已经知道了如何获取子节点,假如我们知道了子节点,该怎样查找父节点呢? 可以通过..
来实现
例如要获取所有a
节点的父节点,则是//a/..
如果要获取所有 a
节点的父节点下的div
节点(即和a
同一级的div
节点),则是//a/../div
拓展知识:也可以通过parent::*
来获取
文本获取
<li>
<a>子链接</a>
</li>
当我们想要获取节点中的文本时,可以使用 text()
方法,
例如要获取所有a
节点中的文本,则是//a/text()
拓展知识:也可以通过string(.)
来获取
属性获取
<li>
<a href="/link.html">子链接</a>
</li>
我们可以用text()
来获取节点内部文本,那么节点属性该怎么获取呢?
我们可以通过通过@属性名
来获取,
例如要获取所有a
节点的href
属性,则是//a/@href
属性匹配
<li class="zhao">
<a href="link1.html">link1</a>
</li>
<li class="zhao">
<a href="link2.html">link2</a>
</li>
<li class="zhong">
<a href="link3.html">link3</a>
</li>
如果想通过属性匹配来获取节点,则用中括号加属性名和值来限定某个属性
我们通过//li[@class="zhao"]
,限制了只获取class
属性为zhao
的li
节点,符合条件的li
节点只有两个,所有结果也会只返回两个元素。
例如要获取所有href
属性值为link1.html
的a
节点,则是//a[@href='link1.html']
。
属性多值匹配
有时候,某个节点的某个属性可能有多个值,例如
<li class="li li-first">
<a href="link.html">first link</a>
</li>
<li class="li li-second">
<a href="link.html">second link</a>
</li>
这里的 HTML 文本中li
节点的class
属性就有两个值li
和li-first
,如果还用之前的属性匹配可能就无法匹配到所有的节点了
这种情况就需要用到contains
方法,则是//li[contains(@class, "li")]
contains
方法,第一个参数传入属性名称,第二个参数传入属性值,只要传入的节点的属性值里包含传入的属性,就可以成功匹配到。
常见的方法
刚刚我们提到了一个新的概念方法
,xpath 的方法有很多,这里列举了一些比较常用的
方法名 | 描述 | 例子 |
---|---|---|
contains | 包含 | //li[contains(@class, "li-first")] 获取li 节点的class 属性中包含li-first的节点 |
text | 获取文本 | //a/text() 获取a 节点中的文本 |
last | 获取最后一个节点的位置序号 | //li[last()] 获取最后一个li 节点 |
position | 获取当前节点的位置序号 | //li[position()<3] 获取位置序号小于 3 的节点 |
..... |
多属性匹配
我们还有可能遇到另一种情况,就是根据多个属性确定一个节点,此时需要同时匹配多个属性,例如
<li class="li li-first" name="item">
<a href="link.html">first link</a>
</li>
<li class="li li-second" name="item">
<a href="link.html">second link</a>
</li>
<li class="li" name="next">
<a href="next.html">next</a>
</li>
这里的 HTML 文本中li
节点就含有多个属性
如果要获取正确的 li
节点,就需要有两个条件,class
属性里包含li
,同时name
属性值为item
我们需要用 xpath 的运算符and
来实现 ,即
//li[contains(@class, "li") and @name="item"]
常见的运算符
刚刚我们提到了一个新的概念运算符
,xpath 的运算符有很多,这里列举了一些比较常用的
运算符 | 描述 | 例子 |
---|---|---|
and | 并且、与 | //li[@class="li" and @name="item"] 获取 class 为li 并且 name 为item 的li节点 |
or | 或者 | //li[@class="li" or @name="item"] 获取 class 为li 或者 name 为item 的li节点 |
` | ` | 计算两个节点的并集 |
> | 大于 | //li[position()>3] 获取第三个li节点之后的所有li 节点 |
< | 小于 | //li[position()<3] 获取第三个li节点之前的所有li 节点 |
...... |
按序选择
在选择节点时,可能会匹配到多个节点,但我们只想要其中的某一个,如第二个或者最后一个,这个时候该怎么处理呢?
<li class="item1"><a href="link.html">first link</a></li>
<li class="item2"><a href="link.html">second link</a></li>
<li class="item3"><a href="link.html">third link</a></li>
<li class="item4"><a href="link.html">fourth link</a></li>
<li class="item4"><a href="link.html">fifth link</a></li>
我们可以使用中括号中传入索引的方法获取特定次序的节点
选择第一个li
,即//li[1]
选择第三个li
,即//li[3]
选择最后一个li
,即//li[last()]
选择前三个li
,即//li[position()<=3]
节点轴选择
我们想通过节点的层级关系来获取节点时该怎么办呢
xpath 提供了很多节点轴的选择方法,包括获取子节点、兄弟节点、父节点、祖先节点等等
<div>
<ul>
<li class="item1"><a href="link.html">first link</a></li>
<li class="item2"><a href="link.html">second link</a></li>
<li class="item3"><a href="link.html">third link</a></li>
<li class="item4"><a href="link.html">fourth link</a></li>
<li class="item4"><a href="link.html">fifth link</a></li>
</ul>
</div>
通过ancestor
获取祖先节点,第一个li
节点的所有祖先节点,例如//li[1]/ancestor::*
通过parent
获取父节点,例如第一个a
节点的父节点,//a/parent::*
通过child
获取子节点,例如第一个li
节点的子节点,//li/child::*
通过descendant
获取子孙节点,例如ul
的子孙节点,//ul/descendant::*
通过following
获取当前节点之后的所有节点,例如li[@class="item1"]
之后的所有节点,li[@class="item1"]/following::*
通过following-sibling
获取当前节点之后的所有同级节点,,例如li[@class="item1"]
之后的所有节点,li[@class="item1"]/following-sibling::*
更多内容可查看 XPath 教程
如何选择合适的 xpath
使用唯一的特征
尽量选择具有唯一性的特征来定位节点,例如id
属性或者class
属性,保证你的 xpath 不会误选到其他的节点
<li id=1 class="item" style="color: black;"><a href="link.html">first link</a></li>
<li id=1 class="item" style="color: black;"><a href="link.html">second link</a></li>
//li[@class="item"]
//li[@style="color: black;"]
避免过于具体的 xpath
尽量编写通用的 XPath,以便在文档结构发生变化时,XPath 仍然有效。过于具体的 XPath 可能会使您的代码变得脆弱
<div>
<ul>
<li class="item" style="color: black;"><a href="link.html">first link</a></li>
<ul>
</div>
//li[@class="item"]
/div/ul/li
使用属性特征
谓词是 XPath 表达式中的条件,它们允许您更精确地选择元素。例如,[@attribute='value']
允许您根据元素的属性来选择
<li data-field="link"><a href="link.html">first link</a></li>
<li data-field="link"><a href="link.html">second link</a></li>
<li data-field="page"><a href="page.html">third link</a></li>
//li[@data-field="link"]
//li[position()<3]
xpath 练手实践
选择合适的下一页
寻找下一页按钮的 xpath 是我们经常遇到的场景,让我们通过几个例子来实践下上述所学的内容。
正常下一页
http://www.ygcgfw.com/gggs/001002/001002006/subpage-gggs.html
通过下边三种常用的 xpath 都可以成功获取到下一页,我们通常建议采用第二种
//ul[@class="m-pagination-page"]/li[7]/a
//ul[@class="m-pagination-page"]/li[last()-1]/a
//ul[@class="m-pagination-page"]/li/a[contains(text(), "下一页")]
第一页与后续页面不一致
http://ggzyjy-eweb.wenzhou.gov.cn/col/col1229678555/index.html###
遇到第一页的下一页 xpath 与后续页面的不一致该怎么办
通过下边两种常用的 xpath 都可以成功获取到,我们通常建议采用第一种
//div[@id="pagelist"]/a[last()-1]
//div[@id="pagelist"]/a[contains(text(), "下页")]
没有下一页
http://jyglj.sc.gov.cn/scjyglj/gjszfgk/list11646105227320.shtml
遇到没有下一页按钮的网站我们应该如何处理
//span[@class='pagination-first']/a[@class="pagination-num hover"]/following-sibling::*[1]