【问题标题】:Parse HTML using shell使用 shell 解析 HTML
【发布时间】:2014-10-11 02:16:08
【问题描述】:

我有一个包含大量数据和我感兴趣的部分的 HTML:

<tr valign=top>
<td><b>Total</b></td>
<td align=right><b>54</b></td>
<td align=right><b>1</b></td>
<td align=right>0 (0/0)</td>
<td align=right><b>0</b></td>
</tr>

我尝试使用awk,现在是:

awk -F "</*b>|</td>" '/<[b]>.*[0-9]/ {print $1, $2, $3 }' "index.html"

但我想要的是:

54
1
0
0

现在我得到:

'<td align=right> 54'
'<td align=right> 1'
'<td align=right> 0'

有什么建议吗?

【问题讨论】:

  • 他是倒数第二个零输出是因为根本没有&lt;b&gt; 标签还是因为&lt;td&gt; 的值为0 (0/0)

标签: bash shell parsing awk


【解决方案1】:

awk 不是 HTML 解析器。为此使用xpath 甚至xsltxmllint 是一个命令行工具,能够执行 XPath 查询,xsltproc 可用于执行 XSL 转换。这两个工具都属于包libxml2-utils

您还可以使用能够解析 HTML 的编程语言

【讨论】:

  • 没有人说它是。不过,绝对可以(轻松)使用 awk 解析其中的单个数据。
  • @dirkk 真的不是,有效地解析整个段可能非常困难(并非不可能),但是对于检索小块数据,因为问题要求使用正则表达式实际上非常容易。每个人都只是跳上不解析 XML/XHTML/HTML 的潮流,甚至一开始就没有理解这个论点,正如你可以从这个“答案”的所有赞成票中看到的那样。查看已接受的答案,该答案清楚地解析了问题中的数据。
  • @Jidder 使用正则表达式正确解析 XML 不可能,不仅仅是困难。评论部分太短,无法证明,但乔姆斯基层次结构是进一步研究的好关键词。这是科学证明的。仅仅因为它在这种情况下有效并不意味着它是正确的。问题是它看起来是正确的,这就是为什么这么多人尝试使用正则表达式进行 XML 解析的原因 - 因为这是不正确的,并且会打开一个痛苦的世界,所以很多人反对它。没错。
  • @dirkk 没有证据证明这一点。当然,您可以在 awk 中编写 HTML 解析器,因为它是图灵完备的。此外,您需要了解从文本文件中提取信息和完全理解和表示 document 是两件不同的事情。但是,嘿,我仍然会使用现成的解析器,而不是使用awk.. 一次又一次地编写自定义解析器
  • @hek2mgl 啊,我明白了。如果 awk 是完整的,那么你实际上是正确的(我现在不太了解 awk,我认为它仅限于常规语言)。所以,总结一下:不要使用正则表达式来解析 XML。您可以使用 awk 来解析 XML,但不应该(因为答案和 cmets 中提到的原因)。
【解决方案2】:
awk  -F '[<>]' '/<td / { gsub(/<b>/, ""); sub(/ .*/, "", $3); print $3 } ' file

输出:

54
1
0
0

另一个:

awk  -F '[<>]' '
/<td><b>Total<\/b><\/td>/ {
    while (getline > 0 && /<td /) {
        gsub(/<b>/, ""); sub(/ .*/, "", $3)
        print $3
    }
    exit
}' file

【讨论】:

  • @Lenny 确保您在使用getline 之前阅读并完全理解awk.info/?tip/getline 讨论的所有注意事项。在这种情况下,根本不需要getline 循环,一个简单的标志就可以做到f{ subs(..); print; if (!/&lt;td /) exit} /..Total/{f=1}
  • @EdMorton 你必须提前移动if (!/&lt;td /) exit。标记实际上也是一个很好的方法,但它更容易想出一些有时没有的东西。当您已经尝试使您的代码更加流畅或高效时,就会完成标记。再说一次关于getlinegetline &gt; 0 是完全安全的,如果你正确阅读手册就足够安全了。很清楚不同的语法在功能上有何不同。唯一需要真正注意的是 getline 命令在成功时返回 1,在文件结束时返回 0,在错误时返回 -1。
  • 是的,!/&lt;td / 上的测试将首先出现。考虑这两种方法,现在添加一个要求,即您需要将从第 1 行到 /&lt;td / 行的每一行打印到名为“foo”的文件中以进行调试。请注意,如果您使用 getline 方法,您需要将您的 print &gt; "foo" 放在 2 个位置,而使用仅让 awk 循环执行其操作的正常方法,您只需将 print &gt; "foo" 放在一个位置。在不需要时避免 getline 不仅是为了编写安全代码,也是为了编写易于维护和扩展的代码。
  • @EdMorton 我不同意它被轻易扩展。请参阅我很久以前编写的代码,其中标志(通过 getline)几乎不能应用:sourceforge.net/p/playshell/code/ci/master/tree/loader/…。我最后一次更新只是为了确保 getline 返回 1 而不仅仅是非零。×评论只能编辑 5 分钟×评论只能编辑 5 分钟×评论只能编辑 5 分钟
  • @konsolebox 我只是给出了一个简单、常见的非 getline 代码更容易扩展的例子。无论如何,我的评论是针对 OP 的,现在他知道适当的 getline 使用的利弊和不同的意见。我查看了您的编译器代码,如果没有 getline,它可以编写得更加健壮和简洁。开个玩笑——当然我不会阅读数百行 awk 代码并试图弄清楚它的作用以及没有 getline 或对其进行任何其他类型的分析时会是什么样子。
