你如何在PHP中解析和处理HTML / XML?

时间:2010-08-26 17:17:19

标签: php xml parsing xml-parsing html-parsing

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

30 个答案:

答案 0 :(得分:1826)

答案 1 :(得分:316)

尝试Simple HTML DOM Parser

  • 用PHP 5+编写的HTML DOM解析器,可让您以非常简单的方式操作HTML!
  • 需要PHP 5 +。
  • 支持无效的HTML。
  • 使用选择器在HTML页面上查找标签,就像jQuery一样。
  • 从一行中提取HTML内容。
  • Download


Examples:

如何获取HTML元素:

// Create DOM from URL or file
$html = file_get_html('http://www.example.com/');

// Find all images
foreach($html->find('img') as $element)
       echo $element->src . '<br>';

// Find all links
foreach($html->find('a') as $element)
       echo $element->href . '<br>';


如何修改HTML元素:

// Create DOM from string
$html = str_get_html('<div id="hello">Hello</div><div id="world">World</div>');

$html->find('div', 1)->class = 'bar';

$html->find('div[id=hello]', 0)->innertext = 'foo';

echo $html;


从HTML中提取内容:

// Dump contents (without tags) from HTML
echo file_get_html('http://www.google.com/')->plaintext;


Scraping Slashdot:

// Create DOM from URL
$html = file_get_html('http://slashdot.org/');

// Find all article blocks
foreach($html->find('div.article') as $article) {
    $item['title']     = $article->find('div.title', 0)->plaintext;
    $item['intro']    = $article->find('div.intro', 0)->plaintext;
    $item['details'] = $article->find('div.details', 0)->plaintext;
    $articles[] = $item;
}

print_r($articles);

答案 2 :(得分:231)

只需使用DOMDocument->loadHTML()即可完成。 libxml的HTML解析算法非常好而且快速,与普遍看法相反,不会阻止格式错误的HTML。

答案 3 :(得分:143)

为什么你不应该什么时候使用正则表达式?

首先,一个常见的误称:正则表达式不适用于解析 HTML。但是,正则表达式可以提取数据。提取就是他们的目标。正则表达式HTML提取优于正确的SGML工具包或基线XML解析器的主要缺点是它们的语法功能和不同的可靠性。

考虑制作一个有点可靠的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实体通常需要手动处理。
  • 最后,对于e 非常简单的任务,例如提取&lt; img src = urls,它们实际上是一个可能的工具。与SGML / XML解析器相比,速度优势大多只是用于这些非常基本的提取过程。

有时甚至建议使用正则表达式/<!--CONTENT-->(.+?)<!--END-->/预先提取HTML片段,并使用更简单的HTML解析器前端处理剩余部分。

注意:我实际上有这个app,我在那里使用XML解析和正则表达式。就在上周,PyQuery解析破了,正则表达式仍然有效。是的很奇怪,我自己无法解释。但事情发生了。
因此,请不要投票考虑现实世界的考虑因素,因为它与正则表达式=邪恶的模因不符。 但是我们也不要过多地投票。这只是本主题的旁注。

答案 4 :(得分:129)

phpQueryQueryPath在复制流畅的jQuery API方面非常相似。这也是为什么它们是正确解析PHP中HTML的两种最简单的方法。

QueryPath的示例

基本上,您首先要从HTML字符串创建一个可查询的DOM树:

 $qp = qp("<html><body><h1>title</h1>..."); // or give filename or URL

生成的对象包含HTML文档的完整树表示。它可以使用DOM方法遍历。但常见的方法是使用jQuery中的CSS选择器:

 $qp->find("div.classname")->children()->...;

 foreach ($qp->find("p img") as $img) {
     print qp($img)->attr("src");
 }

大多数情况下,您希望为#id使用简单的.classDIV->find()标记选择器。但您也可以使用XPath语句,这些语句有时会更快。此外,典型的jQuery方法(如->children()->text(),尤其是->attr()也可以简化提取正确的HTML代码段。 (已经解码了他们的SGML实体。)

 $qp->xpath("//div/p[1]");  // get first paragraph in a div

QueryPath还允许将新标记注入流(->append),然后输出并美化更新的文档(->writeHTML)。它不仅可以解析格式错误的HTML,还可以解析各种XML方言(带名称空间),甚至可以从HTML微格式(XFN,vCard)中提取数据。

 $qp->find("a[target=_blank]")->toggleClass("usability-blunder");

