当前位置:首页 > 问答 > 正文

用jq怎么快找到p标签里头的数据库信息,简单又实用的方法分享

那天我在网上看到一个程序员提的问题,他说他手里有个巨大的JSON文件,是从某个网页抓下来的数据,里面乱七八糟什么都有,他需要从这里面把藏在层层嵌套里的、特别是HTML的<p>标签中的一些关键数据(比如用户评论、产品描述这类他称为“数据库信息”的文本)给提取出来,文件太大,用眼睛看会瞎,用一般的文本编辑器打开都卡死,下面有人回帖说:“用jq啊,神器!” 然后贴了条命令,看起来挺复杂,把提问的人都看懵了。

我当时就想,其实用jq干这个事儿,完全可以很直接,没必要一开始就上那些复杂的表达式,正好最近我也在处理类似的问题,就把我觉着最简单实用的几步分享一下,我的原则是,能一步到位就别分两步,能用简单语法就别写长脚本。

第一步:先看清敌人长啥样

这是最最要紧的一步,比什么都重要,你不能闭着眼睛乱摸,你的JSON文件结构是啥样的?那个你想要的、藏在p标签里的信息,它在第几层?你得先看一眼。

假设你的文件叫 data.json,打开终端,先来个最简单的:

jq '.' data.json

如果文件太大,输出会刷屏,那就用更偷懒的方法,只看第一个元素或者前几个元素,摸个样子出来:

jq '.[0]' data.json   # 看第一个元素
# 或者
jq '.[:2]' data.json # 看前两个元素

你这么一看,发现数据结构大概是这样的:

{
  "pages": [
    {
      "content": "<div><p>我们需要提取这里的文字</p><span>这个不需要</span></div>",
      "title": "某个标题"
    },
    ...
  ]
}

哦,你一下就明白了,你要的东西在 .pages[].content 这个字段里,而这个字段的值是一大段HTML字符串,你的目标就是这段HTML字符串里面的<p>标签里的文本。

第二步:把包含HTML的字段捞出来

现在你知道目标在 .pages[].content 里了,那就先把它单独拎出来看看:

jq '.pages[].content' data.json

这个命令会把所有pages数组里每一个元素的content字段值都打印出来,这时候你看到的就是一行行被引号包围的HTML字符串。

第三步(核心):对付HTML字符串,用containsmatch

好了,关键来了,我们现在有一堆HTML字符串,怎么从里面找出包含<p>标签的那些,并且把里面的文本弄出来呢?这里有两个简单实用的方法。

方法A:快速过滤——我只关心有没有

用jq怎么快找到p标签里头的数据库信息,简单又实用的方法分享

你并不需要立刻看到p标签里具体的文字,你只是想快速知道,哪些条目它的content里包含了p标签,这时候,jqcontains函数就很好用。

jq '.pages[] | select(.content | contains("<p>"))' data.json

这个命令的意思是:遍历pages里的每个元素,然后筛选(select)出那些content字段的值里包含(contains)字符串"<p>"的元素,这样,输出结果就是完整的JSON对象,但只包含那些有p标签的条目,你可以在这个基础上再进一步处理。

方法B:直接抓取——我要文本内容

如果你想更近一步,直接把p标签里面的文本抠出来,那就得用正则表达式了。jq提供了match函数,虽然正则表达式听起来有点专业,但为了这个需求,我们只需要一个很简单的模式。

我们知道,一个简单的p标签长得像这样:<p>这里是文本</p>,我们想捕获的是这里是文本这部分,可以用这个命令:

jq '.pages[].content | match("<p>(.*?)</p>") | .captures[0].string' data.json

我来解释一下这个“咒语”:

  • .pages[].content:先把所有HTML字符串流出来。
  • | match("<p>(.*?)</p>"):对每个字符串,用正则表达式<p>(.*?)</p>去匹配,括号表示一个非贪婪模式的捕获组,目的就是匹配<p></p>之间的所有内容。
  • | .captures[0].stringmatch函数匹配成功后会返回一个对象,其中.captures数组里存放着捕获组的内容,我们取第一个捕获组([0]),然后它的.string属性就是我们想要的纯文本了。

这里有个大坑! 这是我从实际吃亏中总结的:HTML可能很乱,比如可能有多个p标签,或者p标签有大写的<P>,或者标签里有属性像<p class="style">,上面那个简单的正则可能就失灵了。

更健壮点的实用方法:

用jq怎么快找到p标签里头的数据库信息,简单又实用的方法分享

  1. 忽略大小写:在match里加个"i"标志。

    jq '.pages[].content | match("<p>(.*?)</p>"; "i") | .captures[0].string' data.json
  2. 处理有属性的标签:把正则改得更宽松点,匹配<p开始,后面跟着任意字符直到>的。

    jq '.pages[].content | match("<p[^>]*>(.*?)</p>"; "i") | .captures[0].string' data.json

    这个[^>]*意思是“匹配任意不是>的字符,重复0次或多次”,这样就能跳过标签内的所有属性。

  3. 处理多个p标签:如果你想提取所有的p标签内容,而不仅仅是第一个,就用scan函数,它和match类似,但会返回所有匹配项。

    jq '.pages[].content | scan("<p[^>]*>(.*?)</p>"; "i") | .captures[0].string' data.json

第四步:输出整理

默认情况下,jq输出的字符串会带引号,如果你就是想得到纯文本,方便后续用其他命令(比如grep, awk)处理,可以加个-r选项,表示输出原始字符串。

jq -r '.pages[].content | scan("<p[^>]*>(.*?)</p>"; "i") | .captures[0].string' data.json

这样,终端里输出的就是一行行干净的文本了,直接可以重定向到文件里保存。

最后总结一下这个简单流程:

  1. jq '.' data.jsonjq '.[0]' data.json -> 看结构,找到目标字段路径。
  2. jq '你的路径' data.json -> 确认目标字段的值。
  3. 选择你的武器
    • 只想过滤条目:用 select( .字段 | contains("p") )
    • 想直接提取文本:用 matchscan 配合正则表达式,match("<p[^>]*>(.*?)</p>"; "i")
  4. 优化输出:用 -r 参数得到纯文本结果。

这个方法基本上能解决八成从JSON的HTML字段里抓p的需求了,如果HTML特别复杂,或者你需要解析整个DOM树,那可能就得考虑先用专门的HTML解析器处理后再转换成JSON,但对于快速的、命令行下的日志分析或者数据提取,这个jq组合技真的简单又实用。