【解决方案3】:
$ awk -F'<td[^>]*>(<b>)?|(</?b>)?</td>' '$2~/[0-9]/{print $2+0}' file
54
1
0
0

【讨论】:

  • 很好的答案附有代码示例,并为未来的读者提供了解释。虽然问这个问题的人可能理解你的答案,但解释你是如何得出这个问题的将帮助无数其他人。
  • 这很好,但平均需要大约 15 秒才能得出一个答案,并需要几分钟来记录它,所以我有时间为每个问题做前者而不是后者,尤其是那些恕我直言,不言而喻。如果有人有问题,我很乐意回答。
【解决方案4】:

你真的应该使用一些真正的 HTML 解析器来完成这项工作,比如:

perl -Mojo -0777 -nlE 'say [split(/\s/, $_->all_text)]->[0] for x($_)->find("td[align=right]")->each'

打印:

54
1
0
0

但为此你需要有 perl,并安装 Mojolicious 包。

(很容易安装:)

curl -L get.mojolicio.us | sh

【讨论】:

    【解决方案5】:

    BSD/GNUgrep/ripgrep

    对于简单的提取,可以使用grep,例如:

    • 您使用grep 的示例:

      $ egrep -o "[0-9][^<]\?\+" file.html
      54
      1
      0 (0/0)
      0
      

      并使用ripgrep:

      $ rg -o ">([^>]+)<" -r '$1' <file.html | tail +2
      54
      1
      0 (0/0)
      0
      
    • 提取H1的外部html:

      $ curl -s http://example.com/ | egrep -o '<h1>.*</h1>'
      <h1>Example Domain</h1>
      

    其他例子:

    • 提取身体:

      $ curl -s http://example.com/ | xargs | egrep -o '<body>.*</body>'
      <body> <div> <h1>Example Domain</h1> ...
      

      你也可以用tr '\n' ' '代替xargs

    • 多个标签见:Text between two tags

    如果您正在处理大型数据集,请考虑使用具有相似语法的ripgrep,但由于它是用Rust 编写的,因此速度更快。

    【讨论】:

      【解决方案6】:

      HTML-XML-utils

      您可以使用htmlutils 来解析格式良好的 HTML/XML 文件。该软件包包括许多用于提取或修改数据的二进制工具。例如:

      $ curl -s http://example.com/ | hxselect title
      <title>Example Domain</title>
      

      以下是提供数据的示例:

      $ hxselect -c -s "\n" "td[align=right]" <file.html
      <b>54</b>
      <b>1</b>
      0 (0/0)
      <b>0</b>
      

      这是去掉&lt;b&gt;标签的最后一个例子:

      $ hxselect -c -s "\n" "td[align=right]" <file.html | sed "s/<[^>]\+>//g"
      54
      1
      0 (0/0)
      0
      

      如需更多示例,请查看

      【讨论】:

        【解决方案7】:

        我最近被指向pup,在我所做的有限测试中,它对无效的 HTML 和标签汤的宽容度要大得多。

        cat <<'EOF' | pup -c 'td + td text{}'
        <table>
        <tr valign=top>
        <td><b>Total</b></td>
        <td align=right><b>54</b></td>
        <td align=right><b>1</b></td>
        <td align=right>0 (0/0)</td>
        <td align=right><b>0</b></td>
        </tr>
        </table>
        EOF
        

        打印:

        54
        1
        0 (0/0)
        0
        

        【讨论】:

          【解决方案8】:

          使用 ,一个真正的 HTML 解析器和 XPath:

          $ xidel -s "input.html" -e '//td[@align="right"]'
          54
          1
          0 (0/0)
          0
          
          $ xidel -s "input.html" -e '//td[@align="right"]/tokenize(.)[1]'
          # or
          $ xidel -s "input.html" -e '//td[@align="right"]/extract(.,"\d+")'
          54
          1
          0
          0
          

          【讨论】:

            【解决方案9】:

            ex/vim

            对于更高级的解析,您可以使用就地编辑器,例如 ex/vi,您可以在其中 jump between matching HTML tags,选择/删除内部/外部标签,并就地编辑内容。

            命令如下:

            $ ex +"%s/^[^>].*>\([^<]\+\)<.*/\1/g" +"g/[a-zA-Z]/d" +%p -scq! file.html
            54
            1
            0 (0/0)
            0
            

            这是命令的工作方式:

            • 使用 ex 就地编辑器将所有行 (%) 替换为:ex +"%s/pattern/replace/g"

              替换模式由三部分组成:

              • 选择从行首到&gt; (^[^&gt;].*&gt;) 进行删除,就在第二部分之前。
              • 选择我们的主要部分直到&lt; (([^&lt;]+))。
              • 选择 &lt; 之后的所有其他内容以删除 (&lt;.*)。
              • 我们将整个匹配行替换为 \1,它指的是括号内的模式 (())。
            • 替换后,我们使用global: g/[a-zA-Z]/d 删除所有字母数字行。

            • 最后,通过+%p在屏幕上打印当前缓冲区。
            • 然后静默 (-s) 退出而不保存 (-c "q!"),或保存到文件中 (-c "wq")。

            测试后,要就地替换文件,请将-scq! 更改为-scwq


            这是另一个简单的例子,它从标题中删除样式标签并打印解析的输出:

            $ curl -s http://example.com/ | ex -s +'/<style.*/norm nvatd' +%p -cq! /dev/stdin
            

            但是,使用正则表达式解析 html 是 not advised,因此对于长期方法,您应该使用适当的语言(例如 Python, perlPHP DOM)。


            另见:

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-11-15
              • 2011-04-03
              • 2021-07-12
              • 2012-07-27
              相关资源
              最近更新 更多