phpQuery或QueryPath?

通常,QueryPath更适合处理文档。虽然phpQuery也实现了一些伪AJAX方法(只是HTTP请求)更接近jQuery。据说phpQuery通常比QueryPath更快(因为整体功能较少)。

有关差异的更多信息,请参阅this comparison on the wayback machine from tagbyte.org。 (原始来源丢失了,所以这里是一个互联网档案链接。是的,你仍然可以找到丢失的页面,人们。)

这是a comprehensive QueryPath introduction

<强>优点

  • 简单性和可靠性
  • 简单易用的替代方案->find("a img, a object, div a")
  • 正确的数据转义(与正则表达式grepping相比)

答案 5 :(得分:88)

简单的HTML DOM是一个很好的开源解析器:

simplehtmldom.sourceforge

它以面向对象的方式处理DOM元素,而新的迭代对非兼容代码有很多覆盖。还有一些很棒的函数,比如你在JavaScript中看到的,比如“find”函数,它将返回该标记名称的所有元素实例。

我在许多工具中都使用过这个工具,在许多不同类型的网页上进行测试,我觉得它很棒。

答案 6 :(得分:59)

我在这里没有提到的一个通用方法是通过Tidy运行HTML,可以将其设置为吐出有保证的有效XHTML。然后,您可以在其上使用任何旧的XML库。

但是对于您的具体问题,您应该看看这个项目:http://fivefilters.org/content-only/ - 它是Readability算法的修改版本,旨在仅提取文本内容(不是标题)和一个页脚。

答案 7 :(得分:55)

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

该组件设计为独立工作,无需Symfony即可使用。

唯一的缺点是它只适用于PHP 5.3或更新版本。

答案 8 :(得分:52)

顺便提一下,这通常被称为屏幕抓取。我用过的库是Simple HTML Dom Parser

答案 9 :(得分:41)

我们之前已经为我们的需求创建了不少的抓取工具。在一天结束时,通常是简单的正则表达式来做最好的事情。虽然上面列出的库很好用于创建它们的原因,但如果你知道你在寻找什么,正则表达式是一种更安全的方式,因为你也可以处理无效的HTML / XHTML结构,如果通过大多数解析器加载,将会失败。

答案 10 :(得分:38)

我建议PHP Simple HTML DOM Parser

它确实有很好的功能,例如:

foreach($html->find('img') as $element)
       echo $element->src . '<br>';

答案 11 :(得分:36)

这听起来像W3C XPath技术的一个很好的任务描述。可以很容易地表达诸如“返回嵌套在href中的img标记中的所有<foo><bar><baz> elements属性”之类的查询。不是PHP buff,我不能告诉你XPath可用的形式。如果可以调用外部程序来处理HTML文件,则应该能够使用命令行版本的XPath。 有关快速介绍,请参阅http://en.wikipedia.org/wiki/XPath

答案 12 :(得分:29)

使用DOM而不是字符串解析的SimpleHtmlDom的第三方替代方法:phpQueryZend_DomQueryPathFluentDom

答案 13 :(得分:24)

是的,您可以将simple_html_dom用于此目的。但是我使用simple_html_dom工作了很多,特别是对于网页报废而且发现它太脆弱了。它完成了基本工作,但我不会推荐它。

我从来没有使用curl达到目的,但我学到的是卷曲可以更有效地完成工作并且更加坚固。

请查看以下链接:scraping-websites-with-curl

答案 14 :(得分:23)

QueryPath 很好,但是如果你没有意识到这意味着什么会小心“跟踪状态”,这可能意味着你浪费了大量的调试时间来试图找出发生的事情和为什么代码不起作用。

这意味着结果集上的每个调用都会修改对象中的结果集,它不像jquery那样可链接,其中每个链接都是一个新集合,你有一个集合,它是查询的结果,每个函数调用修改单个集合。

为了获得类似jquery的行为,你需要在进行过滤/修改之类的操作之前进行分支,这意味着它将更加密切地反映jquery中发生的事情。

$results = qp("div p");
$forename = $results->find("input[name='forename']");

$results现在包含input[name='forename']的结果集而不是原始查询"div p"这让我感到很沮丧,我发现 QueryPath 跟踪了过滤器和查找以及修改结果并将其存储在对象中的所有内容。你需要这样做

$forename = $results->branch()->find("input[name='forname']")

