vuthaihoc/spiderling

使用kohana或phantomjs爬取网络。

v0.4.0 2020-02-21 22:56 UTC

README

Build Status Scrutinizer Quality Score Code Coverage Latest Stable Version

这是一个使用curl和PhantomJS爬取网页的库。深受Capybara启发。它是phpunit-spiderling集成级别测试的主要组件。它可以轻松处理AJAX请求,并允许轻松从仅PHP的快速驱动程序切换到JavaScript启用如PhantomJS,而不修改代码。

快速示例

use Openbuildings\Spiderling\Page;

$page = new Page();

$page->visit('http://example.com');

$li = $page->find('ul.nav > li.test');

echo $li->text();

$page
  ->fill_in('Name', 'New Name')
  ->fill_in('Description', 'some description')
  ->click_button('Submit');

这将输出HTML节点li.test的文本内容,填写一些输入并提交表单。

领域特定语言(DSL)

页面对象具有丰富的DSL,用于访问内容和填写表单

导航

  • visit($page, $query):将浏览器导向打开新的URL
  • current_url():检索当前URL - 这将受到重定向或脚本以任何方式更改浏览器URL的影响。
  • current_path():这将返回URL而没有协议和主机部分,这对于编写更通用的代码很有用。

获取器

每个节点代表页面上的一个HTML标签,您可以使用广泛的获取方法来探测其内容。所有这些获取器都是动态的,这意味着没有缓存涉及,每个方法都会向其适当的驱动程序发送调用。

  • is_root():检查当前元素是否是根"节点"
  • id():获取当前节点的'id' - 这个ID对于当前页面是唯一的。
  • html():获取当前节点的原始html - 这就像在dom元素上调用outerHTML一样。
  • tag_name():获取dom元素的标签名。例如:DIV,SPAN,FORM,SELECT
  • attribute($name):获取当前标签的属性。如果标签为空,例如<div disabled />,则返回空字符串。如果没有属性,则返回NULL
  • text():获取html标签的文本内容 - 这与浏览器渲染HTML标签的方式相似,所有空白都将合并为单个空格。
  • is_visible():检查节点是否可见。如果项目通过JS,CSS或内联样式隐藏,则PhantomJS驱动程序将返回正确的值。
  • is_selected():检查选项标签是否"选中"
  • is_checked():检查输入标签是否"选中"
  • value():获取输入表单标签的值

以下是一些获取器的示例,如果我们有这个页面

<html>
  <body>
    <ul>
      <li class="first"><span>LABEL</span> This is the first link</li>
      <li class="some class"><span>LABEL</span> Go <a href="http://example.com">somewhere</a></li>
    </ul>
  </body>
</html>

然后你可以编写以下PHP代码

use Openbuildings\Spiderling\Page;

$page = new Page();

$page->visit('http://example.com/the-page');

$li = $page->find('ul > li.first');

// Will output "LI"
echo $li->tag_name();

// Will output "first"
echo $li->attribute('class');

// Will output "LABEL This is the first link"
echo $li->text();

// Will output "<li class="first"><span>LABEL</span> This is the first link</li>"
echo $li->html();

设置器

Spiderling还提供修改当前页面的能力,填写输入字段,点击按钮和链接,提交表单。这可以通过低级设置器实现

  • set($value):如果节点代表输入字段(input,textarea或甚至是select),您可以使用此方法设置其值。
  • append($text):如果您需要将一些文本追加到文本字段或输入中,可以使用append()方法而不是set() - 这允许在更少的往返驱动程序中进行操作。
  • click():如果节点表示可以点击的对象(如链接或按钮),您可以使用该节点的click()方法。这将执行所需的操作,就像有人点击它一样,加载新页面并更新当前_url / current_path获取器的结果。
  • select_option():如果节点是选项标签,则可以使用此方法选择它。这将取消选择SELECT标签中任何其他已选选项,除非它标记为"multiple"。
  • unselect_option()select_option()的反义。
  • hover($x = NULL, $y = NULL):将鼠标悬停在当前节点上,触发javascript / css事件和状态。您可以可选地传递x和y偏移量,以便进行微调。
  • drop_files($files):这将触发与在DOM元素(HTML5功能)上放置文件相关的所有JavaScript事件。

例如,有一个这样的表单

