Json
JSON全称JavaScript Object Notation(JavaScript 对象表示法) ,是一种轻量级的、基于文本的数据交换格式
JSON 脱胎于 JavaScript 语言中对象字面量(object literal)的语法,早期开发者发现,JavaScript 里描述对象的 {key: value} 写法既直观又紧凑,非常适合用来在系统之间传递数据,于是它被广泛用于浏览器与服务器之间的数据交互,以替代笨重的 XML,并随着 Ajax 技术的兴起迅速流行。现在已成为事实上的通用数据交换格式,被广泛用于:
- API 的请求/响应体,浏览器中通过 Content-Type: application/json 标识文件
- 配置文件,如 package.json
- NoSQL 数据库(如 MongoDB)的存储格式
- 部分日志、微服务通信
特点
- JS 对象字面量允许不加引号的键、单引号、函数、undefined、注释、尾随逗号,因此 JSON 也不允许这些值
- YAML 是JSON的超集,可读性更强、支持注释,如果需要注释参考yaml
- JSON 规范不限定数字精度,但很多语言(如 JavaScript)用 IEEE 754 双精度浮点表示,大整数(超过 2^53)会丢失精度。这也是为什么很多 API 用字符串传大 ID
- JSON作为文本文件,解析开销较大,大数据量/高性能场景建议选用其他二进制格式,如:BSON等
数据类型
JSON 文本由值(value)构成,一个合法的 JSON 文件整体就是一个值(通常是一个对象或数组)。文件扩展名为.json,编码必须是UTF-8,Json 支持以下数据类型
- 对象 Object无序的键值对集合,用 { } 包裹,键必须是字符串,如:{"name": "Tom", "age": 18}
- 数组 Array有序的值列表,用 [] 包裹,如:["a", "b", 1, true]
- 字符串 String用双引号包裹的文本,如:"hello"
- 数字 Number整数或浮点数,不支持八进制/十六进制,如:42、3.14、-1e5
- 布尔值 Boolean真或假,如:true、false
- 空值 Null表示空
- 不支持:注释、undefined、日期/函数/NaN/Infinity 等类型
文件格式
- 键必须用双引号包裹(单引号非法)
- 键值对之间用逗号分隔,最后一个元素后不能有逗号(不允许尾随逗号)
- 值可以是上面 6 种类型的任意一种,且可以嵌套(对象里放数组、数组里放对象等)
- 数字不能有前导零(01 非法),不能以 . 开头(.5 非法)
- 空白(空格、换行、制表符)在结构之间可自由使用,用于美化排版
{
"id": 1001,
"name": "Alice",
"isActive": true,
"score": 95.5,
"tags": ["admin", "developer"],
"address": {
"city": "Beijing",
"zip": null
}
}
TOML
TOML全称Tom's Obvious, Minimal Language(Tom 的显明、极简语言),由 Tom Preston-Werner(GitHub 联合创始人)于 2013 年创建,以解决 NI 太简陋、YAML 缩进易错且类型隐晦、JSON 不支持注释且对人不友好的痛点,是一种语义清晰、易于阅读的配置文件格式,被广泛用于:
- 项目配置文件,尤其受 Rust 生态推崇(Cargo.toml)
- Python 打包/配置文件
数据类型
TOML 文件由键值对和表(table)组成,扩展名.toml编码必须是UTF-8
- 字符串 String基本用双引号;也支持单引号(字面量)、三引号(多行),如:"hello"、'C:\path'、"""多行"""
- 整数 Integer支持十进制、十六进制、八进制、二进制,可用下划线分隔,如:42、0xFF、1_000
- 浮点数 Float含小数或指数,支持 inf、nan,如:3.14、-1e5、inf
- 布尔值 Boolean真或假,必须小写,如:true、false
- 日期时间 Datetime支持 4 种时间形式:偏移日期时间、本地日期时间、本地日期、本地时间,如:1979-05-27T07:32:00Z
- 数组 Array用 [] 包裹,元素可混合类型,如:[1, 2, 3]、["a", "b"]
- 表 Table用 [table] 定义,类似分区/命名空间,如:[server]
- 内联表 Inline Table用 {} 写在一行内,如:point = { x = 1, y = 2 }
# 全局配置
title = "示例配置"
enabled = true
timeout = 30.5
created = 2024-01-01T12:00:00Z
ports = [8000, 8001, 8002]
# 表
[server]
host = "localhost"
port = 8080
# 嵌套表
[server.limits]
max_connections = 100
# 表数组
[[users]]
name = "Alice"
admin = true
[[users]]
name = "Bob"
admin = false
文件格式
- 值使用键值对形式key = value,而不是冒号
- 键可用裸键(name)、引号键("my key")、点分键(a.b.c)
- 表用[表名]声明,其下的键都属于该表;子表用[父.子],一个表不能重复定义;子表必须在父表之后定义
- 表数组用[[数组名]],表示可重复的一组表
- 支持注释,用#开头
- 允许尾随逗号,缩进仅影响可读性、无语法意义
yaml
YAML Ain’t Markup Language,小猫咪都会写的yaml文件
关于
YAML是一种数据序列化语言,可用于存储和传输数据,常用于编写配置文件、数据文件、日志文件,以及跨语言进行数据共享等环境,大多数编程语言都可以使用 YAML 进行数据序列化。
YAML文件后缀为.yml或.yaml
基本语法
1. 通常使用键值对形式
- 键与值通常不需要引号包裹 (这点与json不同)
- 值可以为:数组、对象、标量值
- 标量值是指:整数、浮点数、字符串、布尔值、Null、时间、日期
2. 通过缩进或者样式流表示数据结构
- 数据可以通过空格缩进来表示层级关系,但不能用制表符(Tab)
- 子结构相较父结构必须缩进至少一个空格,同一级数据须缩进相同空格数,即左端对齐
- 也可以使用花括号{ },中括号[ ]等样式流表示层级关系
3. 大小写敏感
4. 使用#号注释
对象
最为常用的数据类型,又称为字典、哈希、映射,以 key: value 键值对的形式存储数据,key: 后需要有空格!
对象写法
name: JayChou
album: 十一月的萧邦
或者使用样式流:
{
name: JayChou,
album: 十一月的萧邦
}
等同于json格式下的
{
"name": "JayChou",
"album": "十一月的萧邦"
}
缩进与嵌套
可以通过缩进至少一个空格表示层级关系,从而嵌套其他对象、数组等
student:
name: aaa
age: 16
hobby:
- basketball
- swimming
多级嵌套时,每层嵌套都应当缩进,
且每个子元素左边应该缩进相等的空格数,即左端对齐
使用样式流:
student: {
name: aaa,
age: 16,
hobby: [ basketball , swimming ]
}
复杂对象格式
使用?加空格可以指定多个key值,使用:加空格指定多个value
?
- key1
- key2
:
- value1
- value2
表示key是一个数组,值也是一个数组,样式流:
key1,key2: ["value1","value2"]
数组
使用-开头表示数组元素
数组写法
- value1
- value2
- value3
样式流:
[value1,value2,value3]
缩进与嵌套
可以通过缩进表示层级关系,来嵌套数组、对象等
- name: 周杰伦,
age: 42,
songs:
- 夜曲
- 米兰的小铁匠
- name: 林肯公园,
age: 41,
songs:
- In the End
- Numb
样式流
[
{
name: 周杰伦,
age: 42,
songs: [ 夜曲 , 米兰的小铁匠 ]
},
{
name: 林肯公园,
age: 41,
songs: [ In the End , Numb ]
}
]
标量
标量指最基本的,不可再分的值,这里指:整数、浮点数、字符串、布尔值、Null、时间、日期
基本使用方法:
1. 整数可以为非十进制数
num1: 120 #普通十进制数
num2: 0b10_1011 #二进制以0b开头
num3: 07100 #八进制以0开头
num4: 0xf1ab #十六进制以0x开头
2. 浮点数可以使用科学计数法
float1: 3.14
float2: 3.1415926e+5 #科学计数法314159.26
float3: -.inf #负无穷大
flota4: .nan #不是数字
3. 布尔值大小写均可
bool: true
值为:true、True、TRUE均可,但TRUe将被解析为字符串
4. Null可用~表示
data: ~
5. 日期必须使用ISO 8601格式,即yyyy-MM-dd
date: 2021-07-15
6. 时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区
time: 2021-07-15T00:42:31+08:00
在JavaScript中解析为Thu Jul 15 2021 00:42:31 GMT+0800 (中国标准时间)
7. 使用!!数据类型 值进行类型转换
date: !!str 2021-01-15
字符串处理
一般情况下,字符串不需要添加引号,但如果字符串中间包含特殊字符,尤其是冒号加空格组合时,为了避免被yaml解析为键值对,需要添加引号
str: 'said: meet you like wind'
给数值等标量添加引号将解析为字符串
str: '99.99'
多行字符串,第二行开始所有行需要缩进至少一个空格,但解析时将被解析为一行字符串,且换行符会被替换为空格
str: aaaa
bbbb
cccc
将被解析为
str: "aaaa bbbb cccc"
单引号与双引号
单引号和双引号均可以用于包裹字符串
- 单引号会原样输出,将转义字符作为普通字符串输出
- 双引号会转义字符串,如将 \n 转义为换行
如:
content: '不会\n换行'
内容输出为:
不会\n换行
content: "进行\n换行"
内容输出为:
进行
换行
区块字符串处理
多行字符串可以使用|保留所有换行符,内容从第二行开始需要缩进至少一个空格,且最后一行的换行符也会保留。
lyrics: |
aaaa
bbbb
cccc
dddd
输出为:
aaaa
bbbb
cccc
dddd
可以使用>去除换行,换行符将被空格代替 (如果某一行是空行,该空行会被额外保留)
lyrics: >
aaaa
bbbb
cccc
dddd
输出为:
aaaa bbbb cccc dddd
可以使用+保留文字块末尾的换行
lyrics: |+
aaaa
bbbb
cccc
dddd
输出为:
aaaa
bbbb
cccc
dddd
可以使用-去除文字块末尾的换行
lyrics: |-
aaaa
bbbb
cccc
dddd
输出为:
aaaa
bbbb
cccc
dddd
在yml文件中配置好字符串格式后,读取到html中时往往需要添加如
white-space: pre-line;等css属性来保留换行符和空格,否则读入的数据可能会被css默认样式去除换行符,从而无法展示原生yml指定的数据格式
在样式流中,无法使用 | > + -
如:以上数据写为样式流
{
lyrics: |-
aaaa
bbbb
cccc
dddd
}
|- 将无法解析,抛出错误
添加HTML标签
yaml允许在值中添加HTML标签,并且标签会被正确地解析为DOM
- tag: web
content: 推荐网站<a href='SHIWIVI'>SHIWIVI</a>
或
- tag: web
content: |-
推荐
网站<a href='SHIWIVI'>SHIWIVI</a>
解析为:
使用时需要注意,插入的HTML标签是否会被其他语言解析为字符串,但yaml本身是可以输出为DOM元素的
锚点与引用
可以使用&定义一个锚点,然后使用*引用锚点
first: &anchor #定义锚点
- aaa
- bbb
second: *anchor #调用锚点
解析为
first: [aaaaa,bbb]
second: [aaaaa,bbb]
如果将多个键值对引用到其他地方,可以使用<<合并数据
first: &data #定义锚点
name: lol
age: 18
hobby:
- sing
- swing
second:
<<: *data #将数据插入此处
address: zzz
解析为:
{
"first": {
"name": "lol",
"age": 18,
"hobby": "sing swimming"
},
"second": {
"name": "lol",
"age": 18,
"hobby": "sing swimming",
"address": "zzz"
}
}
如果多次定义了同名的锚点(重定义),则先定义的锚点会被覆盖
文件格式
一个完整的yml文档以---标识文档开始,以...标识文档结束,该标识非必须,无标识的称为裸文档,文件开头可以添加%YAML来识别文件的YAML版本
%YAML 1.2
# 开头注释,注明文档作者等信息
---
ip: 45.77.77.49
port: 443
...
一个yml文件中可能有多个文档
---
ip: 45.77.77.10
port: 80
...
---
ip: 45.77.77.11
port: 443
...
但如果在一个yml文件中写入多个文档,在某些环境下读入可能报错,需要分割为多个文件
err: YAMLException: expected a single document in the stream, but found more
xml
关于
XML 指可扩展标记语言(EXtensible Markup Language),其文件以.xml为后缀名,主要用于数据存储、数据传输、编写配置文件,语法格式与HTML相似,二者主要差异有:
- XML标签没有被预定义,需要自行定义标签;HTML虽然支持自定义标签,但主要使用预定义标签
- XML主要用于传输和存储数据,HTML主要用于显示数据
- XML语法要求比HTML严格
组成
xml文档基本由以下模块组成
- 标签(元素):文档主要部分,将被解析为XML DOM
- 标签属性:提供关于标签的信息
- 转义字符(实体):XML中预用字符的实体引用
- PCDATA(Parsed Character Data):需要由解析器解析的文本
- CDATA(Character Data):解析器不会解析的文本,内容将视为字符串
语法
xml标签定义规范
xml中的标签需要自定义,标签名需要遵循以下规范
- 可以包含字母、数字和其他符号,包括连字符(-)、下划线(_)和点号(.)
- 不能以数字或者标点符号开始
- 不能以xml、 XML、Xml开始
- 不能包含空格
- 尽量避免使用-和.字符,该类字符在某些XML解析器中中可能会被错误解析
- 避免使用:,该字符用于命名空间
xml基本语法格式
<?xml version="1.0" encoding="utf-8"?>
<web>
<author>SHIWIVI</author>
<license>CC BY-NC</license>
<heading><type>Document</type></heading>
<body>Meet you like the wind</body>
</web>
- 首行进行xml文档声明,version属性定义版本,encoding声明编码格式
- 必须包含一个根标签,标签名自定义
- XML标签大小写敏感
- 所有XML标签都必须闭合,自闭合标签禁止省略/
- XML的属性值须加引号,即便属性值是数值,单双引号均可用
- XML标签禁止交叉嵌套
- XML中多个连续空格也会被保留,HTML中会被合并
- XML 以 LF 存储换行
xml正确嵌套:
<a><b>content</b></a>
严禁交叉嵌套:
<a><b>content</a></b>
空标签
xml允许空标签,即标签中无内容
<element></element>
空标签可以使用自闭合,但禁止省略 /
<element />
转义字符
以下字符在XML中被预定义,使用时需要转义
| 字符 |
转义 |
字符 |
转义 |
| < |
< |
' |
' |
| > |
> |
" |
" |
| & |
& |
|
|
CDATA字符数据
CDATA(character data)指不被xml解析器解析的文本数据,当文档中出现需要大量转义的字符时,可以直接使用CDATA进行引用,这样,这些字符将不会被xml解析器解析,
CDATA 部分由<![CDATA[开始,]]>结束
<root>
<title>标题</title>
<![CDATA[
if(a<b&&b<c){
....
}
]]>
</root>
命名空间
由于xml中标签是自定义的,多个xml文档同时使用可能出现标签名相同导致命名冲突的情况,此时需要使用命名空间来解决标签名冲突问题
<person>
<name>周杰伦</name>
<song>米兰的小铁匠</song>
</person>
<person>
<name>东野圭吾</name>
<writing>解忧杂货店</writing>
</person>
添加前缀
在标签名前添加前缀,来定义不同的标签,从而对不同文档的同名标签加以区分
<j:person>
<j:name>周杰伦</j:name>
<j:song>米兰的小铁匠</j:song>
</j:person>
<h:person>
<h:name>东野圭吾</h:name>
<h:writing>解忧杂货店</h:writing>
</h:person>
添加XML Namespace(xmlns)属性
可以在父标签中添加命名空间属性,以此定义一个命名空间,之后带有相同前缀的子标签都将同属于该命名空间
xmlns:命名前缀="namespaceURI"
<j:person xmlns:j="Jay Chou">
<j:name>周杰伦</j:name>
<j:song>米兰的小铁匠</j:song>
</j:person>
<h:person xmlns:h="Higashino Keigo">
<h:name>东野圭吾</h:name>
<h:writing>解忧杂货店</h:writing>
</h:person>
使用默认的命名空间
当父元素定义默认的命名空间时,我们可以省略子元素中的前缀
xmlns="namespaceURI"
<person xmlns="Jay Chou">
<name>周杰伦</name>
<song>米兰的小铁匠</song>
</person>
<person xmlns="Higashino Keigo">
<name>东野圭吾</name>
<writing>解忧杂货店</writing>
</person>
URI指统一资源标识符(Uniform Resource Identifier),用于标识因特网资源,最常用的 URI 是用来标示因特网域名地址的统一资源定位器(URL),另一个不那么常用的 URI 是统一资源命名(URN),在使用命名空间时,很多xml文档会将名称写为一个链接形式,但该链接仅作为该命名空间的字符串名称,并不是要”链接”到某个地址,不会引入任何资源
DTD验证
DTD(Document Type Definition):文档类型定义,是一套为了进行程序间的数据交换而建立的关于标记符的语法规则
xml的标签是可以自由定义的,每个人写的xml将完全不同。但在一个团队内部,我们往往希望拥有一个统一的文件格式来进行数据交换。此时,我们可以使用DTD来对xml文件进行约束,使用一个标准的 DTD 来交换数据
使用IntelliJ IDEA写xml文件可以即时校验文档内容是否满足DTD验证
标签声明
限制标签内可以写入哪些内容
- <!ELEMENT 标签名 (child1,child2...)> 标签中可以包含的子标签
父标签中按顺序声明其中可以包含的子标签,子标签必须以相同的顺序出现在xml文档中,并且所有子标签也要声明其中可以写入的内容,子标签可以嵌套地拥有子标签
- <!ELEMENT 标签名 (child1,(child2|child3)...)> 标签中可以包含的子标签并可选
- <!ELEMENT 标签名 (#PCDATA)> 标签中仅包含需要解析的字符数据(可以为空)
- <!ELEMENT 标签名 EMPTY> 声明为空标签
- <!ELEMENT 标签名 ANY> 标签中可包含任何内容
- <!ELEMENT 标签名 (#PCDATA|child1|child2|...)*> 标签中可包含PCDATA数据,也可以包含指定子标签必须添加*号
限制子标签的数量
- <!ELEMENT 标签名 (child)> 子标签必须出现一次,且只能出现一次
- <!ELEMENT 标签名 (child+)> 子元素至少出现一次
- <!ELEMENT 标签名 (child*)> 子元素出现零次或多次
- <!ELEMENT 标签名 (child?)> 子元素出现零次或一次
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web [
<!ELEMENT web (author,license,(body|content))>
<!ELEMENT author (#PCDATA)>
<!ELEMENT license (#PCDATA)>
<!ELEMENT heading (#PCDATA|type)*>
<!ELEMENT type (#PCDATA)>
<!ELEMENT body (#PCDATA)>
<!ELEMENT content (#PCDATA)>
]>
<web>
<author>SHIWIVI</author>
<license>lol</license>
<heading><type>Document</type></heading>
<body>Meet you like the wind</body>
</web>
1. !DOCTYPE web - 声明文档的根标签是web
2. !ELEMENT web - 声明web元素必须包含元素:author,license标签,body和content标签可二选一
3. !ELEMENT author - 声明author标签内容为 "#PCDATA"(其余语句同理)
4. !ELEMENT heading - 声明author标签内容为 "#PCDATA"或type标签,由于标签内容受限,需添加*号表示可出现零次或多次
声明实体
<!ENTITY 实体引用名称 "实体实际代表含义"> 定义一个实体
引用实体需要添加&和;
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web [
<!ELEMENT web (author,license)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT license (#PCDATA)>
<!ENTITY sm "shiwivi.com">//实体声明
<!ENTITY license "License CC BY-NC 4.0">
]>
<web>
<author>&sm;</author>//将解析为<author>shiwivi.com</author>
<license>&license;</license>
</web>
属性声明
限制标签可添加的属性以及属性值,语法格式为:
<!ATTLIST 标签名 属性名 属性类型 属性值>
| 属性类型 |
说明 |
属性类型 |
说明 |
| CDATA |
字符 |
NMTOKEN |
该值是有效的XML名称 |
| (v1|v2|..) |
值只能为指定内容(枚举) |
NMTOKENS |
该值是有效XML名称的列表 |
| ID |
值为唯一ID |
ENTITY |
值是一个实体 |
| IDREF |
值是另一个元素的id |
ENTITIES |
值是实体列表 |
| IDREFS |
该值是其他ID的列表 |
xml: |
该值是预定义的xml值 |
| 属性值 |
说明 |
属性值 |
说明 |
| 字符串 |
属性默认值 |
#REQUIRED |
属性是必需的 |
| #IMPLIED |
属性是可选的 |
#FIXED 值 |
属性值是固定的 |
1. 限制为test标签添加的属性只能为code,如果开发者不指定属性值,则默认值为java
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web [
<!ELEMENT web (test)>
<!ELEMENT test EMPTY>
<!ATTLIST test code "java">//属性声明
]>
<web>
<test code="javaScript"/>
</web>
2. 该标签必须添加code属性,并且不指定默认值
<!ATTLIST test code CDATA #REQUIRED>
3. 该标签不强制要求添加code属性,也不指定默认值
<!ATTLIST test code CDATA #IMPLIED>
4. 该标签强制拥有code属性并指定属性值为java,且不允许修改
<!ATTLIST test code CDATA #FIXED "java">
DTD文件引入
内部DTD
如果DTD被包含在xml文件中,需要被包装于DOCTYPE声明中
<!DOCTYPE 根标签 [标签声明内容]>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web [
<!ELEMENT web (author,license)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT license (#PCDATA)>
]>
<web>
.....
</web>
外部私有DTD
可以将DTD声明为一个外部文件,然后引用到xml文档中
<!DOCTYPE 根标签 SYSTEM "DTD的URL">
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web SYSTEM "./check.dtd">
<web>
....
</web>
<!--check.dtd文件-->
<!ELEMENT web (author,license)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT license (#PCDATA)>
外部公用DTD
也可以使用一些标准化组织或权威机构提供的DTD
<!DOCTYPE 根标签 PUBLIC "DTD公共名称" "DTD的URL">
DTD公共名称格式为:"是(否)ISO注册//注册组织//DTD所描述的文件的信息//语言"
XPath
XPath(XML Path Language) 是一门在 XML/HTML 文档中查找和定位节点的查询语言,可以理解成"文档的路径表达式",类似文件系统的路径 /home/user/docs,它由 W3C 标准化,最初为 XSLT 设计,后来被广泛用于各种解析库(lxml、Selenium、Scrapy 等)
路径符号
| 符号 |
含义 |
示例 |
| / |
从根节点开始(绝对路径) |
/html/body |
| // |
从任意位置查找后代(相对路径) |
//div |
| . |
当前节点 |
./span |
| .. |
父节点 |
../div |
| @ |
属性 |
@href |
| * |
通配符(任意节点) |
//* |
//div 所有 div 节点
//div/a 所有 div 下的 a 子节点
//div//a 所有 div 下的所有 a 后代
/html/body/div 绝对路径逐层定位
//a/@href 所有 a 的 href 属性值
//p/text() 所有 p 的文本内容
筛选条件
可以通过谓词[]过滤节点
//div[@id="main"] id 为 main 的 div
//a[@href="/b"] href 为 /b 的 a
//div[@class="box"] class 为 box 的 div
//div[1] 第 1 个 div
//div[last()] 最后一个 div
//div[position()<3] 前 2 个 div
//div[@id and @class] 同时有 id 和 class
//div[@id="a" or @id="b"] id 是 a 或 b
逻辑运算
支持and、or、 取反not()、括号分组、逻辑分组
//div[not(@class)] 没有 class 属性的 div
//div[not(@id="main")] id 不是 main 的 div
//div[not(contains(@class,"item"))] class 不含 item 的 div
//div[@id and not(@class)] 有 id 但没有 class 的 div
//div[@id="a" or @id="b" or @id="c"] id 是 a/b/c 之一
//div[(@id="a" or @id="b") and @class="box"] 括号分组
//div[not(@id="a" or @id="b")] id 既不是 a 也不是 b
通配符
注意:* 只匹配元素,不匹配属性、文本、注释等,匹配全类型内容需要使用 node(),或使用 @*、text()、comment()单独匹配属性、文本、注释
//* 文档中所有元素
//div/* div 的所有直接子元素
//div//* div 的所有后代元素
//div/@* div 的所有属性
//div[@*] 带任意属性的 div
//div[not(@*)] 不带任何属性的 div
//div/node() div 的所有子节点(元素+文本+注释)
//div/text() div 的直接文本节点
//div/comment() div 的注释节点
//*:div 任意命名空间下的 div(XPath 2.0+)
//*[local-name()="div"] 本地名为 div,忽略命名空间(1.0 变通)
//*[namespace-uri()="http\://www.w3.org/1999/xhtml"] 指定命名空间
属性匹配
//div[@id] 有 id 属性的 div
//div[not(@id)] 没有 id 属性的 div
//a[@href="/b" and @target] 同时满足两个属性条件
//div[@class="box"][@id="main"] 多个谓词连写(等价于 and)
//*[@id="main"] 不限标签,任意带 id=main 的元素
//div[contains(@class,"item") and contains(@class,"active")] class 同时含两个词
//div[starts-with(@id,"box-")] id 以 box- 开头
字符串匹配
//div[contains(@class,"item")] class 含 "item"
//a[starts-with(@href,"http")] href 以 http 开头
//div[text()="确定"] 文本正好是"确定"
//div[contains(text(),"关键词")] 文本含"关键词"
//div[ends-with(@id,"box")] ID结尾为box,XPath 2.0 支持
//a[ends-with(@href,".pdf")] href 以 .pdf 结尾(XPath 2.0)
//div[normalize-space(text())="确定"] 忽略首尾空白后比较
//div[string-length(text())>5] 文本长度大于 5
//div[translate(text(),"ABC","abc")="abc"] 大小写转换后比较
//div[matches(text(),"^\d{4}$")] 正则匹配(XPath 2.0+)
//div[replace(text(),"a","b")] 正则替换(XPath 2.0+)
//div[substring(@id, string-length(@id)-2)="box"] 模拟 ends-with(XPath 1.0)
取文本
//a/text() 只取直接文本
//div//text() 取所有后代文本
string(//a) 取第一个 a 的完整文本
normalize-space(//a/text()) 去首尾空格
//a/@href 取属性值(返回属性节点)
string(//a/@href) 取属性值(返回字符串)
concat(//a/text(),"-",//b/text()) 拼接
substring(//a/text(),1,3) 截取前 3 个字符
位置函数
注意 //li[3] 是“每个父节点下第 3 个 li”,(//li)[3] 才是“全文档第 3 个 li”。
//li[1] 第一个 li
//li[last()] 最后一个 li
//li[position()<=3] 前 3 个
//li[last()-1] 倒数第二个
//li[position()>1] 除第一个外的所有 li
//li[position() mod 2 = 1] 奇数位置的 li
//li[position() mod 2 = 0] 偶数位置的 li
(//li)[3] 全文档第 3 个 li(注意括号)
//li[position()=last()] 等价于 last()
数值与存在判断
//div[count(a)=2] 恰好含 2 个 a 子元素的 div
//div[count(*)>0] 至少有一个子元素的 div
//div[count(@*)=2] 恰好有 2 个属性的 div
//div[price>100] 子元素 price 数值大于 100
//div[price>=100 and price<=200] 数值区间
//ul[li] 含 li 子元素的 ul
//ul[not(li)] 不含 li 子元素的 ul
节点测试与类型
//div/node() 所有子节点
//div/text() 文本节点
//div/comment() 注释节点
//div/processing-instruction() 处理指令节点
//div/element() 元素子节点(XPath 2.0+,等价 *)
//div[@id]/@id 属性节点
node() 任意节点
* 任意元素
@* 任意属性
轴(Axes)
除了向下查找,XPath 还能沿"轴"定位父、兄弟等
//a/parent::div a 的父 div
//a/ancestor::div a 的所有祖先 div
//a/following-sibling::a a 之后的兄弟 a
//a/preceding-sibling::a a 之前的兄弟 a
//a/child::span a 的子 span
//a/descendant::span a 的后代 span
//a/following::div a 之后所有 div
//a/preceding::div a 之前所有 div
//a/self::a 自身(判断类型常用)
//a/ancestor-or-self::div a 的祖先及自身中的 div
//a/descendant-or-self::a a 的后代及自身中的 a
//a/parent::* a 的父节点(任意类型)
//a/ancestor::*[1] a 的最近一个祖先元素
//a/following-sibling::*[1] a 的下一个兄弟元素
//a/preceding-sibling::*[1] a 的上一个兄弟元素
//div/attribute::id 等价 //div/@id
//div/child::text() 等价 //div/text()
#轴简写对照
child:: 可省略,如 //div/a 就是 child::a
attribute:: 可写成 @
self:: 可写成 .
parent:: 可写成 ..
descendant:: 可写成 //
其他实用写法
//div[.="文本"] 当前节点的字符串值等于"文本"
//div[contains(.,"关键词")] 当前节点(含后代)文本含关键词
//div[normalize-space(.)="a b"] 忽略空白后的整体文本比较
//div[@id="a"]/following::*[1] id=a 之后第一个任意元素
//div[position()=1][@class] 先取第1个,再判断 class
//div[@class][position()<=2] 先判断有 class,再取前2个
//*[local-name()="div" and @id] 忽略命名空间 + 属性条件
XPath 版本差异
浏览器 document.evaluate()、Python lxml 默认都是 XPath 1.0,2.0+ 写法会直接报错;需要时用 substring + string-length 模拟 ends-with。
ends-with(@id,"box") 仅 2.0+
matches(text(),"\d+") 仅 2.0+
replace(...) / tokenize(...) 仅 2.0+
*:div 仅 2.0+
element() 仅 2.0+