支付宝扫一扫付款
                    微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
DomCrawler组件使HTML和XML文档的导览(navigation)变得容易。
你可以通过下述两种方式安装:
通过Composer安装(Packagist上的symfony/dom-crawler)
通过官方Git宝库(https://github.com/symfony/dom-crawler)
然后,包容vendor/autoload.php文件,以开启Composer提供的自动加载机制。否则,你的程序将无法找到这个Symfony组件的类。
Crawler 类提供的方法用于查询和操作HTML以及XML文档。
Crawler实例呈现的是一组 DOMElement 对象,它们是你可以轻松遍历的基本节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  | use Symfony\Component\DomCrawler\Crawler;
 
$html = <<<'HTML'
<!DOCTYPE html>
<html>
    <body>
        <p class="message">Hello World!</p>
        <p>Hello Crawler!</p>
    </body>
</html>
HTML;
 
$crawler = new Crawler($html);
 
foreach ($crawler as $domElement) {
    var_dump($domElement->nodeName);
} | 
特殊化的 Link, Image 和 Form 类,在你遍历HTML文档树的过程中操作html链接、图片以及表单时有用。
DomCrawler将尝试自动修复你的HTML以令其匹配官方协议。例如,如果你把一个 <p> 标签嵌套在另一个 <p> 标签中,它会被移动到成为父标签的sibling(的位置)。这是预期行为,也是HTML5协议的一部分。但如果你得到的是预想以外的行为,这也许是个诱因。尽管DomCrawler并不代表要剥离内容,通过剥离它(dumping it),你仍然可以看到你的“被修复过的”HTML。
使用Xpath表达式十分简单:
1  | $crawler = $crawler->filterXPath('descendant-or-self::body/p'); | 
DOMXPath::query 用于在内部真正操作Xpath查询。
如果你安装了CssSelector组件,过滤(filtering)甚至更加容易。它可以让你使用类似Jquery的选择器(selector)来进行遍历:
1  | $crawler = $crawler->filter('body > p'); | 
可以使用匿名函数来过滤更为复杂的标准:
1 2 3 4 5 6 7 8 9  | use Symfony\Component\DomCrawler\Crawler;
// ...
 
$crawler = $crawler
    ->filter('body > p')
    ->reduce(function (Crawler $node, $i) {
        // filter every other node / 过滤每一个其他的节点
        return ($i % 2) == 0;
    }); | 
要删除一个节点,匿名函数必须返回false。
所有filter方法返回一个带有已过滤内容的 Crawler 实例。
filterXPath() 和 filter() 方法都使用XML namespaces,命令空间可以被自动发现或显式注册。
考虑以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14  | <?xml version="1.0" encoding="UTF-8"?>
<entry
    xmlns="http://www.w3.org/2005/Atom"
    xmlns:media="http://search.yahoo.com/mrss/"
    xmlns:yt="http://gdata.youtube.com/schemas/2007"
>
    <id>tag:youtube.com,2008:video:kgZRZmEc9j4</id>
    <yt:accessControl action="comment" permission="allowed"/>
    <yt:accessControl action="videoRespond" permission="moderated"/>
    <media:group>
        <media:title type="plain">Chordates - CrashCourse Biology #24</media:title>
        <yt:aspectRatio>widescreen</yt:aspectRatio>
    </media:group>
</entry> | 
这可以通过  Crawler 来过滤而毋须用 filterXPath() :
1  | $crawler = $crawler->filterXPath('//default:entry/media:group//yt:aspectRatio'); | 
和 filter() 来注册命名空间的假名:
1  | $crawler = $crawler->filter('default|entry media|group yt|aspectRatio'); | 
默认的命名空间通过 "default" 前缀来注册,该前缀可以用 setDefaultNamespacePrefix() 方法来修改。
命名空间可以通过 registerNamespace() 方法来显式注册:
1 2  | $crawler->registerNamespace('m', 'http://search.yahoo.com/mrss/');
$crawler = $crawler->filterXPath('//m:group//yt:aspectRatio'); | 
通过其在列表(list)中的位置来访问节点:
1  | $crawler->filter('body > p')->eq(0); | 
在当前选中的内容中取得第一个或最后一个节点:
1 2  | $crawler->filter('body > p')->first();
$crawler->filter('body > p')->last(); | 
获取与当前选中内容同级的节点:
1  | $crawler->filter('body > p')->siblings(); | 
获取与当前选中内容“之前或之后”的节点同级的节点:
1 2  | $crawler->filter('body > p')->nextAll();
$crawler->filter('body > p')->previousAll(); | 
获取父节点的全部子节点:
1 2  | $crawler->filter('body')->children();
$crawler->filter('body > p')->parents(); | 
所有tranveral方法返回一个新的 Crawler 实例。
访问当前选中内容 (如 "p" 或 "div") 的第一个节点的名称 (HTML标签名):
1 2 3  | // will return the node name (HTML tag name) of the first child element under <body>
// 将返回 <body> 下的第一个子元素的节点名称(HTML标签名)
$tag = $crawler->filterXPath('//body/*')->nodeName(); | 
访问当前选中内容的第一个节点的值:
1  | $message = $crawler->filterXPath('//body/p')->text(); | 
访问当前选中内容的第一个节点的属性值:
1  | $class = $crawler->filterXPath('//body/p')->attr('class'); | 
从节点列表中提取属性 和/或 节点值:
特殊的 _text 属性,代表的是节点值。
对列表中的每个节点应用匿名函数:
1 2 3 4 5 6  | use Symfony\Component\DomCrawler\Crawler;
// ...
 
$nodeValues = $crawler->filter('p')->each(function (Crawler $node, $i) {
    return $node->text();
}); | 
匿名函数接收节点(一个Crawler)和位置作为参数。结果是一个由匿名函数返回的值所构成的数组。
crawler支持以多种方式来添加内容:
1 2 3 4 5 6 7 8 9 10  | $crawler = new Crawler('<html><body /></html>');
 
$crawler->addHtmlContent('<html><body /></html>');
$crawler->addXmlContent('<root><node /></root>');
 
$crawler->addContent('<html><body /></html>');
$crawler->addContent('<root><node /></root>', 'text/xml');
 
$crawler->add('<html><body /></html>');
$crawler->add('<root><node /></root>'); | 
当处理 ISO-8859-1 以外的字符集(character set)时,组件始终用 addHtmlContent() 方法添加内容,你可以指定第二个参数来设置目标字符集。
由于Crawler的实现是基于DOM extension的,它也可以与原生的 DOMDocument, DOMNodeList 和 DOMNode 对象进行互动:
1 2 3 4 5 6 7 8 9 10  | $document = new \DOMDocument();
$document->loadXml('<root><node /><node /></root>');
$nodeList = $document->getElementsByTagName('node');
$node = $document->getElementsByTagName('node')->item(0);
 
$crawler->addDocument($document);
$crawler->addNodeList($nodeList);
$crawler->addNodes(array($node));
$crawler->addNode($node);
$crawler->add($document); | 
要通过name (或一张可点击的图片的 alt 属性) 来找到一个链接,对当前已存在的Crawler使用 selectLink 方法。 这将返回一个仅包含所选择链接的 Crawler 。调用 link() 可以取得一个特殊的 Link 对象:
Link 对象有一些有用的方法来获取关于“所选中的链接自身”的更多信息:
1 2 3  | // return the proper URI that can be used to make another request
// 返回适当的URL,以便用于下一次请求中
$uri = $link->getUri(); | 
getUri() 极为有用,因为它清除了 href 值,并且把它转换成“能够被真正执行”的程度。例如,对于一个带有 href="#foo" 的链接,这将返回带有 #foo 后缀的当前页面的完整URI。 getUri() 的返回值始终是一个“你可以使用”的完整URI。
要通过 alt 属性来找到一张图片,对当前已存在的Crawler使用 selectImage 方法。 这将返回一个仅包含所选择图片的 Crawler 实例。调用 image() 可以取得一个特殊的 Image 对象:
1 2 3 4 5  | $imagesCrawler = $crawler->selectImage('Kitten');
$image = $imagesCrawler->image();
 
// or do this all at once / 或者一次做完所有事
$image = $crawler->selectImage('Kitten')->image(); | 
Image 拥有和 Link 相同的 getUri() 方法。
对于表单,也有专门的对治方法。Crawler有一个 selectButton() 方法可供使用,它返回另一个匹配了那个“包含了给定text”的按钮 (input[type=submit], input[type=image], 或者是 button) 的Crawler。这个方法特别有用,因为你可以用它取出一个 Form 对象,该对象正是那个按钮所在的表单:
1 2 3 4 5 6 7  | $form = $crawler->selectButton('validate')->form();
 
// or "fill" the form fields with data
// 或者用数据去 “填充” 表单字段
$form = $crawler->selectButton('validate')->form(array(
    'name' => 'Ryan',
)); | 
Form 对象有很多用于操作表单的方法:
1 2 3  | $uri = $form->getUri();
 
$method = $form->getMethod(); | 
getUri() 方法所能做的,不仅是单纯返回表单的 action 属性。如果表单的method是GET,那么它会模拟浏览器行为并返回这个 action
属性,后面跟着(提交来的)全部表单值的query string。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  | // set values on the form internally
// 在内部设置表单的值
$form->setValues(array(
    'registration[username]' => 'symfonyfan',
    'registration[terms]'    => 1,
));
 
// get back an array of values - in the "flat" array like above
// 取得一个值数组 - 类似上面的“扁平”数组
$values = $form->getValues();
 
// returns the values like PHP would see them,
// where "registration" is its own array
// 返回在PHP中可见的值, 即 "registration" 就是它自己的数组
$values = $form->getPhpValues(); | 
操作多维字段(multi-dimensional fields):
1 2 3 4 5  | <form>
    <input name="multi[]" />
    <input name="multi[]" />
    <input name="multi[dimensional]" />
</form> | 
传入一个“值数组”( an array of values):
这很强大,但它还可以做得更好!Form 对象允许你像浏览器一样与表单互动,选择radio值,点选checkboxes,上传文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14  | $form['registration[username]']->setValue('symfonyfan');
 
// check or uncheck a checkbox / 复选框的选中或反选
$form['registration[terms]']->tick();
$form['registration[terms]']->untick();
 
// select an option / 下拉选择
$form['registration[birthday][year]']->select(1984);
 
// select many options from a "multiple" select / 下拉多选
$form['registration[interests]']->select(array('symfony', 'cookies'));
 
// even fake a file upload / 模拟file upload
$form['registration[photo]']->upload('/path/to/lucas.jpg'); | 
做所有这些的意义是什么?如果你进行内部测试的话,你可以抓取你的表单信息,就像它通过PHP值被提交过来一样:
1 2  | $values = $form->getPhpValues();
$files = $form->getPhpFiles(); | 
如果你使用的是一个外部HTTP客户端,你可以使用表单来抓取“你需要为表单创建一个POST请求”的全部信息:
1 2 3 4 5 6 7  | $uri = $form->getUri();
$method = $form->getMethod();
$values = $form->getValues();
$files = $form->getFiles();
 
// now use some HTTP client and post using this information
// 现在可以使用某些HTTP客户端,并且在提交时使用这些信息 | 
一个整合了所有这些方法的完美系统是 Goutte。Goutte理解Symfony 的Crawler对象并且能够用它来直接提交表单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  | use Goutte\Client;
 
// make a real request to an external site
// 制造一个对外部网站的真实请求
$client = new Client();
$crawler = $client->request('GET', 'https://github.com/login');
 
// select the form and fill in some values
// 选取表单,并填充一些值
$form = $crawler->selectButton('Sign in')->form();
$form['login'] = 'symfonyfan';
$form['password'] = 'anypass';
 
// submit that form
// 提交此表单
$crawler = $client->submit($form); | 
默认时,(Symfony的)choice字段(select、radio)带有已激活的内部验证,为的是防止你的表单设置无效值。如果你希望能够设置无效值,可以对整个表单或特定字段使用 disableValidation() 方法:
1 2 3 4 5 6 7 8  | // Disable validation for a specific field
// 禁用特定字段的验证
$form['country']->disableValidation()->select('Invalid value');
 
// Disable validation for the whole form
// 禁用整个表单的验证
$form->disableValidation();
$form['country']->select('Invalid value'); | 
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。