<html>
  <body>
    <form action="/submit" method="post">
      <div class="row">
        <label for="text-input">Name:</label>
        <input type="text" id="text-input" name="name" />
      </div>
      <div class="row">
        <label for="description-input">Description:</label>
        <textarea name="description" id="description-input" cols="30" rows="10">
          Some text
        </textarea>
      </div>
      <input type="submit" value="Submit"/>
    </form>
  </body>
</html>

我们可以编写如下脚本

use Openbuildings\Spiderling\Page;

$page = new Page();

$page->visit('http://example.com/the-form');

$page
  ->find('#text-input')
    ->set('New Name');

$page
  ->find('#description-input')
    ->append(' with some additions');

$page
  ->find('input[type="submit"]')
    ->click();

// This will return the submitted action of the form, e.g. http://example.com/submit
echo $page->current_url();

定位器

您不仅可以按CSS选择器(默认操作)查找元素,还可以使用特殊查找器查找输入元素、按钮和链接。这被称为"定位器类型"。

  • css - 默认
  • xpath - 使用XPath
  • link - 通过id、标题、链接内的文本或链接内图像的alt文本查找链接。
  • field - 通过id、name、标签的文本、指向此输入的标签、输入的占位符或没有值(通常是默认选项)的选择标签的选项文本查找输入元素(TEXTAREA、INPUT、SELECT)。
  • label - 通过id、标题、内容文本或标签内图像的alt文本查找标签。
  • button - 通过id、标题、name、value、按钮内的文本或按钮内图像的alt文本查找按钮。

所有这些定位器类型都允许您轻松扫描页面并选择要点击或填充的内容,而无需查看页面的HTML。在任何地方,您都可以输入array('{locator type}', '{selector}')来更改默认定位器类型。

以下是一个使用前述HTML的示例

<html>
  <body>
    <form action="/submit" method="post">
      <div class="row">
        <label for="text-input">Name:</label>
        <input type="text" id="text-input" name="name" />
      </div>
      <div class="row">
        <label for="description-input">Description:</label>
        <textarea name="description" id="description-input" cols="30" rows="10">
          Some text
        </textarea>
      </div>
      <input type="submit" value="Submit"/>
    </form>
  </body>
</html>

PHP代码变得更清晰且不易碎 - 基础HTML可以更改,但您的代码仍然按预期工作

use Openbuildings\Spiderling\Page;

$page = new Page();

$page->visit('http://example.com/the-form');

$page
  ->find(array('field', 'Name'))
    ->set('New Name');

$page
  ->find(array('field', 'Description'))
    ->set('some description');

$page
  ->find(array('button', 'Submit'))
    ->click();

// This will return the submitted action of the form, e.g. http://example.com/submit
echo $page->current_url();

过滤器

如果仅使用定位器不足以缩小搜索范围,您可以使用"过滤器"轻松地缩小搜索范围。它们遍历找到的候选者,过滤掉不匹配的项。请注意,它们会加载节点并逐一检查它们,这可能会对性能产生影响,但在大多数情况下是可以接受的。

以下是一些可用的过滤器

  • visible:TRUE或FALSE - 过滤掉可见或不可见的节点
  • value:值字符串 - 过滤掉没有匹配值的节点
  • text:文本字符串 - 过滤掉没有给定文本的节点
  • attributes:属性名称 => 属性值数组 - 过滤掉没有所有给定属性(名称和值)的节点
  • at:特别选择要返回的列表中的节点,所有其他节点都被过滤掉。

以下是如何使用此HTML的过滤器

<html>
  <body>
    <ul>
      <li class="one">Row One</li>
      <li class="two">Row Two</li>
      <li style="display:none">Row Three</li>
    </ul>
    <select name="test">
      <option value="test">Option 1</option>
      <option value="test 2">Option 2</option>
    </select>
  </body>
</html>
use Openbuildings\Spiderling\Page;

$page = new Page();

$page->visit('http://example.com/the-filters-page');

// Will output "Row One"
echo $page->find('li', array('text' => 'One'))->text();

// Will output "Row Three"
echo $page->find('li', array('visible' => FALSE))->text();

// Will output "Option 2"
echo $page->find('option', array('value' => 'test 2'))->text();

查找器

