DomCrawler组件
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'); |
从节点列表中提取属性 和/或 节点值:
1 2 3 4 |
$attributes = $crawler
->filterXpath('//body/p')
->extract(array('_text', '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
对象:
1 2 3 4 5 |
$linksCrawler = $crawler->selectLink('Go elsewhere...');
$link = $linksCrawler->link();
// or do this all at once / 或者一次做完所有事
$link = $crawler->selectLink('Go elsewhere...')->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):
1 2 3 4 5 6 7 8 |
// Set a single field / 设置单一字段
$form->setValues(array('multi' => array('value')));
// Set multiple fields at once / 一次设置多个字段
$form->setValues(array('multi' => array(
1 => 'value',
'dimensional' => 'an other value'
))); |
这很强大,但它还可以做得更好!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); |
选择无效的choices值 ¶
默认时,(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'); |