XPath 与 JSONPath 测试
文本所有处理都在浏览器本地完成,数据不上传服务器
把内容粘进来,写一条路径,看它选中了什么。常见的几种用法:`//a/@href` 抓全部链接、`//img/@src` 抓全部图片地址、`//h2` 取出所有小标题,或者 `$.store.book[*].title` 从接口返回里取出全部书名、`$..id` 找出所有层级的 id。 为什么两套语法共用一个页面:它们的形状完全一样 —— 一条路径 + 一份文档 → 一组结果。分开做的话,你得先判断「我手里这份是 JSON 还是 XML」再去挑工具,而那个判断在你把内容粘进来之前是多余的。上面选一下语法就行。 两套都是明确的子集。 支持的写法写在下面;不支持的写法会明确报「不支持」,而不是给出一个空结果 —— 因为「语法没被实现」与「真的没匹配到」在屏幕上一模一样,前者会让你以为自己的选择器写错了。 JSONPath 没有事实标准,这一条要提前知道:`$..book[?(@.price<10)]` 在 JavaScript、Python、Java 的各个 JSONPath 库里结果并不一致(切片边界、递归下降是否包含自身,各家做法不同)。RFC 9535 到 2024 年才定下来。这里按 RFC 9535 实现,结果适合本机调试,贴进代码前先确认那边库的行为。 为什么 XPath 不做完整版:完整实现要处理 13 条轴、命名空间、变量与一大票函数,是几千行;而浏览器自带的 `document.evaluate` 虽然现成,却要求算法碰 DOM —— 本站所有工具算法都是纯函数,碰了就没法在 Node 里测。所以这里在站内的轻量 HTML 解析器上实现最常用的那一层。 解析是容错的:用户粘进来的 HTML 常常缺闭合标签、少引号,这些都不该让整次查询失败(解析器与「HTML 转 Markdown」共用同一个)。
功能
- 一套界面两种语法:XPath 走 HTML / XML,JSONPath 走 JSON
- XPath 轴:子节点 `/` 与后代 `//`(抓取场景里用得最多的两条)
- XPath 节点测试:标签名、`*`、`@属性名`、`text()`,标签名不区分大小写
- XPath 谓词:`[1]`、`[last()]`、`[@a]`、`[@a="v"]`、`[contains(@a,"v")]`、`[starts-with(@a,"v")]` 等
- JSONPath:`.key`、`["key"]`、`[*]`、`..` 递归下降、`[0]` 下标与并集、`[1:3]` 切片
- JSONPath 过滤器:`[?(@.price < 100)]`,支持 `==` `!=` `<` `>` 比较、`&&` `||` 与括号
- JSONPath 可额外输出每个结果在文档里的位置(`$` 开头的路径),方便写代码时直接引用
- 四种输出:纯文本/值、HTML 源码、所在位置、只给数量
- 位置谓词的语义与 XPath 一致:`//a[1]` 是「每个父节点下的第一个 a」
- 每条结果带 1 起的序号并逐行可复制;复制对象时给格式化过的 JSON
- 没匹配到时给一句说明,而不是一片空白
- 不支持的写法明确报错,不会与「没匹配到」混淆
- 畸形的 HTML 也能解析,不会因为缺个闭合标签就整次失败
使用方法
- 选语法:XPath(HTML / XML)还是 JSONPath(JSON)
- 粘贴源码或 JSON,写一条表达式,例如 //a/@href 或 $.store.book[*].title
- 选输出形式:纯文本/值、HTML 源码、所在位置,还是只给数量
- 结果实时更新,点某一行的复制按钮取走一项
常见问题
- 支持哪些 XPath 写法?
- 轴只有子节点 `/` 与后代 `//`;节点测试有标签名、`*`、`@属性`、`text()`;谓词有 `[1]`、`[last()]`、`[@a]`、`[@a="v"]`、`[text()="v"]`、`[contains(@a,"v")]`、`[starts-with(@a,"v")]`,以及两条用 `and` 连接;函数只有外层 `count(...)`。不在这个范围内的写法会明确报「不支持」 —— 比如 `position()>1`、`!=` 比较、`ancestor::` 这类轴、以及命名空间。报错而不是给空结果,是因为这两种情况的屏幕表现完全一样,而后者会让你去反复检查一个其实没错的选择器。
- `//a[1]` 为什么选中了好几个?
- 因为它选的是「每个父节点下的第一个 `a`」,不是「整篇里的第一个 `a`」—— 这是 XPath 的明确规定(`//a` 等价于「任意节点的 a 子节点」,位置谓词在每一组里各算各的)。想要整篇第一个,用 `(//a)[1]`(本工具不支持这种括号写法)或 `//a[1]` 配合只取第一行结果。这个区别是 XPath 最常被误解的地方,所以工具里按标准语义实现,没有为了方便而改掉它。
- 能抓不到链接时为什么是空的?
- 先确认三件事:属性名有没有写错(`href` 而不是 `link`);链接是不是写在别的属性上(有些站用 `data-href` 或 `onclick`);页面内容是不是脚本渲染出来的 —— 工具解析的是你粘贴进来的源码,如果你的源码来自浏览器的「查看源代码」而内容是 JS 渲染的,那份源码里本来就没有这些元素,得从开发者工具的 Elements 面板里复制渲染后的 HTML。工具不会去请求那个网址,这一条是刻意的。
- JSONPath 支持哪些写法?
- 根 `$`(可省)、子节点 `.key` 与 `['key']`、通配 `[*]` 与 `.*`、递归下降 `..`、下标与并集 `[0]`、`[0,2]`、切片 `[1:3]`、以及过滤表达式 `[?(@.price < 100)]`(支持存在性判断、`==` `!=` `<` `<=` `>` `>=` 比较、`&&` `||`、括号,以及取反 `!`)。不支持的会明确报错:函数扩展(`length()`、`count()`、`match()`、`search()`、`value()`)、正则、过滤器里以 `$` 开头的绝对路径。
- 为什么我的 JSONPath 结果和代码里跑出来的不一样?
- 因为 JSONPath 至今没有统一的标准。RFC 9535 到 2024 年才发布,此前 JavaScript 的 `jsonpath`、Python 的 `jsonpath-ng`、Java 的 Jayway 各写各的,分歧集中在三处:切片边界(`:3` 含不含第 3 项)、递归下降要不要包含自身(`$..a` 能不能选到最顶层那个 `a`)、以及过滤器的比较是否允许跨类型(`@.price < "10"` 拿字符串比数字)。这里一律按 RFC 9535:切片右开、递归下降包含自身、跨类型比较返回 false。所以这个工具适合在本机调试时确认路径写对了,贴进代码前先拿真实数据在目标库里验一遍。
- 「所在位置」输出是干什么用的?
- 它给每个匹配结果标出在文档里的路径,例如 `$.store.book[2].title`。写代码时常常是反过来 —— 你已经知道某个值长什么样,但要在结构里找到它的位置,好写死一条取值路径。直接输出值只能告诉你「有这几个」,输出位置才能告诉你「它们在哪」。注意路径里带特殊字符的键名会写成 `['a.b']` 的形式,这样它和 `.a.b` 的区别才保留下来。
相关工具
HTML 格式化
格式化在线 HTML 格式化与压缩:按标签层级缩进,文本与行内标签保持同一行,script 与 pre 内容原样保留。代码不出浏览器,不上传服务器。
JSON 格式化
格式化在线 JSON 格式化与校验工具:树形折叠浏览、搜索键值、逐节点改值与删除、语法错误定位到具体行列,输入框带行号与高亮。全部在浏览器本地完成。
HTML 转 Markdown
文本把 HTML 转成 Markdown:标题、列表、表格、链接、图片、代码块都按 GitHub 风格输出,正文里的 Markdown 语法字符会自动转义。
正则测试
文本浏览器里调试正则:匹配实时高亮、捕获组、替换预览,并挡住灾难性回溯;调好可生成 JavaScript、Python、Java、Go、PHP 代码,反向引用按语言转换。
文本对比
文本把两份文本逐行对比,高亮标出新增、删除与未改动的行,改动按最小差异对齐,而不是整段标成变了。全部在浏览器本地完成,内容不上传。
字数统计
文本中英混排的文本到底有多长?中文按字、英文按词分开统计,给出字符数、行数、字节数、阅读与朗读时长,以及会被切成几条短信。全部在本机完成。