如何解析HTML/XML并从中提取信息?


当前回答

Symfony框架具有可以解析HTML的捆绑包,您可以使用CSS样式来选择DOM,而不是使用XPath。

其他回答

XML_HTMLMax相当稳定——即使不再维护它。另一种选择是通过HtmlTidy将HTML导入,然后用标准的XML工具解析它。

处理HTML/XML DOM的方法有很多,其中大多数已经提到。因此,我不会亲自列出这些。

我只想补充一点,我个人更喜欢使用DOM扩展,以及为什么:

iit充分利用了底层C代码的性能优势它是OO PHP(并允许我对其进行子类化)它的级别相当低(这允许我将其用作更高级行为的非臃肿基础)它提供对DOM的每个部分的访问(不像SimpleXml,它忽略了一些鲜为人知的XML特性)它具有用于DOM爬行的语法,与原生Javascript中使用的语法类似。

虽然我怀念为DOMDocument使用CSS选择器的能力,但有一种非常简单和方便的方法可以添加此功能:将DOMDocument子类化,并将类似于querySelectorAll和querySelector的JS方法添加到子类中。

为了解析选择器,我建议使用Symfony框架中的非常简约的CsSelector组件。该组件只是将CSS选择器转换为XPath选择器,然后可以将其输入到DOMX路径中以检索相应的Nodelist。

然后,您可以使用这个(仍然是非常低级的)子类作为更高级类的基础,例如解析非常特定的XML类型或添加更多类似jQuery的行为。

下面的代码直接来自我的DOM查询库,并使用了我描述的技术。

对于HTML分析:

namespace PowerTools;

use \Symfony\Component\CssSelector\CssSelector as CssSelector;

class DOM_Document extends \DOMDocument {
    public function __construct($data = false, $doctype = 'html', $encoding = 'UTF-8', $version = '1.0') {
        parent::__construct($version, $encoding);
        if ($doctype && $doctype === 'html') {
            @$this->loadHTML($data);
        } else {
            @$this->loadXML($data);
        }
    }

    public function querySelectorAll($selector, $contextnode = null) {
        if (isset($this->doctype->name) && $this->doctype->name == 'html') {
            CssSelector::enableHtmlExtension();
        } else {
            CssSelector::disableHtmlExtension();
        }
        $xpath = new \DOMXpath($this);
        return $xpath->query(CssSelector::toXPath($selector, 'descendant::'), $contextnode);
    }

    [...]

    public function loadHTMLFile($filename, $options = 0) {
        $this->loadHTML(file_get_contents($filename), $options);
    }

    public function loadHTML($source, $options = 0) {
        if ($source && $source != '') {
            $data = trim($source);
            $html5 = new HTML5(array('targetDocument' => $this, 'disableHtmlNsInDom' => true));
            $data_start = mb_substr($data, 0, 10);
            if (strpos($data_start, '<!DOCTYPE ') === 0 || strpos($data_start, '<html>') === 0) {
                $html5->loadHTML($data);
            } else {
                @$this->loadHTML('<!DOCTYPE html><html><head><meta charset="' . $encoding . '" /></head><body></body></html>');
                $t = $html5->loadHTMLFragment($data);
                $docbody = $this->getElementsByTagName('body')->item(0);
                while ($t->hasChildNodes()) {
                    $docbody->appendChild($t->firstChild);
                }
            }
        }
    }

    [...]
}

另请参见Symfony的创建者Fabien Potencier关于为Symfony创建CsSelector组件的决定以及如何使用它的CSS选择器解析XML文档。

只需使用DOMDocument->loadHTML()就可以了。libxml的HTML解析算法非常好,速度也很快,与流行的观点相反,它不会对格式错误的HTML产生影响。

为什么不应该以及何时应该使用正则表达式?

首先,一个常见的误称:Regexp不用于“解析”HTML。然而,正则表达式可以“提取”数据。提取是它们的目的。与适当的SGML工具包或基线XML解析器相比,正则表达式HTML提取的主要缺点是它们的语法工作和不同的可靠性。

考虑制作一个稍微可靠的HTML提取正则表达式:

<a\s+class="?playbutton\d?[^>]+id="(\d+)".+?    <a\s+class="[\w\s]*title
[\w\s]*"[^>]+href="(http://[^">]+)"[^>]*>([^<>]+)</a>.+?

比简单的phpQuery或QueryPath等效文件可读性差得多:

$div->find(".stationcool a")->attr("title");

然而,在某些特定的用例中,它们可以提供帮助。

许多DOM遍历前端不显示HTML注释<!--,然而,它们有时是用于提取的更有用的锚。特别是伪HTML变体<$var>或SGML残基很容易用正则表达式驯服。通常,正则表达式可以节省后期处理。然而,HTML实体通常需要手动管理。最后,对于提取<img src=urls等极其简单的任务,它们实际上是一个可能的工具。与SGML/XML解析器相比,速度优势主要用于这些非常基本的提取过程。

有时甚至建议使用正则表达式/<--内容-->(.+?)<--END-->/并使用更简单的HTML解析器前端处理其余部分。

注意:我实际上有一个应用程序,在那里我交替使用XML解析和正则表达式。就在上周,PyQuery解析中断,正则表达式仍然有效。是的,很奇怪,我自己也解释不了。但事情就是这样发生的。因此,请不要因为现实世界的考虑与正则表达式=邪恶模因不匹配就投票否决。但我们也不要对此投太多赞成票。这只是这个话题的一个旁注。

对于1a和2:我将投票支持新的Symfony Componet类DOMCrawler(DOMCrawler)。此类允许类似于CSS选择器的查询。看看这个演示文稿,看看真实世界的例子:news-of-the-symfony2-world。

该组件设计为独立工作,可以在没有Symfony的情况下使用。

唯一的缺点是它只适用于PHP5.3或更高版本。