然后$results将不会被修改,您可以一次又一次地重复使用结果集,也许有更多知识的人可以清除这一点,但它基本上就像我发现的那样。

答案 15 :(得分:19)

Advanced Html Dom是一个简单的HTML DOM替代品,提供相同的界面,但它基于DOM,这意味着不会发生任何相关的内存问题。

它还具有完整的CSS支持,包括jQuery扩展名。

答案 16 :(得分:18)

对于HTML5,html5 lib已经被放弃多年了。我能找到的唯一一个带有最新更新和维护记录的HTML5库是html5-php,它在一周前刚刚被带到beta 1.0。

答案 17 :(得分:17)

我编写了一个通用的XML解析器,可以轻松处理GB文件。它基于XMLReader,使用起来非常简单:

$source = new XmlExtractor("path/to/tag", "/path/to/file.xml");
foreach ($source as $tag) {
    echo $tag->field1;
    echo $tag->field2->subfield1;
}

这是github回购:XmlExtractor

答案 18 :(得分:17)

我创建了一个名为PHPPowertools/DOM-Query的库,它允许您像使用jQuery一样抓取HTML5和XML文档。

在幕后,它使用symfony/DomCrawler将CSS选择器转换为XPath选择器。即使将一个对象传递给另一个对象,它也始终使用相同的DomDocument,以确保良好的性能。

使用示例:

namespace PowerTools;

// Get file content
$htmlcode = file_get_contents('https://github.com');

// Define your DOMCrawler based on file string
$H = new DOM_Query($htmlcode);

// Define your DOMCrawler based on an existing DOM_Query instance
$H = new DOM_Query($H->select('body'));

// Passing a string (CSS selector)
$s = $H->select('div.foo');

// Passing an element object (DOM Element)
$s = $H->select($documentBody);

// Passing a DOM Query object
$s = $H->select( $H->select('p + p'));

// Select the body tag
$body = $H->select('body');

// Combine different classes as one selector to get all site blocks
$siteblocks = $body->select('.site-header, .masthead, .site-body, .site-footer');

// Nest your methods just like you would with jQuery
$siteblocks->select('button')->add('span')->addClass('icon icon-printer');

// Use a lambda function to set the text of all site blocks
$siteblocks->text(function( $i, $val) {
    return $i . " - " . $val->attr('class');
});

// Append the following HTML to all site blocks
$siteblocks->append('<div class="site-center"></div>');

// Use a descendant selector to select the site's footer
$sitefooter = $body->select('.site-footer > .site-center');

// Set some attributes for the site's footer
$sitefooter->attr(array('id' => 'aweeesome', 'data-val' => 'see'));

// Use a lambda function to set the attributes of all site blocks
$siteblocks->attr('data-val', function( $i, $val) {
    return $i . " - " . $val->attr('class') . " - photo by Kelly Clark";
});

// Select the parent of the site's footer
$sitefooterparent = $sitefooter->parent();

// Remove the class of all i-tags within the site's footer's parent
$sitefooterparent->select('i')->removeAttr('class');

// Wrap the site's footer within two nex selectors
$sitefooter->wrap('<section><div class="footer-wrapper"></div></section>');

[...]

支持的方法:

  1. 重命名为“选择”,原因显而易见
  2. 重命名为'void',因为'empty'是PHP中的保留字
  3. 注意:

    该库还包含自己的零配置自动加载器,用于PSR-0兼容库。包含的示例应该开箱即用,无需任何其他配置。或者,您可以将它与作曲家一起使用。

答案 19 :(得分:15)

您可以尝试的其他选项是QueryPath。它的灵感来自jQuery,但是在PHP中的服务器上,在Drupal中使用。

答案 20 :(得分:15)

您可以尝试使用类似HTML Tidy的内容来清理任何“损坏的”HTML并将HTML转换为XHTML,然后您可以使用XML解析器进行解析。

答案 21 :(得分:12)

XML_HTMLSax相当稳定 - 即使它不再维持。另一种选择可能是通过Html Tidy管道HTML,然后使用标准XML工具解析它。

答案 22 :(得分:11)

Symfony框架包含可以解析HTML的包,您可以使用CSS样式选择DOMs,而不是使用XPath

答案 23 :(得分:11)

有许多方法可以处理HTML / XML DOM,其中大多数已经提到过。因此,我不会尝试自己列出这些。

