composer类库-HTML分析组件DomCrawler

最近用php进行爬虫学习,用composer安装了一个类库 symfony/dom-crawler,用来分析抓取到的网页html元素,提取其中想要的内容。因其没有中文文档,也很少有使用这个类库的相关中文资料,所以使用过程中也遇到了一些坑,在这里将使用过程中的心得及遇到问题和解决办法记录一下。


  • DomCrawler工作原理:
    工作原理就是将抓取到的html页面字符串实例化为一个dom对象,通过 xpath 语法或者 css选择器 语法选择其中的dom节点,类似于jquery的css选择器一样,提取页面元素的属性或者值,基本能获取到页面任意想要的内容,非常强大,是一个应用于爬虫中分析html元素及提取内容的利器。

  • 安装:

composer require symfony/dom-crawler
composer require symfony/css-selector    #css-selector也需要安装,才能使用xpath选择器
  • 使用:
  1. 简单使用
require_once 'vendor/autoload.php';

#引入安装的composer类库
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\CssSelector\CssSelectorConverter;

#抓取到的html字符串
$html = <<<'HTML'
<!DOCTYPE html>
<html>
    <body>
        <p class="message">Hello World!</p>
        <p>Hello Crawler!</p>
    </body>
</html>
HTML;

#实例化dom对象
$crawler = new Crawler();
$crawler->addHtmlContent($html);

#获取节点元素
foreach ($crawler as $domElement) {
    var_dump($domElement->nodeName);
}

  1. css选择器语法使用
    语法规则如下:
$crawler->filter('selector名称');

如果想要选择页面的某个元素下的内容,css选择器语法该怎么写呢?
有一个简单的方法,以谷歌浏览器为例,打开 www.baidu.com,比如我要获取到 百度一下 这个按钮下的内容


F12 打开开发者工具,光标定位到该元素,是如下的元素:

想要获取上图中span下的内容,点击span > 右键 > copy >copy selector,便复制了该按钮的css选择器。
然后使用以下规则:

$crawler->filter('#s_btn_wr');

就能获取到该span下的内容了,至于获取到某个元素下的内容后怎么进一步处理,比如获取元素属性值,元素内容等,后面会详细说。
当然我们有时候页面某个元素并没有一个id或者class的选择器,但是也可以按照以上的方法来获取css选择器,只不过这个选择器可能会比较长了,如下:

#有时候可能会这样,不过也能获取到某个元素下的内容
#rmain > div > div:nth-child(2) > div:nth-child(3) > div:nth-child(5) > div.bd > table > tbody > tr

  1. xpath选择器语法使用
    用css选择器可能有时候会比较憋屈,因为copy出来的选择器会比较长,而且有时候不一定准,要结合页面源代码观察,而且有时候在某些特殊情况下并没有办法获取到想要的内容,比如有以下要求:获取一个页面所有包含某个关键词的a标签href属性中的链接,这个用传统的css选择器比较难做到,需要费很大劲,这时候xpath选择器就上场了,不得不说,xpath很强大,非常强大,几乎能获取到页面中想要获取的任何内容,我在这里说几个典型的常用的用法吧,具体如何使用可以参考 xpath教程
###获取页面中所有a标签href属性值包含 *info* 关键词的a元素,在提取页面某一类url很有用
$crawler->filterXPath('//a[contains(@href,"info")]');   
###获取文本中包含 *职称* 两个字的span标签,这在通过文本信息来提取某类元素有用
$crawler->filterXPath('//span[contains(text(),"职称")]');
####选择两个特定元素之间的所有元素,这个很变态,很强大,放一个公式吧,具体怎么用可参考https://ask.helplib.com/xml/post_1005470 文章内容
$ns1[count(.|$ns2) = count($ns2)]  

关于xpath语法更多详细的使用,大家可参考下xpath教程的一些语法规则,多用用就知道了。
在这里我介绍一个谷歌浏览器的xpath的插件,在谷歌应用商店下载 xpath helper(前提需要fanqiang)如下图:


很好用,可以在某个页面中打开,用来检测自己的xpath表达式是否正确,很有用。


  1. 用选择器获取到元素下内容后的进一步处理
    以获取某个页面 body > p 元素内容后的进一步处理为例:
$crawler->filter('body > p')->eq(0);    ###获取body下的第一个p元素
$crawler->filter('body > p')->first();  ###获取body下的第一个p元素
$crawler->filter('body > p')->last();   ###获取body下的最后一个p元素
$crawler->filter('body > p')->siblings();   ###获取body下p元素的所有兄弟元素
$crawler->filter('body > p')->nextAll();   ###获取body下p元素所有后面的元素
$crawler->filter('body > p')->previousAll();   ###获取body下p元素所有前面的元素
$crawler->filter('body')->children();   ###获取body下的所有子元素
$crawler->filter('body > p')->parents();   ###获取body下p元素的父元素

$crawler->filterXPath('//body/*')->nodeName();  ###获取body下元素节点名称
$crawler->filterXPath('//body/p')->text();    ###获取body下p元素中的文本内容
$crawler->filterXPath('//body/p')->attr('class');  ###获取body下p元素的class属性值

有时候会获取到多个节点,需要采用循环对每一个节点进行处理,可以用循环如下:

$crawler->filter('p')->each(function (Crawler $node, $i) {
    return $node->text();
});

  • 使用中问题及解决办法:
  1. composer安装了 dom-crawler 后使用不了 xpath选择器,即调用 $crawler->filterXPath('//xxx') 会报错,如何解决?
    解决办法:composer安装 css-selector,php页面引入 CssSelectorConverter
composer require symfony/css-selector  #composer安装
use Symfony\Component\CssSelector\CssSelectorConverter;   #php页面中引入
  1. new Crawler($html) 返回的结果会出现乱码,如何解决?
    解决办法:这个应该与页面编码有关,所以可以采用下面的方式,先初始化crawler,然后添加node。
$crawler = new Crawler();
$crawler->addHtmlContent($html);
  1. 用css选择器的时候,按照上面说的步骤去获取,为什么有时候获取不到,会报错 The current node list is empty,如何解决?
    因为有时候用开发者工具获取的css选择器不太准,比如获取的以下内容:
#rmain > div > div:nth-child(2) > div:nth-child(3) > div:nth-child(5) > div.bd > table > tbody > tr

我们在查看页面源代码的时候,也许table下是没有tbody的,可能table下直接是tr,不过为什么通过开发者工具复制出来的selector有呢,因为浏览器对html的table有时候会自动把tbody给补上的,但是页面源代码中并没有,所有我们有时候还需要结合页面源代码来分析选择器是否正确。


推荐阅读更多精彩内容