我想在PHP中进行网页抓取

在搜索“爬虫入门”时,会发现大量推荐使用Python,这是因为Python拥有丰富的信息和优秀的库。不过,并不意味着只有Python才能进行爬虫。由于最近的网络环境,爬虫通常需要与无头浏览器配合使用,只要能编写操作浏览器的脚本,使用何种编程语言并不重要。

因为在 PHP 中进行网页爬取的人似乎并不多,即使进行了调查,所获得的信息也很陈旧(或者很过时),没有太大的参考价值,所以我只是随手写了一个实现的例子。

目标 (mù

用 PHP 开发一个爬虫,通过网络爬取网站信息。

使用中的库

Guzzle : HTTP クライアント

PHP DOM Wrapper : DOM 操作

Chrome PHP : ヘッドレス Chrome 操作

WorkerPool : 並列処理

composer require guzzlehttp/guzzle scotteh/php-dom-wrapper chrome-php/chrome qxsch/worker-pool

我要感谢图书馆的创作者?

小心事项

    • スクレイピングは対象のサイトに過剰な負荷をかけないように節度を守って行いましょう。

 

    • スクレイピングが禁止されているサイトでは行わないようにしましょう。

 

    プログラムのバグで対象のサイトに過剰な負荷をかけないように、ローカル環境でのテストやデバッグを徹底しましょう。

以下介绍的代码针对的是实际上不存在的 URL,因此在进行实际测试时请适时替换。

实例

我想從 URL 中下載 HTML,並提取所需的信息。

爬虫是解析包含所需信息的文档,定位信息的位置,并提取出来的过程。文档从URL下载(本例中针对HTML文档)。

下载时使用Guzzle,HTML解析器使用PHP DOM封装器。

<?php

require_once __DIR__ . '/vendor/autoload.php';

use DOMWrap\Document;
use GuzzleHttp\Client;

// ここに書かれている URL はダミーです
// クロールリストは実際にはデータベースから取ってくるとか
$sites = [
    [
        'url' => 'https://dev.wazly.net/',
        'selector' => '#news > div > dl:nth-child(1) > dd > p > a',
    ],
    [
        'url' => 'https://info.wazly.net/news',
        'selector' => '#root > div.contents > div.main > ul > li:nth-child(1) > a > h4'
    ]
];

$client = new Client;

foreach ($sites as $site) {
    $response = $client->get($site['url']);
    $html = (string) $response->getBody();
    $doc = new Document;
    $node = $doc->html($html);

    // 取得した文字列
    $text = $node->find($site['selector'])->text();

    // 通知したりデータベースに追加したり
    echo $text, PHP_EOL;
}

根据情况,可以为 HTTP 请求头设置适当的值(例如,在查看移动设备的网站时更改用户代理)。

PHP DOM包装器不仅可以使用CSS选择器来指定元素,还可以像jQuery一样处理DOM。

我想要支持动态网站。

并不保证所要获取的信息在要爬取的网页的HTML中已经存在。许多网站在页面加载后会通过JavaScript从服务器上下载信息。此外,有时候需要进行一些操作(如点击按钮)才能提取到信息。

在这种情况下,您需要打开浏览器并访问网站来执行动作。当然,我们会使用PHP脚本来控制浏览器的启动、关闭和操作,并自动化进行。

以下的代码是关于使用Chrome PHP和Google Chrome的无头模式在macOS上进行爬虫和抓取的示例:

<?php

require_once __DIR__ . '/vendor/autoload.php';

use DOMWrap\Document;
use HeadlessChromium\BrowserFactory;

// ここに書かれている URL はダミーです
// クロールリストは実際にはデータベースから取ってくるとか
$sites = [
    [
        'url' => 'https://dev.wazly.net/',
        'selector' => '#news > div > dl:nth-child(1) > dd > p > a',
    ],
    [
        'url' => 'https://info.wazly.net/news',
        'selector' => '#root > div.contents > div.main > ul > li:nth-child(1) > a > h4'
    ]
];

foreach ($sites as $site) {
    $browserFactory = new BrowserFactory(
        // 実行するブラウザのパスに応じて変更
        // https://github.com/chrome-php/headless-chromium-php/issues/75
        '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome'
    );
    $browser = $browserFactory->createBrowser();
    $page = $browser->createPage();
    $page->navigate($site['url'])->waitForNavigation();
    $evaluation = $page->evaluate('document.documentElement.innerHTML');
    $value = $evaluation->getReturnValue();
    $browser->close();

    $doc = new Document;
    $node = $doc->html($value);

    // 取得した文字列
    $text = $node->find($site['selector'])->text();

    // 通知したりデータベースに追加したり
    echo $text, PHP_EOL;
}

您可以通过不下载图片来节省资源,或者进行屏幕截图等操作。

希望同时进行以节省时间。

由于页面加载等原因,对于动态网站的抓取需要花费很长时间。等待当前打开的浏览器结束后再打开下一个浏览器的做法效率太低。因此我们要打开多个浏览器并行处理它们(请避免同时对一个网站进行多个访问)。

以下是使用WorkerPool进行并行爬取的示例代码:

<?php

require_once __DIR__ . '/vendor/autoload.php';

use DOMWrap\Document;
use QXS\WorkerPool\WorkerPool;
use QXS\WorkerPool\ClosureWorker;
use HeadlessChromium\BrowserFactory;

// ここに書かれている URL はダミーです
// クロールリストは実際にはデータベースから取ってくるとか
$sites = [
    [
        'url' => 'https://dev.wazly.net/',
        'selector' => '#news > div > dl:nth-child(1) > dd > p > a',
    ],
    [
        'url' => 'https://info.wazly.net/news',
        'selector' => '#root > div.contents > div.main > ul > li:nth-child(1) > a > h4'
    ]
];

$wp = new WorkerPool;

$wp->setWorkerPoolSize(2) // 同時実行する数
    ->create(new ClosureWorker(
        function ($input, $semaphore, $storage) {
            $browserFactory = new BrowserFactory('/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome');
            $browser = $browserFactory->createBrowser();
            $page = $browser->createPage();
            echo 'Browsing ' . $input['url'], PHP_EOL;
            $page->navigate($input['url'])->waitForNavigation();
            $evaluation = $page->evaluate('document.documentElement.innerHTML');
            $value = $evaluation->getReturnValue();
            echo 'Leaving ' . $input['url'], PHP_EOL;
            $browser->close();

            $doc = new Document;
            $node = $doc->html($value);

            // 取得した文字列
            return $node->find($input['selector'])->text();
        }
    ));

foreach ($sites as $site) {
    $wp->run($site);
}

$wp->waitForAllWorkers();

foreach ($wp as $val) {
    // 通知したりデータベースに追加したり
    echo $val['data'], PHP_EOL;
}

当执行此操作时

Browsing https://info.wazly.net/news
Browsing https://dev.wazly.net/

这两行同时输出,然后有一段时间间隔。

Leaving https://dev.wazly.net/
Leaving https://info.wazly.net/news

每一行都会以不同的顺序进行输出,最先完成处理的将首先输出。最后输出的是获取的结果。

根据执行环境来调整并行数,以充分利用资源,并且因为并行处理可以极大地加快完成速度,所以最好选择并行处理。

总结和感想

在PHP中也可以进行网络爬虫。而且如果尝试一下,会发现它出奇地简单和实用。这也归功于库的便利。除了Guzzle之外,GitHub上的星数大多只有两到三位数,并不那么有名。但是,将它们组合起来发挥协同作用会很有趣。如果你平时经常写PHP并且想尝试爬取网页数据的话,一定要试试看?

广告
将在 10 秒后关闭
bannerAds