XPath 简介
XPath (XML Path Language) 最初是为了在 XML 文档中进行导航而设计的语言,后来被广泛应用于 HTML 文档的解析。与 BeautifulSoup 相比,XPath 有以下特点:
- 语法强大:可以通过简洁的表达式精确定位元素
- 跨平台性:几乎所有编程语言都有 XPath 的实现
- 灵活性高:可以通过各种轴、谓词和函数构建复杂的选择条件
在 Python 中,我们主要通过 lxml 或者 selenium 库来使用 XPath 功能。可以通过 pip 安装下面的依赖包。
pip install lxml
pip install selenium
XPath 测试工具
浏览器
用浏览器测试验证 XPath 最直观,最方便,优先推荐这种方式。比如在百度热搜 百度热搜 测试 XPath。
在浏览器开发者工具的 Elements 中按 Ctrl + F
,在搜索框中输入 XPath 。比如查找百度热搜标题,XPath 正确的话,会在页面高亮显示对应的元素。
lxml
Python 的 lxml 库提供了强大的 XPath 支持。比如以下用来解析提取热搜新闻标题。
from lxml import etree
import requests# 获取HTML内容
url = "https://top.baidu.com/board?tab=realtime"
response = requests.get(url)
html_text = response.text# 解析HTML
# html = etree.HTML(html_text)
# 或者从文件加载HTML
html = etree.parse('百度热搜.html', etree.HTMLParser())# 使用XPath提取数据
titles = html.xpath('//div/a/div[@class="c-single-text-ellipsis"]/text()') # 获取标题文本
for index, title in enumerate(titles):print(f"第 {index + 1} 条新闻;新闻标题:{title}")
print("===热搜标题提取正常===")
第 1 条新闻;新闻标题:去“三好”邻邦家做客
第 2 条新闻;新闻标题:拜登:特朗普太掉价了
第 3 条新闻;新闻标题:央行1万亿元大红包对普通人影响多大
...
第 48 条新闻;新闻标题:世界人形机器人运动会将在北京举办
第 49 条新闻;新闻标题:苹果探索在浏览器中加入AI搜索功能
第 50 条新闻;新闻标题:专家解读:为何央行此时宣布降准降息
第 51 条新闻;新闻标题:骑士G2冤死 裁判报告公布三次漏判
===热搜标题提取正常===
selenium
Python 的 selenium 库提也供了强大的 XPath 支持。比如使用 RPA 流程自动化爬取数据的时候,总避免不了通过 XPath 去定位和查询元素。以下用来解析提取热搜新闻标题。
from selenium import webdriver
from selenium.webdriver.common.by import Bydef init_driver():option = webdriver.ChromeOptions()driver = webdriver.Chrome(r'./driver/chromedriver.exe', options=option)driver.maximize_window()return driverdef main():driver = init_driver()url = r'file:///E:\lky_project\tmp_project\百度热搜.html'driver.get(url)try:xpath_titles = '//div/a/div[@class="c-single-text-ellipsis"]'titles = driver.find_elements(By.XPATH, xpath_titles)for index, title in enumerate(titles):print(f"第 {index + 1} 条新闻;新闻标题:{title.text}")print("===热搜标题提取正常===")except Exception as e:print("===热搜标题提取报错===", e)returnif __name__ == '__main__':main()
第 1 条新闻;新闻标题:去“三好”邻邦家做客
第 2 条新闻;新闻标题:拜登:特朗普太掉价了
第 3 条新闻;新闻标题:央行1万亿元大红包对普通人影响多大
...
第 48 条新闻;新闻标题:世界人形机器人运动会将在北京举办
第 49 条新闻;新闻标题:苹果探索在浏览器中加入AI搜索功能
第 50 条新闻;新闻标题:专家解读:为何央行此时宣布降准降息
第 51 条新闻;新闻标题:骑士G2冤死 裁判报告公布三次漏判
===热搜标题提取正常===
XPath 常用函数
contains()
contains()
函数用于判断某个属性的取值中是否包含指定的字符串。其语法格式如下:
contains(@attribute, "substring")
@attribute
:表示要过滤的属性名称,属性名称前需加上@
符号。substring
:表示要判断是否包含的字符串。
在自动化测试中,contains
函数特别适用于定位那些属性值不固定的元素。例如,某个元素的id
属性值在每次页面刷新时都会发生变化,但其中某些字符是固定的。通过 contains
函数,可以基于这些固定的字符进行定位。
比如页面中查找 name
属性中包含 username 的 input
元素:
//input[contains(@name, 'username')]
starts-with()
与 contains() 类似,只不过是限定指定开头的属性或者文本。
//div/a/div[starts-with(text(), " 国防部")]
ends-with()
限定指定结尾的属性或者文本(部分浏览器不一定支持,要想兼容性更强的话建议最好少用)。
//div/a/div[ends-with(text(), "热烈欢迎 ")]
position()
position()
函数用于选取指定位置的元素。其语法格式如下。
//element[position() < number]
//element[position() <= number]
//element[position() = number]
//element[position() > number]
//element[position() >= number]
position()
:表示元素的序号,从1开始计数。number
:表示设定的阈值。
比如:
//element[1]
等价于:
//element[position() = 1]
比如页面中有多个 input
元素,我们希望选取前两个 input
元素。
//input[position()<3]
last()
last()
函数用于选取从后往前数的元素。其语法格式如下:
//element[last() - number]
last()
:表示匹配元素的总数。number
:表示从后往前数的元素位置。
假设页面中有多个 input
元素,我们希望选取最后一个 input
元素:
//input[last()]
如果希望选取倒数第二个 input
元素:
//input[last() - 1]
count()
count() 函数通过对子元素指定类型节点进行统计来限定父元素的选取。语法格式如下:
//element[count(sub_element) < number]
//element[count(sub_element) <= number]
//element[count(sub_element) = number]
//element[count(sub_element) > number]
//element[count(sub_element) >= number]
count(sub_element)
:表示 element 元素 下 sub_element 的数目。number
:表示设定的阈值。
比如我们选取子元素中 div 的个数为 2 的 a 元素:
//a[count(div)=2]
text()
text() 函数用于获取元素的直接文本内容。一般与 contains() 结合使用,来选取文本内容包含指定字符串的元素。
比如查找文本中包含 "热烈欢迎" 的 div 元素。
//div[contains(text(), "热烈欢迎")]
也可以用文本的精确匹配。
//div[text()="热烈欢迎"]
node()
node()
函数用于选取所有节点。其语法格式如下:
//node()
node()
函数的功能与 * 通配
符号类似,用于选取所有节点。例如:
//*
或
//node()
上述两种表达式的效果相同,均用于选取页面中的所有节点(但通配符 * 不包括文本,注释,指令等节点,如果也要包含这些节点请用 node() 函数)。
XPath 基础语法
路径操作符
操作符 | 描述 | 示例 |
---|---|---|
/ | 从根节点选取元素 | /html/body |
// | 递归步进下降选择文档中符合条件的所有元素 | //a |
. | 选取当前节点 | ./div |
.. | 选取当前节点的父节点 | ../ |
@ | 选取属性 | //div[@id] |
* | 通配符,选择任意元素 | //* |
条件谓词
XPath 允许我们使用方括号 []
来添加条件谓词进行筛选限定 。
//div[1] # 第一个div元素
//div[last()] # 最后一个div元素
//div[position()<3] # 前两个div元素
//div[@class] # 所有有class属性的div元素
//div[@class='main'] # class属性值为'main'的div元素
//div[contains(@class, 'content')] # class属性包含'content'的div元素
//a[text()='click'] # 文本内容为'click'的a元素
//a[count(div)=2] # 包含2个div元素的a元素
轴操作
以 百度热搜 首页为例。
ancestor
ancestor 用以获取元素的祖先节点。比如 热搜 对应 xpath 为:
//div[2]/a/span[text()="热搜"]
则下面的 xpath 用以获取 热搜 元素对应的所有祖先节点:
//div[2]/a/span[text()="热搜"]/ancestor::*
包含当前节点自身则需要用 ancestor-or-self:
//div[2]/a/span[text()="热搜"]/ancestor-or-self::*
则下面的 xpath 用以获取 热搜 元素对应的所有 div 祖先节点:
//div[2]/a/span[text()="热搜"]/ancestor::div
descendant
descendant 用以获取元素的后代节点。
比如获取 //div[@id="sanRoot"] 的所有后代 div 节点(不限定节点类型则使用 *)
//div[@id="sanRoot"]/descendant::div
包含当前节点自身则需要用 decendant-or-self:
//div[@id="sanRoot"]/descendant-or-self::div
following
following 用以选取文档中当前节点结束标签之后的所有节点。
//div/div[@class="bg-wrapper"]/following::*
如果只选取当前节点之后的所有兄弟节点,则使用 following-sibling。
//div/div[@class="bg-wrapper"]/following-sibling::*
preceding
preceding 用于选取文档中当前节点开始标签之前的所有节点(不包含当前节点的父辈节点)。
//div[last()]/div[@class="content_1YWBm"]/preceding::*
如果选取当前节点之前的所有同级节点,则使用 preceding-sibling。
//div[last()]/div[@class="content_1YWBm"]/preceding-sibling::*
parent
parent 用以获取元素的父节点。
//div/div[@class="bg-wrapper"]/parent::*
child
child 用以获取元素的子节点。
//div/div[@id="sanRoot"]/child::*
self
self 用以获取当前元素节点自己本身。
//div/div[@id="sanRoot"]/self::*
attribute
通过属性来获取元素。
比如获取所有包含 class 属性的节点。
//attribute::class
或
//@class
获取所有包含 class 属性的 div 节点。
//div[@class]
逻辑运算符
and
逻辑与,比如查询热搜的前三个热搜。
//div/div[@class="category-wrap_iQLoo horizontal_1eKyQ" and position()<4]
or
逻辑或
//div/div[@class="category-wrap_iQLoo horizontal_1eKyQ" or position()=last()]
not
非
//div/a/div[not(@class="c-single-text-ellipsis")]
XPath 优化
一般可以在浏览器中直接复制 XPath,但是浏览器复制的 XPath 一般是用绝对路径写的能唯一定位这个元素的 XPath。 如果页面结构稍微发生变动,可能导致 XPath 不可用。
比如复制的 XPath 如下:
//*[@id="sanRoot"]/main/div[2]/div/div[2]/div[1]/div[2]/a/div[1]
优化后可以为:
//div[1]/div/a/div[@class="c-single-text-ellipsis"]
XPath 常用的优化思路包括:
- 找到元素附近的独特标识(如 id、特定的 class 等)
- 尽量使用相对路径而非绝对路径
- 使用
contains()
等函数处理动态变化的属性值