【问题标题】:How to parse html in bash?如何在bash中解析html?
【发布时间】:2016-09-15 11:21:22
【问题描述】:

据我所知,在 Bash 中解析 html 通常被认为是个坏主意。但是一个人永远不会在骑自行车的过程中不摔倒几次。

因此,我尝试使用 Bash 从 html 网页中提取一些数据。我试图获得的相关部分是data-nick="someguy99"这是一个用户名,然后消息"Hello. This is the data I wish to obtain."显示在正下方的行上。

<body>
 <div id="main">
  <div class="content">
   <div class="block">
    <div class="section">
     <div class="chat-holder">             
      <div class="chat-box">  
       <div class="chat-list">
        <div id="0" class="text" style="color: rgb(73, 73, 73);">
         <span class="username messagelabel" data-nick="someguy99">someguy99:</span>  
         "Hello. This is the data I wish to obtain."

使用wget 我无法遍历"chat-list"。我尝试将输出通过管道传输到其他程序wget -O - http://website.url | lynx -source -dump 但没有任何效果。总是相同的输出。例如:

wget --quiet -F -O - http://website.url/example | \
lynx -dump -source -stdin | grep 'chat-list'

结果……

        var img = $('.chat-list img[title="' + slug + '"]');

这与使用网络浏览器时在文档树中看到的输出不同。并且用grep 'data-nick' 替换grep 'chat-list' 根本不会返回匹配的模式。

我做错了什么?如何更深入地解析以获得我要寻找的数据?

我的大脑现在感觉有点炸,所以如果我遗漏了任何相关信息,请告诉我,我会提供更多详细信息。

  • Mac OS X 10.11.5
  • GNU bash 4.3.42

谢谢。

【问题讨论】:

  • 您说“总是相同的输出”,但您忽略了该输出可能是什么。 “它不起作用”从不是一个充分的问题陈述。
  • 虽然实际上如果那是真实数据(到底是什么 = $0 在那里做什么?)那么很明显 html 解析器会遇到类属性中缺少双引号的问题(sectiontext,在您的摘录中)。这也会使使用浏览器阅读页面变得很棘手。你能让页面的所有者修复他们的标记吗?
  • 您是对的,对此我深表歉意。我已经编辑了问题以显示我得到的输出。那 = $0 不应该在那里。我的错。缺少的引号只是我的输入错误。
  • 您能否澄清您问题中的文字来自何处?它是网络服务器返回的实际文本,还是您通过从实际网页检查 DOM 获得的?使用现代浏览器的“inspect”功能和使用“view source”是有很大区别的。
  • ...如果您确实想 grep 网页的来源,则不需要 lynx ;您可以将wget 直接通过管道传输到grep。所以我的猜测是,您正在尝试获取由浏览器中运行的 javascript 组装的 DOM。这与解析完全不同。

标签: html bash


【解决方案1】:

遗憾的是,您在 Safari 的 Web Inspector 中看到的不是 HTML 页面的文本。它是浏览器解释页面的结果,可能包括执行嵌入式 Javascript 程序和从其他页面读取的数据。此外,Web Inspector 向您展示了一个完全嵌套的树结构,即使原始 HTML 可能缺少关闭标签甚至一些开始标签:一个典型的例子是您将始终在 &lt;table&gt; 中看到 &lt;tbody&gt; 元素元素,即使 HTML 页面不包含带有 tbody 标记的单个元素。

所以wgetwget | lynx -source 向您显示相同的数据并不奇怪,通过grep 的管道找不到您在Web Inspector 中看到的行。该行根本不存在于网页的源代码中;它是 Web Inspector 解释组装页面对象的内部表示的结果。

据我所知,没有一个常见的文本模式浏览器实现 Javascript,尽管有一些实验性支持。此外,(同样,据我所知,对于常见的文本模式浏览器),不支持转储 DOM(“域对象模型”;即 Web Inspector 显示的实际对象树)。文本模式浏览器倾向于让您选择 -dump 以将呈现的输出显示为文本或 -source 以显示原始 HTML 文件。

在我看来,处理客户端生成的页面(即在本地 Web 浏览器加载页面期间组装的页面)的最佳方式是使用 headless browser,例如 PhantomJS(有维基百科文章中列出的其他人,但我只有 PhantomJS 的经验)。或者,您可以尝试使用浏览器自动化工具,例如 Selenium,它可以让您编写浏览器脚本。或者,在 Mac OS X 上,您可以使用 Applescript 编写 Safari 浏览器的脚本。 (我不再有 Mac,但 Safari Applescript 字典显示您可以通过 open 一个 URL 和 do javascript 在该页面中执行 javascript。)