大多数定位器类型都有一个用于查找特定类型元素的定制方法。还有一些其他可能对您有用的自定义查找器

  • find($selector, array $filters = array()) - 默认,使用CSS选择器
  • find_field($selector, array $filters = array()) - 使用'field'定位器类型查找输入元素
  • find_link($selector, array $filters = array()) - 使用'link'定位器类型查找锚点标签
  • find_button($selector, array $filters = array()) - 使用'button'定位器类型
  • not_present($selector, array $filters = array()) - "find"的对立面确保元素不在页面上
  • all($selector, array $filters = array()) - 返回一个Nodelist - 一个可迭代和可计数的类似数组对象,你可以轻松地使用'foreach'和'count'。请注意,它具有懒加载功能,因此只有当你访问节点时,它才会被驱动程序加载。 count()不会加载任何节点。

上一个示例可以重写为以下形式

use Openbuildings\Spiderling\Page;

$page = new Page();

$page->visit('http://example.com/the-form');

$page
  ->find_field('Name')
    ->set('New Name');

$page
  ->find_field('Description')
    ->set('some description');

$page
  ->find_button('Submit')
    ->click();

// This will return the submitted action of the form, e.g. http://example.com/submit
echo $page->current_url();

操作

在页面上可以执行的一些常用操作(如修改输入、点击链接和按钮等)具有快捷方法,可以使你的代码更易于阅读和健壮。

以下是所有这些操作

  • click_on($selector, array $filters = array()):使用CSS选择器查找节点并点击它
  • click_link($selector, array $filters = array()):使用“链接”定位类型查找节点并点击它
  • click_button($selector, array $filters = array()):使用“按钮”定位类型查找节点并点击它
  • fill_in($selector, $with, array $filters = array()):使用“字段”定位类型查找节点并使用“$with”设置其值。
  • choose($selector, array $filters = array()):使用“字段”定位类型选择特定的单选输入标签。
  • check($selector, array $filters = array()):使用“字段”定位类型查找复选框输入标签并将其选中。
  • uncheck($selector, array $filters = array()):使用“字段”定位类型查找复选框输入标签并将其取消选中。
  • attach_file($selector, $file, array $filters = array()):使用“字段”定位类型查找文件输入标签并将其设置为“$file”。
  • select($select, $option_filters, array $filters = array()):使用“字段”定位类型查找选择标签,并将其一个或多个选项标记为“选中”。如果$option_filters是一个字符串,则将具有该字符串值的选项设置为选中状态,否则将设置与过滤器匹配的所有选项。这允许通过值、文本甚至位置来选择。
  • unselect($select, $option_filters, array $filters = array()):与“select”相同,但匹配的选项将被取消选中
  • hover_on($select, array $filters = array()):将鼠标移至由CSS选择器找到的元素上
  • hover_link($select, array $filters = array()):将鼠标移至由链接定位类型找到的元素上
  • hover_field($select, array $filters = array()):将鼠标移至由“字段”定位类型找到的元素上
  • hover_button($select, array $filters = array()):将鼠标移至由“按钮”定位类型找到的元素上

使用这些方法可以使你的代码非常易于阅读。此外,所有这些操作都返回$this,允许你轻松地进行链式调用。考虑一下在“查找器”部分中的上一个示例 - 你可以将其重写为以下内容

use Openbuildings\Spiderling\Page;

$page = new Page();

$page->visit('http://example.com/the-form');

$page
  ->fill_in('Name', 'New Name')
  ->fill_in('Description', 'some description')
  ->click_button('Submit');

// This will return the submited action of the form, e.g. http://example.com/submit
echo $page->current_url();

以下是一个更复杂的示例。我们将使用以下HTML

