Python
基础语法
01概念
02安装
03变量
04字符串
05数
06常量与注释
07列表
08元组
09if语句
10字典
11集合
12复合数据类型对比
13推导式
14用户输入
15while循环
16函数
17类
18面向对象编程
19文件操作
20异常处理
21日期和时间
22魔术方法
23内置函数
24线程
25并发&并行
26正则表达式
27迭代器
28装饰器
29生成器
30上下文管理器
31函数式编程
32闭包
33解包
34工具库
35连接关系型数据库
36虚拟环境
37异步编程
网络爬虫
01urllib库[了解]
02requests库
03数据交换格式
04解析库
05lxml
06Beautiful Soup
07Xpath语法
08动态网页的处理
-
+
首页
07Xpath语法
XPath(XML Path Language)是一种用于在 XML 和 HTML 文档中定位节点(元素、属性、文本等)的查询语言,由 W3C 制定。 目前最广泛应用的是**XPath 1.0**(1999 年发布),后续版本(XPath 2.0/3.0/3.1)虽增强了功能,但在主流解析工具(如 lxml、浏览器开发者工具)中,XPath 1.0 仍是支持最完善、使用最广泛的版本。 ## 概念 XPath 将 XML/HTML 文档视为**节点树**,所有内容都以节点形式存在。核心节点类型包括: - **元素节点**(Element):如\<div>、\<a>等标签(最常用); - **属性节点**(Attribute):元素的属性,如\<a href="url">中的href; - **文本节点**(Text):标签内的文本内容,如\<p>text\</p>中的text; - **根节点**(Root):文档的最顶层节点(HTML 中通常为\<html>,XML 中为文档节点); - **注释节点**(Comment):如\<!-- 注释内容 -->; - **处理指令节点**(Processing Instruction):用于指示处理文档的指令(较少见)。 ## 路径表达式 XPath 通过 “路径” 定位节点,**路径表达式由节点名、运算符和筛选条件组成**,核心符号如下: | 表达式 | 含义 | 例如 | | ------ | ---------------------------------------- | ------------------------------------------------------------ | | / | 从根节点开始(绝对路径) | /html/body/div → 根节点\<html>下的\<body>→\<div> | | // | 从文档任意位置开始(相对路径,忽略层级) | //div → 文档中所有\<div>元素;//ul//li → 所有\<ul>下的\<li>(无论嵌套层级) | | . | 当前节点 | ./p → 当前节点的直接子节点\<p>;//div/. → 等价于//div | | .. | 当前节点的父节点 | //div/.. → 所有\<div>的父节点;//li/../@class → 所有\<li>父节点的class属性 | | @ | 选取属性节点 | //a/@href → 所有\<a>的href属性值;//input/@* → 所有\<input>的所有属性 | ## 节点选择 ### 选取元素节点 **直接通过标签名**:选取所有该标签的元素节点。 例如://p → 所有\<p>元素;/html/head/title → 根节点下\<html>→\<head>→\<title>(绝对路径)。 **通配符(匹配任意节点)**: - \*:匹配任意元素节点。例如://div/* → 所有\<div>的直接子元素(无论标签名); - @\*:匹配任意属性节点。例如://a/@* → 所有\<a>的所有属性; - node():匹配任意类型节点(元素、文本、注释等)。例如://div/node() → \<div>下的所有节点(包括文本、子元素)。 ### 选取文本节点 通过text()函数选取文本节点,返回节点的文本内容。 例如: - //h1/text() → 所有\<h1>标签内的文本; - //div[@class="content"]/p/text() → class="content"的\<div>下所有\<p>的文本; - //*[text()="登录"] → 文本内容为 “登录” 的任意元素。 ### 选取属性节点 通过**@属性名**选取属性节点,返回属性值。 例如: - //img/@src → 所有图片的src属性(图片 URL); - //a[@class="link"]/@href → class="link"的\<a>标签的href属性(链接); - //*[@id="username"] → 所有含id="username"属性的元素。 ## 条件筛选节点 谓语是 XPath 的核心功能,用于通过条件筛选节点,语法为[条件](类似 SQL 的WHERE),条件可基于索引、属性、文本、子节点等。 ### 索引筛选(索引从 1 开始,非 0!) 例如: ```txt //li[1] → 所有<li>中的第一个(按文档顺序); //ul/li[last()] → 每个<ul>的最后一个<li>(last()为内置函数); //tr[position() < 4] → 前 3 个<tr>(position()返回当前节点位置); //div[last()-1] → 倒数第二个<div>。 ``` ### 属性条件筛选 等于 / 不等于://div[@class="main"] → class属性为 “main” 的<div>;//a[@href!="#"] → href不等于 “#” 的<a>。 多属性匹配://input[@name="email" and @type="text"] → 同时满足name="email"和type="text"的<input>(and/or逻辑运算符)。 属性存在性://div[@id] → 所有含id属性的<div>;//p[not(@class)] → 不含class属性的<p>(not()取反)。 属性包含://a[contains(@href, "article")] → href属性包含 “article” 的<a>(contains()函数)。 ### 文本条件筛选 //p[text()="欢迎使用"] → 文本内容精确为 “欢迎使用” 的<p>; //span[contains(text(), "警告")] → 文本包含 “警告” 的<span> 【contains(str1, str2):str1 是否包含 str2】; //h3[starts-with(text(), "章节")] → 文本以 “章节” 开头的<h3> 【starts-with(str1, str2):str1 是否以 str2 开头】; //div[text()!=null] → 包含非空文本的<div>。 ### 节点条件筛选 存在子节点://ul[li] → 包含<li>子节点的<ul>;//div[p[@class="intro"]] → 包含class="intro"的<p>子节点的<div>。 子节点文本条件://li[span/text()="推荐"] → 子节点<span>文本为 “推荐” 的<li>;//book[price>50] → 子节点<price>值大于 50 的<book>(数值比较)。 ## 通过节点关系定位 轴用于描述节点与当前节点的关系(如父子、兄弟、祖先等),语法为**轴名称::节点测试[条件]**。常用轴如下: | 轴名称 | 含义 | 示例 | | ----------------- | -------------------------------------------------- | ------------------------------------------------------------ | | child | 当前节点的直接子节点(默认轴,可省略) | //div/child::p 等价于 //div/p → \<div>的直接子节点<p> | | parent | 当前节点的直接父节点 | //li/parent::ul → \<li>的直接父节点\<ul>;//a/parent::* → \<a>的任意类型父节点 | | ancestor | 当前节点的所有祖先节点(父、祖父等) | //p/ancestor::div → \<p>的所有\<div>类型祖先;//span/ancestor::* → \<span>的所有祖先节点 | | ancestor-or-self | 当前节点及其所有祖先节点 | //div[@id="box"]/ancestor-or-self::* → id="box"的\<div>及其所有祖先 | | following-sibling | 当前节点之后的所有兄弟节点(同层级) | //li[1]/following-sibling::li → 第一个\<li>之后的所有兄弟\<li> | | preceding-sibling | 当前节点之前的所有兄弟节点(同层级) | //li[last()]/preceding-sibling::li → 最后一个\<li>之前的所有兄弟\<li> | | following | 文档中当前节点之后的所有节点(除祖先、自身、兄弟) | //h2/following::p → \<h2>之后的所有\<p>(无论层级) | | preceding | 文档中当前节点之前的所有节点(除后代、自身、兄弟) | //footer/preceding::div → \<footer>之前的所有\<div> | | self | 当前节点自身 | //div[self::div] → 等价于//div(通常用于条件判断) | | descendant | 当前节点的所有后代节点(子、孙等) | //ul/descendant::a → \<ul>下的所有\<a>(无论嵌套层级);等价于//ul//a | 例如: ```html <div class="nav"> <ul> <li>首页</li> <li>产品</li> <li>关于我们</li> </ul> </div> ``` //li[text()="产品"]/preceding-sibling::li/text() → 第一个<li>的文本 “首页”(“产品” 前的兄弟节点); //li[text()="产品"]/following-sibling::li/text() → 第三个<li>的文本 “关于我们”(“产品” 后的兄弟节点); //li/ancestor::div/@class → 导航<div>的class属性 “nav”(<li>的祖先<div>)。 ## 常用函数 | 函数 | 作用 | 示例 | | ----------------------- | --------------------------------------- | ------------------------------------------------------------ | | text() | 返回当前节点的文本节点 | //h1/text() → \<h1>的文本内容 | | position() | 返回当前节点在同级中的位置(从 1 开始) | //li[position()=2] → 第二个\<li> | | last() | 返回同级节点中的最后一个位置索引 | //tr[last()] → 最后一个\<tr> | | contains(str1, str2) | 判断 str1 是否包含 str2(模糊匹配) | //a[contains(@href, "detail")] → href含 “detail” 的\<a> | | starts-with(str1, str2) | 判断 str1 是否以 str2 开头 | //div[starts-with(@id, "section")] → id以 “section” 开头的\<div> | | not(condition) | 对条件取反 | //p[not(contains(text(), "广告"))] → 文本不含 “广告” 的\<p> | | count(node-set) | 返回节点集中的节点数量 | count(//li) → 文档中\<li>的总数量 | | sum(node-set) | 计算节点集中数值的总和 | sum(//price/text()) → 所有\<price>文本的数值总和 | | string(node) | 将节点转换为字符串 | string(//div[@id="info"]) → id="info"的\<div>内所有文本的拼接 | ## 命名空间 XML 和部分 HTML 文档可能包含命名空间(用于避免标签名冲突),如: ```xml <ns:book xmlns:ns="http://maolin101/books"> <ns:title>XPath教程</ns:title> </ns:book> ``` 此时需通过命名空间前缀定位节点,语法为前缀:标签名,并在解析时绑定命名空间 URI 与前缀。在 lxml 中处理方式: ```python from lxml import etree xml_str = ''' <ns:book xmlns:ns="http://maolin101/books"> <ns:title>XPath教程</ns:title> </ns:book> ''' root = etree.fromstring(xml_str) # 绑定命名空间:前缀"ns"对应URI ns = {"ns": "http://example.com/books"} # XPath中使用前缀 title = root.xpath("//ns:title/text()", namespaces=ns) print(title) # ['XPath教程'] ``` ## 注意事项 XPath 虽设计用于 XML,但可兼容 HTML(通过解析器自动修复不规范标签),但需注意: **大小写敏感**:XPath 对标签名和属性名严格大小写(如//Div与//div不同),但 HTML 标签通常小写,需保持一致。 **多 class 属性处理**:HTML 标签可能含多个 class(如\<li class="item active">),直接匹配@class="item active"可能因顺序失效,需用contains(): //li[contains(@class, "item") and contains(@class, "active")]。 **空白文本节点**:HTML 中的换行、空格会被解析为文本节点,使用following-sibling等轴时需注意过滤(可结合node()[name()="标签名"]筛选元素节点)。 ## 案例 ### 解析 HTML 页面(提取文章列表) ```python from lxml import etree # 1. 定义HTML内容 html_content = """ <!DOCTYPE html> <html> <head> <title>技术文章列表</title> </head> <body> <div class="container"> <h1>热门技术文章</h1> <ul class="articles"> <li class="article"> <a href="/python-basic" class="link">Python 基础入门</a> <span class="views">10000+ 阅读</span> </li> <li class="article hot"> <a href="/xpath-tutorial" class="link">XPath 语法详解</a> <span class="views">25000+ 阅读</span> </li> <li class="article"> <a href="/lxml-guide" class="link">lxml 库使用指南</a> <span class="views">8000+ 阅读</span> </li> </ul> </div> </body> </html> """ # 2. 解析HTML tree = etree.HTML(html_content) # 3. 使用XPath提取数据 print("===== 提取页面标题 =====") title = tree.xpath('//title/text()')[0] print(title) # 技术文章列表 print("\n===== 提取所有文章标题 =====") article_titles = tree.xpath('//ul[@class="articles"]/li/a/text()') for title in article_titles: print(title) print("\n===== 提取所有文章链接 =====") article_links = tree.xpath('//a[@class="link"]/@href') for link in article_links: print(link) print("\n===== 提取热门文章(带hot类) =====") hot_article = tree.xpath('//li[contains(@class, "hot")]/a/text()')[0] hot_views = tree.xpath('//li[contains(@class, "hot")]/span/text()')[0] print(f"热门文章:{hot_article},阅读量:{hot_views}") print("\n===== 提取阅读量超过10000的文章 =====") # 先获取所有文章,再筛选阅读量 articles = tree.xpath('//li[@class="article"]') for article in articles: title = article.xpath('./a/text()')[0] views = article.xpath('./span/text()')[0] # 提取数字部分并比较 if int(views.replace('+', '').split()[0]) > 10000: print(title, views) ``` 输出结果: ```txt ===== 提取页面标题 ===== 技术文章列表 ===== 提取所有文章标题 ===== Python 基础入门 XPath 语法详解 lxml 库使用指南 ===== 提取所有文章链接 ===== /python-basic /xpath-tutorial /lxml-guide ===== 提取热门文章(带hot类) ===== 热门文章:XPath 语法详解,阅读量:25000+ 阅读 ===== 提取阅读量超过10000的文章 ===== ``` ### 解析 XML 文档(提取书籍信息) ```python from lxml import etree # 1. 定义XML内容 xml_content = """ <bookstore> <book category="编程"> <title lang="zh">Python 编程入门</title> <author>张三</author> <price>59.9</price> <publish_date>2023-01-15</publish_date> </book> <book category="数据科学"> <title lang="en">Data Analysis with Python</title> <author>李四</author> <price>79.0</price> <publish_date>2022-05-20</publish_date> </book> <book category="编程"> <title lang="zh">JavaScript 高级程序设计</title> <author>王五</author> <price>109.0</price> <publish_date>2021-11-30</publish_date> </book> </bookstore> """ # 2. 解析XML tree = etree.XML(xml_content) # 3. 使用XPath提取数据 print("===== 提取所有书籍标题 =====") titles = tree.xpath('//title/text()') for title in titles: print(title) print("\n===== 提取所有编程类书籍 =====") programming_books = tree.xpath('//book[@category="编程"]/title/text()') for book in programming_books: print(book) print("\n===== 提取中文书籍(lang属性为zh) =====") chinese_books = tree.xpath('//title[@lang="zh"]/text()') for book in chinese_books: print(book) print("\n===== 提取价格大于70的书籍信息 =====") expensive_books = tree.xpath('//book[price > 70]') for book in expensive_books: title = book.xpath('./title/text()')[0] author = book.xpath('./author/text()')[0] price = book.xpath('./price/text()')[0] print(f"{title} - {author} - {price}元") print("\n===== 提取最后一本书的出版日期 =====") last_book_date = tree.xpath('//book[last()]/publish_date/text()')[0] print(last_book_date) ``` 输出结果: ```python ===== 提取所有书籍标题 ===== Python 编程入门 Data Analysis with Python JavaScript 高级程序设计 ===== 提取所有编程类书籍 ===== Python 编程入门 JavaScript 高级程序设计 ===== 提取中文书籍(lang属性为zh) ===== Python 编程入门 JavaScript 高级程序设计 ===== 提取价格大于70的书籍信息 ===== Data Analysis with Python - 李四 - 79.0元 JavaScript 高级程序设计 - 王五 - 109.0元 ===== 提取最后一本书的出版日期 ===== 2021-11-30 ``` ### 使用轴选择器解析复杂结构 ```python from lxml import etree # 1. 定义HTML内容 html_content = """ <div class="nav"> <ul> <li id="home">首页</li> <li id="products">产品 <ul class="submenu"> <li>电脑</li> <li>手机</li> <li>平板</li> </ul> </li> <li id="about">关于我们</li> <li id="contact">联系我们</li> </ul> </div> """ # 2. 解析HTML tree = etree.HTML(html_content) # 3. 使用轴选择器提取数据 print("===== 提取所有子菜单(后代节点) =====") submenu_items = tree.xpath('//li[@id="products"]/descendant::li/text()') for item in submenu_items: print(item) print("\n===== 提取关于我们的前一个兄弟节点 =====") prev_sibling = tree.xpath('//li[@id="about"]/preceding-sibling::li[1]/text()')[0] print(prev_sibling) # 产品 print("\n===== 提取首页的所有后续兄弟节点 =====") next_siblings = tree.xpath('//li[@id="home"]/following-sibling::li/text()') # 过滤空白文本 next_siblings = [s.strip() for s in next_siblings if s.strip()] for sibling in next_siblings: print(sibling) print("\n===== 提取子菜单的所有祖先节点 =====") ancestors = tree.xpath('//ul[@class="submenu"]/ancestor::*/@id | //ul[@class="submenu"]/ancestor::*/@class') print(ancestors) # ['products', 'nav'] ``` 输出结果: ```txt ===== 提取所有子菜单(后代节点) ===== 电脑 手机 平板 ===== 提取关于我们的前一个兄弟节点 ===== 产品 ===== 提取首页的所有后续兄弟节点 ===== 产品 关于我们 联系我们 ===== 提取子菜单的所有祖先节点 ===== ['nav', 'products'] ```
毛林
2025年9月7日 12:11
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码