不幸的是,这些技术都没有详细记录(恕我直言),并且存在的文档往往侧重于单元测试网页(这是一个非常重要的用例,但不一定与数据抓取相关)。我发现 PhantomJS 开始使用起来非常烦人,直到我发现您尝试在网页内执行的 javascript 中的任何语法错误都会导致 PhantomJS 简单地挂起,而不会产生任何错误消息。因此,在 PhantomJS 中尝试之前,使用其他一些 javascript 解释器(例如 Node)对脚本进行语法检查非常重要。

在网页中运行的 javascript 程序中,您通常可以使用JQuery 进行导航,这使得根据属性值(如您的问题)查找内容非常容易。对于页面尚未导入 JQuery 的情况,PhantomJS 为您提供了一种将 JQuery 注入页面的机制,但我从未使用过。

祝你的项目好运。

【讨论】:

  • 我想这可以解释为什么 Chrome 中的树看起来与 Safari 中的不同。好吧,我似乎有很多阅读要做。我实际上已经安装了 JQuery,但老实说,我对它的了解很少。在这一点上,我对 Javascript 一无所知,但我有一种即将改变的感觉。在您回答的最后一段中 - 您是说 JQuery 可用于查找用户定义的值,即使这些值在 API 中没有特别提及?我什至不知道这个问题是否有意义。感谢您给出如此详尽而详尽的答复。
  • @user556068:我担心一个完整的答案不适合 SO 帖子,别介意评论。也许这就是我一直在寻找的书籍项目 :) 最好的想法是 Javascript 程序在网页内部运行,因为每个网页实际上都是一个沙箱,并且与外部世界的交互有限(除了显示结果)。因此,在本地机器上“拥有 JQuery”毫无意义。如果一个网页需要 JQuery,它会从网上获取。在网页中运行的脚本中,JQuery 使搜索变得容易。例如...
  • ... 要找到属性为data-nick 且值为someguy99&lt;span&gt;,您可以使用$("span[data-nick='someguy99']")。然而,棘手的一点是获取该元素之后的文本,因为它没有包含在任何东西中。您可以通过附加.parent().text() 来获取跨度父元素的文本,但这将包括跨度本身内部的文本。也许这就足够了 :) 获得文本后,您需要将其打印出来。您不能使用 console.log() 因为这在网页中不起作用。 PhantomJS 有一个你可以使用的控制台代理,不过...
  • .... 这涉及在外部 phantomjs 脚本中安装控制台日志处理程序,因此一旦设置好,您就可以执行console.log($("span[data-nick='someguy99']").parent().text()。由于 jquery 的工作方式,这将适用于文档中的每个匹配跨度......我想我会停在那里。正如我所说,祝你好运。
  • 是的,我认为一本书是个好主意。我假设(看起来是错误的)JQuery 与我不久前从自制软件下载的jq 程序相同.. 到目前为止,我的大部分假设似乎都是错误的。感谢您抽出时间与我交谈。你给了我很多宝贵的信息。如果你真的写了一本书,我会在它出版时排在第一位。在那之前,我想我的一部分日子会在房间的角落里度过,蜷缩成胎儿的姿势。再次感谢您。
【解决方案2】:

我获取了您的 HTML 片段并将其写入 tmp 文件。 然后,我使用 Rubular.com 根据您的要求构建了一个正则表达式, 然后我在它上面运行了 grep -P 并且结果很接近:

#> grep -Pzo 'data-nick="[^>](.+|\n)[^"|\n]+"' /tmp/test.html
data-nick="someguy99"

但是,您需要某种覆盖多行的方法,我认为 |\n 会这样做,但不完全是 - 抱歉!我正在使用 Ubuntu 14.04 并将 grep 切换到 PCRE(非 POSIX 模式),因此您可能需要指定您的 O/S 和 bash 版本,因为我相信在不同的系统上有不同版本的 grep。

【讨论】:

  • 谢谢。我相信如果我的数据是我认为的那样,这将起作用。事实证明不是。但感谢您的努力。
猜你喜欢
  • 2010-10-27
  • 2010-09-08
  • 2010-12-06
  • 2011-05-16
  • 2013-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-22
相关资源
最近更新 更多