我只是想补充一点,我个人更喜欢使用DOM扩展以及原因:

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

虽然我错过了为DOMDocument使用CSS选择器的能力,但有一种相当简单方便的方法来添加此功能:继承DOMDocument并添加类似JS的querySelectorAll和子类的querySelector方法。

为了解析选择器,我建议使用CssSelector component中非常简约的Symfony framework。该组件只是将CSS选择器转换为XPath选择器,然后可以将其输入DOMXpath以检索相应的Nodelist。

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

以下代码直接来自DOM-Query library,并使用我所描述的技术。

对于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创建CssSelector组件以及如何使用它的决定Parsing XML documents with CSS selectors

答案 24 :(得分:9)

使用FluidXML,您可以使用 XPath CSS选择器查询和迭代XML。

$doc = fluidxml('<html>...</html>');

$title = $doc->query('//head/title')[0]->nodeValue;

$doc->query('//body/p', 'div.active', '#bgId')
        ->each(function($i, $node) {
            // $node is a DOMNode.
            $tag   = $node->nodeName;
            $text  = $node->nodeValue;
            $class = $node->getAttribute('class');
        });

https://github.com/servo-php/fluidxml

答案 25 :(得分:7)

有几个原因不能通过正则表达式解析HTML。但是,如果您完全控制将生成什么HTML,那么您可以使用简单的正则表达式。

上面是一个通过正则表达式解析HTML的函数。请注意,此函数非常敏感,要求HTML遵守某些规则,但在许多情况下它都能很好地工作。如果你想要一个简单的解析器,并且不想安装库,请给它一个镜头:

function array_combine_($keys, $values) {
    $result = array();
    foreach ($keys as $i => $k) {
        $result[$k][] = $values[$i];
    }
    array_walk($result, create_function('&$v', '$v = (count($v) == 1)? array_pop($v): $v;'));

    return $result;
}

function extract_data($str) {
    return (is_array($str))
        ? array_map('extract_data', $str)
        : ((!preg_match_all('#<([A-Za-z0-9_]*)[^>]*>(.*?)</\1>#s', $str, $matches))
            ? $str
            : array_map(('extract_data'), array_combine_($matches[1], $matches[2])));
}

print_r(extract_data(file_get_contents("http://www.google.com/")));

答案 26 :(得分:7)

来自XML的JSON和数组分为三行:

$xml = simplexml_load_string($xml_string);
$json = json_encode($xml);
$array = json_decode($json,TRUE);

Ta da!

答案 27 :(得分:2)

我创建了一个名为HTML5DOMDocument的库,可以在https://github.com/ivopetkov/html5-dom-document-php免费获取

它也支持查询选择器,我认为在您的情况下非常有用。以下是一些示例代码:

public class CustomAuthorize : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {

        var isAuthorized = base.AuthorizeCore(httpContext);
        if (!isAuthorized)
        {
            return false;
        }

        if (httpContext.User.Identity.IsAuthenticated && Request.Url.ToString().Contains("Register"))
        {
            return false;
        }

    }

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
        else
        {
            filterContext.Result = new RedirectToRouteResult(new
            RouteValueDictionary(new { controller = "Error", action = "AccessDenied" }));
        }
    }
}

答案 28 :(得分:0)

如果您熟悉jQuery选择器,则可以将ScarletsQuery用于PHP

<pre><?php
include "ScarletsQuery.php";

// Load the HTML content and parse it
$html = file_get_contents('https://www.lipsum.com');
$dom = Scarlets\Library\MarkupLanguage::parseText($html);

// Select meta tag on the HTML header
$description = $dom->selector('head meta[name="description"]')[0];

// Get 'content' attribute value from meta tag
print_r($description->attr('content'));

$description = $dom->selector('#Content p');

// Get element array
print_r($description->view);

此库通常需要不到1秒的时间来处理脱机html。
它还接受无效的HTML或标记属性中缺少引号。

答案 29 :(得分:0)

解析xml的最佳方法:

$xml='http://www.example.com/rss.xml';
$rss = simplexml_load_string($xml);
$i = 0;
foreach ($rss->channel->item as $feedItem) {
$i++;
echo $title=$feedItem->title;
echo '<br>';
echo $link=$feedItem->link;
echo '<br>';
if($feedItem->description !='') {$des=$feedItem->description;} else {$des='';}
echo $des;
echo '<br>';
if($i>5) break;
}