多级页面爬取

在有些情况,我们需要先爬取列表页,再去爬取详情页。

这种情况我们建议的实现方法是,创建两个XCrawler实例去爬取:

  • 第一个用来来爬取列表,并把详情页的链接入队列。
  • 第二个出队列去爬取详情。

这样做的好处是:

  • 代码可读性(可维护性)大大提升
  • 更好的可测试性

示例:

这里我们给出一个示例:爬取电影天堂“2018新片精品”板块中的影片名称、URL、发布时间、以及磁力链接。(磁力链接需要到详情页爬取)

完整代码如下:

<?php
require 'vendor/autoload.php';

use XCrawler\XCrawler;
use Symfony\Component\DomCrawler\Crawler;

// 爬取dytt8影片列表
$xcrawler = new XCrawler([
    'name' => 'dytt8:index',
    'requests' => function() {
        $url = 'http://www.dytt8.net/';
        yield $url;
    },
    'success' => function($result, $request, $xcrawler, $res_headers) {
        // 把html的编码从gbk转为utf-8
        $result = iconv('GBK', 'UTF-8', $result);
        $crawler = new Crawler();
        $crawler->addHtmlContent($result);

        $list = [];
        // 通过css选择器遍历影片列表
        $tr_selector = '#header > div > div.bd2 > div.bd3 > div:nth-child(2) > div:nth-child(1) > div > div:nth-child(2) > div.co_content8 tr';
        $crawler->filter($tr_selector)->each(function (Crawler $node, $i) use (&$list) {
            $name = dom_filter($node, 'a:nth-child(2)', 'html');
            if (empty($name)) {
                return;
            }
            $url = 'http://www.dytt8.net'.dom_filter($node, 'a:nth-child(2)', 'attr', 'href');

            $data = [
                'name' => $name,
                'url' => $url,
                'time' => dom_filter($node, '.inddline font', 'html'),
            ];
            // 把影片url、name推送到redis队列,以便进一步爬取影片下载链接
            redis()->lpush('dytt8:detail_queue', json_encode($data));
            $list[] = $data;
        });
        var_dump($list);
    }
]);
$xcrawler->run();

// 爬取dytt8影片详情
$xcrawler = new XCrawler([
    'name' => 'dytt8:detail',
    'concurrency' => 3,
    'requests' => function() {
        while ($data = redis()->rpop('dytt8:detail_queue')) {
            $data = json_decode($data, true);
            $request = [
                'uri' => $data['url'],
                'callback_data' => $data,
            ];
            yield $request;
        }
    },
    'success' => function($result, $request, $xcrawler) {
        $result = iconv('GBK', 'UTF-8', $result);
        $crawler = new Crawler();
        $crawler->addHtmlContent($result);

        $data = $request['callback_data'];
        $crawler->filter('td[style="WORD-WRAP: break-word"] a')->each(function (Crawler $node, $i) use (&$data) {
            $data['download_links'][] = $node->attr('href');
        });
        var_dump($data);
    }
]);
$xcrawler->run();

小建议

在实际开发中,我们更加建议把不同类型页面的爬虫,分别单独创建一个文件(命令)。每一个不同类型页面的爬虫可以单独执行,也可以在另一个爬虫中被调用。

因为这样做会有更好的可测试性:你可以单独对每一个爬虫进行调试或修改。