<html>
  <body>
    <form action="/submit" method="post">
      <div class="row">
        <label for="text-input">Name:</label>
        <input type="text" id="text-input" name="name" />
      </div>
      <div class="row">
        <label>Features:</label>
        <ul>
          <li>
            <input type="checkbox" id="feature-input-1" name="feature_1" checked />
            <label for="feature-input-1">Feature One</label>
          </li>
          <li>
            <input type="checkbox" id="feature-input-2" name="feature_2" />
            <label for="feature-input-2">Feature Two</label>
          </li>
        </ul>
      </div>
      <div class="row">
        <label>State:</label>
        <ul>
          <li>
            <input type="radio" id="state-input-1" name="state" checked />
            <label for="state-input-1">Open</label>
          </li>
          <li>
            <input type="radio" id="state-input-2" name="state" />
            <label for="state-input-2">Closed</label>
          </li>
        </ul>
      </div>
      <div class="row">
        <label for="type-input">Type:</label>
        <select name="type" id="type-input">
          <option>Select an Option</option>
          <option value="big">Type Big</option>
          <option value="small">Type Small</option>
        </select>
      </div>
      <div class="row">
        <label for="text-input">Name:</label>
        <input type="text" id="text-input" name="name" />
      </div>
      <div class="row">
        <label for="description-input">Description:</label>
        <textarea name="description" id="description-input" cols="30" rows="10">
          Some text
        </textarea>
      </div>
      <button>
        <img src="/img/submit-form.png" alt="Submit" />
      </button>
    </form>
  </body>
</html>
use Openbuildings\Spiderling\Page;

$page = new Page();

$page->visit('http://example.com/the-big-form');

$page
  ->fill_in('Name', 'New Name')
  ->uncheck('Feature One')
  ->check('Feature Two')
  ->choose('Closed')
  ->select('Type', array('text' => 'Type Small'))
  ->fill_in('Description', 'some description')
  ->click_button('Submit');

// This will return the submited action of the form,
// e.g. http://example.com/submit
echo $page->current_url();

嵌套

当页面上有多个元素时,你可能希望更加具体,Spiderling允许你通过嵌套节点来实现这一点 - 你可以在节点“内部”调用所有操作和查找器,这样查找器就会只在节点的子元素中进行搜索。

例如

use Openbuildings\Spiderling\Page;

$page = new Page();

$page->visit('http://example.com/the-big-form');

$page
  ->fill_in('Name', 'New Name')
  ->find('.row', array('text' => 'Type'))
    ->choose('Closed')
  ->end()
  ->click_button('Submit');

注意“end()”方法 - 这允许你返回到上一个级别并从那里继续工作。此外,你可以多次嵌套而不会出现任何问题(你将需要多次使用“end()”来“退出”嵌套)

use Openbuildings\Spiderling\Page;

$page = new Page();

$page->visit('http://example.com/the-big-form');

$page
  ->fill_in('Name', 'New Name')
  ->find('.row', array('text' => 'Type'))
    ->find('ul')
      ->choose('Closed')
    ->end()
  ->end()
  ->click_button('Submit');

其他

作为DSL的一部分,还有一些其他附加方法

  • confirm($confirm):如果页面上打开了一个警报或确认对话框,你可以使用此方法来关闭它(通过提供FALSE)或批准它(对于确认对话框,提供TRUE)
  • execute($script, $callback = NULL):在给定节点的作用域内执行任意JavaScript。您将能够将其作为回调的第一个参数访问,例如arguments[0]。JavaScript执行的结果将通过JSON序列化返回该方法。可选地,您可以提供一个回调,结果将是回调的第一个参数(第二个是节点本身)。
  • screenshot($file):获取页面当前状态的截图,将其作为PNG图像放置在文件中。

AJAX处理

Spiderling遵循Capybara相同的哲学,即它不明确支持或等待AJAX调用完成,但是每个查找器不会立即断定失败,如果元素未加载,则会等待一段时间(默认2秒)然后抛出异常。要利用这一点,在编写爬虫时,如果您有AJAX请求,则需要查找AJAX即将执行的改变。

例如

use Openbuildings\Spiderling\Page;

$page = new Page();

$page->visit('http://example.com/the-big-form');

$page
  ->click_button('Edit')
  // This will wait for the appearance of the "edit" form, loaded via AJAX
  ->find('h1', array('text' => 'Edit Form'))
    // Enter a new name inside the form
    ->fill_in('Name', 'New Name');
    ->click_button('Save')
  ->end();
  // We wait a bit to make sure the form is closed,
  // also as it might take longer than normal,
  // we extend the wait time from 2 to 4 seconds.
$page
  ->next_wait_time(4000)
  ->find('.notification', array('text' => 'Saved Successfully'));

驱动程序

Spiderling的一个强大之处在于能够为您的代码使用不同的驱动程序。这允许在不修改代码的情况下从仅使用PHP的curl解析页面切换到PhantomJS。例如,如果我们想使用PhantomJS驱动程序而不是默认的“Simple”驱动程序,那么我们需要这样做

