Javascript 使用Node.js和XPath执行HTML页面解析

Javascript 使用Node.js和XPath执行HTML页面解析,javascript,html,node.js,xpath,phantomjs,Javascript,Html,Node.js,Xpath,Phantomjs,我正在使用Node.js进行一些web抓取。我想使用XPath,因为我可以用几种GUI半自动地生成它。问题是我找不到一个有效的方法 jsdom非常慢。它在一分钟左右的时间内解析500KB的文件,CPU负载满,内存占用大 用于HTML解析的流行库(例如,cheerio)既不支持XPath,也不公开与W3C兼容的DOM 显然,有效的HTML解析是在WebKit中实现的,因此使用phantom或casper将是一种选择,但这些解析需要以特殊的方式运行,而不仅仅是节点。我不能相信这一变化所隐含的风险。例

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

  • jsdom
    非常慢。它在一分钟左右的时间内解析500KB的文件,CPU负载满,内存占用大
  • 用于HTML解析的流行库(例如,
    cheerio
    )既不支持XPath,也不公开与W3C兼容的DOM
  • 显然,有效的HTML解析是在WebKit中实现的,因此使用
    phantom
    casper
    将是一种选择,但这些解析需要以特殊的方式运行,而不仅仅是
    节点
    。我不能相信这一变化所隐含的风险。例如,找到如何使用
    phantom
    运行
    node inspector
    要困难得多
  • Spooky
    是一个选项,但它是,所以它根本没有在我的机器上运行
  • 那么,用XPath解析HTML页面的正确方法是什么呢?

    我刚刚开始使用它来解析和提取相关的HTML部分。它声称比纯js实现快50倍(我还没有证实这一说法)

    根据您的需要,您可以直接使用HTML条带,或者提升代码和绑定,使您自己在HTMLPATH本地//P>内部使用C++。 如果您想使用xpath,那么请使用此处已有的包装器;

    是目前最快的实现,因为它只绑定到支持XPath 1.0查询的C库:

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

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

  • 使用
    parse5
    解析HTML。糟糕的是结果不是DOM。虽然它足够快,而且W3C compiant
  • 使用
    xmlserializer
    将其序列化为XHTML,该XHTML接受类DOM结构的
    parse5
    作为输入
  • 使用
    xmldom
    再次解析该XHTML。现在你终于有了DOM
  • 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
    

    可能永远都没有正确的方法来解析HTML页面。对网页抓取和爬行的第一次回顾表明,它可以很好地满足您的需求。它同时接受CSS和XPath选择器。在Node.js领域中,我们有一个非常新的模块。这个模块是基于libxmljs构建的,因此它应该同时支持CSS和XPath,尽管我没有发现任何使用XPath的示例。

    我想这就是您想要的

    • 使用本机libxmlc绑定
    • 支持CSS 3.0和XPath 1.0选择器的混合
    • 嘶嘶声选择器、光滑选择器等
    • 没有像jQuery、cheerio或jsdom这样的大型依赖项
    • HTML解析器特性

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

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


    只需一行代码,您就可以使用:

    constxpath=require(“XPathHTML”);
    const node=xpath.fromPageSource(html).findElement(“//*[text()='Made with love by']);
    
    0。你的链接断了。1.这个库正在解析实体,这从它的名字就很明显了。2.您的答案中甚至没有提到XPath;添加了xpath实现的链接,您自己没有找到/使用它的原因是什么?必须在某种DOM上运行
    xpath
    库。解析HTML的唯一解决方案是
    jsdom
    ,它非常慢。这是上面列表中的第一项。你读过这个问题吗?如果你读过
    npm xpath
    文档,你会看到他建议使用xmldom,以及xmldom应该如何解析HTML?或者?@mb21是否有任何性能DOM实现来运行它们?谢谢,非常好用。除了我需要替换
    var document=parser.parse(html.toString())
    by
    var document=parse5.parse(html.toString())
    并去掉行
    var parser=new parse5.parser()(使用parse5版本2.0.2)您正在加载内存中的所有内容(整个DOM)。。。有没有更节省内存的方法呢?我想知道是否有可能创建一个自定义的parse5 treeAdapter来避免serializeToString/parseFromString步骤?(请参阅)@Fabiosoft不幸的是,XPath查询确实需要DOM。有一些XPath子集的实现可以在用于PHP的SAX解析器上工作,但是(我几乎希望)npm上没有这种东西。@Frankfreiburger如果我今天要做任何网络爬虫,我只会使用CSS选择器。它们缺少类似于返回某个父级的功能,但除了调用
    parse5
    之外,您不需要任何其他功能。XML及其相关工具(如XPath或Java)早在2014年就退出了主流。很好,您已经创建了一个库,通过@pda包含了答案。如果出现更好的方法,则可以只更新一个库。另一方面,有点可疑的是,你没有提到这是你的库,而这个库基本上是这个线程的另一个答案。值得注意的是,现在这里有一个/一些严重的bug:
    osmosis.get(url)
        .find('//div[@class]/ul[2]/li')
        .then(function () {
            count++;
        })
        .done(function () {
            assert.ok(count == 2);
            assert.done();
        });