【问题标题】:Performant parsing of HTML pages with Node.js and XPath使用 Node.js 和 XPath 对 HTML 页面进行高性能解析
【发布时间】:2014-11-03 09:23:05
【问题描述】:

我正在使用 Node.js 进行一些网络抓取。我想使用 XPath,因为我可以使用多种 GUI 半自动生成它。问题是我找不到有效的方法。

  1. jsdom 非常慢。它在一分钟左右的时间内解析了 500KiB 的文件,CPU 负载满载,内存占用很大。
  2. 用于 HTML 解析的流行库(例如 cheerio)既不支持 XPath,也不公开符合 W3C 的 DOM。
  3. 显然,有效的 HTML 解析是在 WebKit 中实现的,因此使用 phantomcasper 将是一种选择,但它们需要以特殊方式运行,而不仅仅是 node <script>。我不能依赖这种变化隐含的风险。例如,很难找到如何运行 node-inspectorphantom
  4. Spooky 是一个选项,但它是 buggy enough,所以它根本没有在我的机器上运行。

那么用 XPath 解析 HTML 页面的正确方法是什么?

【问题讨论】:

标签: javascript html node.js xpath phantomjs


【解决方案1】:

我刚刚开始使用npm install htmlstrip-native,它使用native implementation 来解析和提取相关的html 部分。它声称比纯 js 实现快 50 倍(我尚未验证该说法)。

根据您的需要,您可以直接使用 html-strip,或者提升代码和绑定以使您拥有在 htmlstrip-native 内部使用的 C++

如果你想使用 xpath,那么使用这里已经可用的包装器; https://www.npmjs.org/package/xpath

【讨论】:

  • 0。您的链接已损坏。 1. 那个库正在解析实体,从它的名字就很明显了。 2. 您的回答中甚至没有提到 XPath。
  • 修复了断开的链接;添加了指向 xpath 实现的链接,您自己没有找到/使用它的任何原因?
  • xpath 库必须在某种 DOM 上运行。解析 HTML 的唯一解决方案是 jsdom,它非常慢。这是上面列表中的第一项。你读过这个问题吗?
  • 如果您阅读 npm xpath 文档,您会看到他建议使用 xmldom。
  • 那么 xmldom 应该如何解析 HTML?
【解决方案2】:

Libxmljs 目前是最快的实现(like a benchmark),因为它只绑定到支持 XPath 1.0 查询的LibXML C 库:

var libxmljs = require("libxmljs");
var xmlDoc = libxmljs.parseXml(xml);
// xpath queries
var gchild = xmlDoc.get('//grandchild');

但是,您需要先清理 HTML 并将其转换为正确的 XML。为此,您可以使用 HTMLTidy 命令行实用程序 (tidy -q -asxml input.html),或者如果您希望它仅保留节点,则可以使用类似 xmlserializer 的方法。

【讨论】:

    【解决方案3】:

    您可以通过几个步骤来完成。

    1. 使用parse5 解析HTML。不好的部分是结果不是 DOM。虽然它足够快并且符合 W3C 标准。
    2. 使用xmlserializer 将其序列化为XHTML,接受parse5 的类DOM 结构作为输入。
    3. 使用xmldom 再次解析XHTML。现在你终于有了那个 DOM。
    4. xpath 库建立在xmldom 之上,允许您运行 XPath 查询。请注意,XHTML 有自己的命名空间,//a 之类的查询不起作用。

    你终于得到了这样的东西。

    const fs = require('mz/fs');
    const xpath = require('xpath');
    const parse5 = require('parse5');
    const xmlser = require('xmlserializer');
    const dom = require('xmldom').DOMParser;
    
    (async () => {
        const html = await fs.readFile('./test.htm');
        const document = parse5.parse(html.toString());
        const xhtml = xmlser.serializeToString(document);
        const doc = new dom().parseFromString(xhtml);
        const select = xpath.useNamespaces({"x": "http://www.w3.org/1999/xhtml"});
        const nodes = select("//x:a/@href", doc);
        console.log(nodes);
    })();
    

    请注意,您必须在查询的每个 HTML 元素前添加 x: 前缀,例如,匹配 div 中的 a 您需要:

    //x:div/x:a
    

    【讨论】:

    • 谢谢,完美运行。除了我需要用var document = parse5.parse(html.toString()); 替换var document = parser.parse(html.toString()); 并去掉var parser = new parse5.Parser(); 行(使用parse5 版本2.0.2)
    • 您正在将所有内容都加载到内存中(整个 DOM)...有没有更高效的内存方式来做到这一点?
    • 我想知道是否可以创建一个自定义的 parse5 treeAdapter 来避免 serializeToString/parseFromString 步骤? (见github.com/inikulin/parse5/blob/master/packages/parse5/docs/…
    • @Fabiosoft 不幸的是,XPath 查询确实需要 DOM。有一些 XPath 子集的实现可以在 PHP 的 SAX 解析器上运行,但是(我几乎希望)在 npm 上没有这样的东西。
    • @FranckFreiburger 如果我今天要做任何网络爬虫,我只会使用 CSS 选择器。他们缺乏像回到某个父母身边这样的功能,但除了打电话给parse5之外,你不需要任何东西。 XML 和围绕它的工具(如 XPath 或 Java)早在 2014 年的某个时候就退出了主流。
    【解决方案4】:

    可能永远不会有正确的方法来解析 HTML 页面。对网络抓取和抓取的第一次评论告诉我,Scrapy 可以满足您的需求。它接受 CSS 和 XPath 选择器。在 Node.js 领域,我们有一个非常新的模块 node-osmosis。这个模块是基于 libxmljs 构建的,因此它应该支持 CSS 和 XPath,尽管我没有找到任何使用 XPath 的示例。

    【讨论】:

      【解决方案5】:

      我认为Osmosis 是您要找的。​​p>

      • 使用本机 libxml C 绑定
      • 支持 CSS 3.0 和 XPath 1.0 混合选择器
      • Sizzle 选择器、Slick 选择器等
      • 没有像 jQuery、cheerio 或 jsdom 这样的大型依赖项
      • HTML 解析器功能

        • 快速解析
        • 搜索速度非常快
        • 内存占用小
      • HTML DOM 特性

        • 加载和搜索 ajax 内容
        • DOM 交互和事件
        • 执行嵌入式和远程脚本
        • 在 DOM 中执行代码

      Here's an example:

      osmosis.get(url)
          .find('//div[@class]/ul[2]/li')
          .then(function () {
              count++;
          })
          .done(function () {
              assert.ok(count == 2);
              assert.done();
          });
      

      【讨论】:

        【解决方案6】:

        只需一行,您就可以使用xpath-html

        const xpath = require("xpath-html");
        
        const node = xpath.fromPageSource(html).findElement("//*[text()='Made with love by']");
        

        【讨论】:

        • 很高兴您制作了一个包含@pda 答案的库。如果出现更好的方法,则可以只更新一个库。另一方面,您没有提到这是您的库,并且该库基本上是该线程的另一个答案,这有点可疑。
        • 值得注意的是,这现在有一个/一些严重的错误:github.com/hieuvp/xpath-html/issues/10#issuecomment-752248148
        猜你喜欢
        • 1970-01-01
        • 2011-11-14
        • 1970-01-01
        • 1970-01-01
        • 2011-07-18
        • 2012-07-19
        • 2012-08-23
        相关资源
        最近更新 更多