use Openbuildings\Spiderling\Page;

$page = new Page(new Driver_Phantomjs);

$page->visit('http://example.com/the-big-form');

$page
  ->fill_in('Name', 'New Name')
  ->find('.row', array('text' => 'Type'))
    ->choose('Closed')
  ->end()
  ->click_button('Submit');

目前有4个驱动程序

  • Driver_Simple:使用PHP curl加载页面。不支持JavaScript或浏览器警告对话框。
  • Driver_Kohana:使用Kohana框架的本地Internal Request类,根本不打开互联网连接 - 如果您的代码已经使用Kohana框架,则性能非常出色。
  • Driver_Phantomjs:启动PhantomJS服务器。您需要安装PhantomJS并将其可访问地添加到PATH中。它随机选择一个新的端口,因此可以同时打开多个PhantomJS浏览器。

您可以通过扩展Driver类并实现自己的方法轻松编写自己的驱动程序。某些驱动程序可能不支持所有功能,因此不实现每个方法是可以的。

现在让我们详细了解一下每个驱动程序

Driver_Simple

使用curl加载HTML页面,然后使用PHP的本地DOM和XPath解析它。所有查找器都非常快,所以如果您不依赖于JavaScript或其他浏览器特定功能,那么这是您的最佳选择。它也很容易扩展,以便为特定的Web框架制作“本地”版本 - 您需要实现的是加载部分,例如可以在“Driver_Kohana”类中看到的一个示例。

在每个请求之前,$_GET、$_POST和$_FILES都会保存,填充适当的值,然后稍后恢复,模拟真实的PHP请求。

除了通过curl加载HTML外,您还可以直接设置内容,如果您已经通过其他方式加载了它。

以下是它的外观

use Openbuildings\Spiderling\Page;

$page = new Page();

$big_form_content = file_get_contents('big_content.html');

$page->content($big_form_content);

$page
  ->fill_in('Name', 'New Name')
  ->find('.row', array('text' => 'Type'))
    ->choose('Closed')
  ->end()
  ->click_button('Submit');

通常不建议自己执行POST请求,因为不是所有驱动程序都支持它们。但与Driver_Simple一起,您可以直接执行任意请求,例如测试API调用。这可以通过驱动程序直接完成,如下所示

use Openbuildings\Spiderling\Page;

$page = new Page();

$page->driver()->post('http://example.com/api/endpoint', array(), array('name' => 'some post value'));

Driver_Kohana

使用Kohana框架的本地Internal Request(对其进行轻微修改以欺骗框架,使其认为这是一个初始请求)。它扩展了Driver_Simple

它还处理重定向,限制它们最多为8次(可配置)并使用Request::$user_agent作为其User Agent。

示例使用

use Openbuildings\Spiderling\Page;

$page = new Page(new Driver_Kohana);

Driver_Phantomjs

使用此驱动程序,您可以使用PhantomJS执行所有查找和操作,使用真正的WebKit引擎和JavaScript,而无需任何图形环境(无头)。您需要将其安装到PATH中,可以通过调用“phantomjs”来访问。

您可以从这里下载它:http://phantomjs.org/download.html

默认情况下,它会在4445和5000之间的随机端口上启动一个新的服务器。

如果您已经安装了PhantomJS,则应该可以正常工作。

use Openbuildings\Spiderling\Page;

$page = new Page(new Driver_Phantomjs);

如果您想独立启动服务器,您可以修改PhantomJS连接,您还可以将其配置为将消息输出到日志文件,并且可以调整其他参数。

use Openbuildings\Spiderling\Page;

$connection = new Driver_Phantomjs_Connection;
$connection->port(5500);
$connection->start('pid_file', 'log_file');

$driver = new Driver_Phantomjs($connection);

$page = new Page();

在启动时设置“pid文件”参数,允许驱动程序将该PhantomJS服务器进程的pid保存到该文件,然后在再次启动时尝试清理服务器,从而确保您不会在各个地方都有运行的PhantomJS进程。

许可证

版权所有 (c) 2012-2013, OpenBuildings Ltd. 由Ivan Kerin作为clippings.com的一部分开发。

在BSD-3-Clause许可证下,请阅读LICENSE文件。