前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >高性能PHP框架webman爬虫引擎插件

高性能PHP框架webman爬虫引擎插件

作者头像
Tinywan
发布2024-10-08 10:33:19
800
发布2024-10-08 10:33:19
举报
文章被收录于专栏:开源技术小栈

概述

PHPCreeper,中文名《爬山虎》,是一个专注于高效敏捷开发的爬虫引擎。它不仅简化了爬取工作的复杂性,还解决了传统PHP爬虫框架在性能和扩展性上的瓶颈问题。通过充分利用多进程、分布式和分离式部署环境,PHPCreeper能够支持无头浏览器,执行JavaScript代码以爬取动态页面,极大地提升了爬取效率和灵活性。

PHPCreeper的核心技术架构基于workerman,这是一个高性能的PHP socket服务器框架。PHPCreeper继承了Workerman的所有特性,并在此基础上增加了对无头浏览器的支持,使得爬取动态页面成为可能。此外,它还支持类似Linux Crontab的定时任务、分布式部署以及灵活的回调和第三方中间件定制。

特性

传统的PHP爬虫框架普遍有两大不足:一个是大多为单进程工作模型;另一个是大多为单机或同步工作模式,换句话即看不到 socket 的身影, 因此无法做到分布式及分离式部署,因此无法最大化发挥爬取性能,而爬山虎是基于 workerman 开发的, 能够轻松支持如下架构:异步IO + 多进程 + 分布/分离式部署 + 事件驱动模型, 从而保证爬山虎发挥最大化性能【不过作者认为:性能其实在爬虫领域内没有突出意义,而且有限的政策环境也使性能蒙上了物理阴影】;此外爬山虎采用了微内核 + 插件 + 分离式部署的设计理念,所以具备强扩展性。

安装

该插件依赖与webman框架,如果已安装跳过即可。

安装文档:https://www.workerman.net/doc/webman/install.html

爬山虎插件安装

代码语言:javascript
复制
composer require blogdaren/webman-phpcreeper

插件地址:https://www.workerman.net/plugin/39

快速开始

案例场景:模拟抓取未来3天内北京的天气预报

创建爬虫目录

代码语言:javascript
复制
mkdir app/spider

这里的app即webman的应用目录

创建生产器

句柄类文件app/spider/TinywanProducer.php

代码语言:javascript
复制
<?php
/**
 * @desc TinywanProducer.php 描述信息
 * @author Tinywan(ShaoBo Wan)
 * @date 2024/10/5 10:02
 */
declare(strict_types=1);

namespace app\spider;

use PHPCreeper\Kernel\PHPCreeper;
use Webman\PHPCreeper\Producer;

class TinywanProducer extends Producer
{
    /**
     * @brief   生产任务
     */
    public function makeTask()
    {
        //任务私有context,其上下文成员与全局context完全相同,最终会采用合并覆盖策略
        $private_task_context = [
            //是否缓存下载数据(可选项,默认false)
            'cache_enabled' => true,
            //缓存下载数据存放目录  (可选项,默认位于系统临时目录下)
            'cache_directory' => sys_get_temp_dir() . '/DownloadCache4PHPCreeper/',
            //在特定的生命周期内是否允许重复抓取同一个URL资源(可选项,默认false)
            'allow_url_repeat' => true,
            //要不要跟踪完整的HTTP请求参数,开启后终端会显示完整的请求参数 [默认false]
            'track_request_args' => true,
            //要不要跟踪完整的TASK数据包,开启后终端会显示完整的任务数据包 [默认false]
            'track_task_package' => true,
            //在v1.6.0之前,如果rulename留空,默认会使用 md5($task_url)作为rulename
            //自v1.6.0开始,如果rulename留空,默认会使用 md5($task_id) 作为rulename
            //所以这个配置参数是仅仅为了保持向下兼容,但是不推荐使用,因为有潜在隐患
            //换句话如果使用的是v1.6.0之前旧版本,那么才有可能需要激活本参数 [默认false]
            'force_use_md5url_if_rulename_empty' => false,
            //强制使用多任务创建API的旧版本参数风格,保持向下兼容,不再推荐使用 [默认false]
            'force_use_old_style_multitask_args' => false,
            //设置http请求头:默认引擎会自动伪装成常见的各种随机User-Agent
            'headers' => [
                //'User-Agent' => 'Mozilla/5.0 Chrome/124.0.0.0 Safari/537.36',
                //'Accept'     => 'text/html,*/*',
            ],
            //cookies成员的配置格式和guzzle官方不大一样,屏蔽了cookieJar,取值[false|array]
            'cookies' => [
                //'domain' => 'domain.com',
                //'k1' => 'v1',
                //'k2' => 'v2',
            ],
            //无头浏览器,如果是动态页面考虑启用,否则应当禁用 [默认使用chrome且为禁用状态]
            'headless_browser' => ['headless' => false, /*更多其他无头参数*/],
            //除了内置参数之外,还可以自由配置自定义参数,在上下游业务链应用场景中十分有用
            'user_define_arg1' => 'user_define_value1',
            'user_define_arg2' => 'user_define_value2',
            //更多其他上下文参数详见官方手册
        ];

        $task = [
            'active' => true,       //是否激活当前任务,只有配置为false才会冻结任务,默认true
            'url' => 'http://www.weather.com.cn/weather/101010100.shtml',
            "rule" => array(        //如果该字段留空默认将返回原始下载数据
                '日子' => ['div#7d ul.t.clearfix h1', 'text', [], 'function($field_name, $data){
                    return  date("Y-m-d") . " | " . $data; 
                }'],                //关于回调字符串的用法务必详看官方手册
                '天气' => ['div#7d ul.t.clearfix p.wea', 'text'],
                '温度' => ['div#7d ul.t.clearfix p.tem', 'text'],
            ),
            'rule_name' => '',     //如果留空将使用md5($task_id)作为规则名
            'refer' => '',
            'type' => 'text', //已丧失原本的概念设定,可以自由设定类型
            'method' => 'get',
            'context' => $private_task_context,
        ];

        $this->createTask($task);
    }

    /**
     * @brief    使用无头浏览器爬JavaScript渲染的取动态页面
     *
     * @return   mixed
     */
    public function makeDynamicTask()
    {
        $private_task_context = [
            //是否缓存下载数据(可选项,默认false)
            'cache_enabled' => true,
            //缓存下载数据存放目录  (可选项,默认位于系统临时目录下)
            'cache_directory' => sys_get_temp_dir() . '/DownloadCache4PHPCreeper/',
            //无头浏览器,如果是动态页面考虑启用,否则应当禁用 [默认使用chrome且为禁用状态]
            'headless_browser' => ['headless' => true, /*更多其他无头参数*/],
        ];

        $dynamic_task = [
            'url' => "https://www.toutiao.com",
            'rule' => [
                '今日头条热榜标题' => ['div.show-monitor ol li a', 'aria-label'],
                '今日头条热榜链接' => ['div.show-monitor ol li a', 'href'],
            ],
            'context' => $private_task_context,
        ];

        $this->createTask($dynamic_task);
    }

    /**
     * @brief    onProducerStart
     * @param PHPCreeper $producer
     * @return   void
     */
    public function onProducerStart(PHPCreeper $producer)
    {
        $this->makeTask();
        $this->makeDynamicTask();

        //使用Timer定时器创建任务
        //Timer::add(5, [$this, "makeTask"], [], true);

        //使用Crontab定时器创建任务
        //new Crontab('*/5 * * * * *', function(){
        //$this->makeTask();
        //});
    }

    /**
     * @brief    onProducerStop
     * @param PHPCreeper $producer
     * @return   void
     */
    public function onProducerStop(PHPCreeper $producer)
    {
    }

    /**
     * @brief    onProducerReload
     * @param PHPCreeper $producer
     * @return   void
     */
    public function onProducerReload(PHPCreeper $producer)
    {
    }
}

创建下载器

句柄类文件app/spider/TinywanDownloader.php

代码语言:javascript
复制
<?php
/**
 * @desc TinywanDownloader.php 描述信息
 * @author Tinywan(ShaoBo Wan)
 * @date 2024/10/5 10:06
 */
declare(strict_types=1);

namespace app\spider;

use PHPCreeper\Kernel\PHPCreeper;
use Webman\PHPCreeper\Downloader;

class TinywanDownloader extends Downloader
{
    /**
     * @brief    onDownloaderStart
     *
     * @param PHPCreeper $downloader
     *
     * @return   void
     */
    public function onDownloaderStart(PHPCreeper $downloader)
    {
        $downloader->setClientSocketAddress([
            'ws://127.0.0.1:8888',
        ]);
    }

    /**
     * @brief    onDownloaderStop
     * @param PHPCreeper $downloader
     * @return   void
     */
    public function onDownloaderStop(PHPCreeper $downloader)
    {
    }

    /**
     * @brief    onDownloaderReload
     * @param PHPCreeper $downloader
     */
    public function onDownloaderReload(PHPCreeper $downloader)
    {
    }

    /**
     * @brief    onDownloaderMessage
     *
     * @param PHPCreeper $downloader
     * @param string $parser_reply
     */
    public function onDownloaderMessage(PHPCreeper $downloader, string $parser_reply)
    {
        //pprint($parser_reply, __METHOD__);
    }

    /**
     * @brief    onBeforeDownload
     * @param PHPCreeper $downloader
     * @param array $task
     * @return   mixed
     */
    public function onBeforeDownload(PHPCreeper $downloader, array $task)
    {
        //$downloader->httpClient->setConnectTimeout(3);
        //$downloader->httpClient->setTransferTimeout(10);
        //$downloader->httpClient->setHeaders(array());
        //$downloader->httpClient->setProxy('http://127.0.0.1:8800');
    }

    /**
     * @brief    onStartDownload
     *
     * @param PHPCreeper $downloader
     * @param array $task
     * @return   void
     */
    public function onStartDownload(PHPCreeper $downloader, array $task)
    {
    }

    /**
     * @brief    onAfterDownload
     *
     * @param PHPCreeper $downloader
     * @param array $download_data
     * @param array $task
     *
     * @return   mixed
     */
    public function onAfterDownload(PHPCreeper $downloader, array $download_data, array $task)
    {
        //pprint($downloader->getDbo('test'), __METHOD__);
    }

    /**
     * @brief    onTaskEmpty
     * @param PHPCreeper $downloader
     */
    public function onTaskEmpty(PHPCreeper $downloader)
    {
        //$downloader->createTask($task);
    }

    /**
     * @brief    onHeadlessBrowserOpenPage
     *
     * @param PHPCreeper $downloader
     * @param object $browser
     * @param object $page
     * @param string $url
     *
     * @return   mixed
     */
    public function onHeadlessBrowserOpenPage(PHPCreeper $downloader, $browser, $page, $url)
    {
        //注意:灵活设计特定类型的返回值有助于对付各种复杂的应用场景
        //1. 返回false, 会触发中断后续的业务逻辑;
        //2. 返回string,会触发中断后续的业务逻辑,一般多用于返回页面的HTML;
        //3. 返回array, 会继续执行后续的业务逻辑,一般多用于返回无头浏览器选项参数;
        //4. 返回其他,  会继续执行后续的业务逻辑,相当于是什么也没有发生;

        //注意:一般无需调用如下几行代码,因为爬山虎内部默认会自动调用无头API做同样的工作.
        //$page->navigate($url)->waitForNavigation('firstMeaningfulPaint');
        //$html = $page->getHtml();
        //return $html;
    }
}

创建解析器

句柄类文件app/spider/TinywanParser.php

代码语言:javascript
复制
<?php
/**
 * @desc TinywanParser.php 描述信息
 * @author Tinywan(ShaoBo Wan)
 * @date 2024/10/5 10:06
 */
declare(strict_types=1);

namespace app\spider;

use Webman\PHPCreeper\Parser;

class TinywanParser extends Parser
{
    /**
     * @brief    onParserStart
     *
     * @param    object $parser
     *
     * @return   mixed
     */
    public function onParserStart($parser)
    {
    }

    /**
     * @brief    onParserStop
     *
     * @param    object $parser
     *
     * @return   mixed
     */
    public function onParserStop($parser)
    {
    }

    /**
     * @brief    onParserReload
     *
     * @param    object $parser
     *
     * @return   mixed
     */
    public function onParserReload($parser)
    {
    }

    /**
     * @brief    onParserMessage
     *
     * @param    object $parser
     * @param    object $connection
     * @param    string $download_data
     *
     * @return   mixed
     */
    public function onParserMessage($parser, $connection, $download_data)
    {
        //pprint(strlen($download_data), __METHOD__);
    }

    /**
     * @brief    onParserFindUrl
     *
     * @param    object $parser
     * @param string $url
     *
     * @return   string
     */
    public function onParserFindUrl($parser, string $url)
    {
        return $url;
    }

    /**
     * @brief    onParserExtractField
     *
     * @param    object $parser
     * @param    string $download_data
     * @param    array  $fields
     *
     * @return   mixed
     */
    public function onParserExtractField($parser, $download_data, $fields)
    {
        !empty($fields) && pprint($fields[$parser->task['rule_name']]);
    }
}

配置

自定义进程配置

配置文件路径config/plugin/blogdaren/webman-phpcreeper/process.php

修改为以下配置

代码语言:javascript
复制
<?php
return [
    'myproducer' => [
        'handler'     => \app\spider\TinywanProducer::class,
        'listen'      => '',
        'count'       => 1,
        'constructor' => ['config' => 
            include('spider/global.php')
        ],
    ],
    'mydownloader' => [
        'handler'     => \app\spider\TinywanDownloader::class,
        'listen'      => '',
        'count'       => 1,
        'constructor' => ['config' => 
            include('spider/global.php')
        ],
    ],
    'myparser' => [
        'handler'     => \app\spider\TinywanParser::class,
        'listen'      => 'websocket://0.0.0.0:8888',
        'count'       => 1,
        'constructor' => ['config' => 
            include('spider/global.php')
        ],
    ],
];


Redis配置

配置文件路径config/plugin/blogdaren/webman-phpcreeper/spider/database.php

修改为自己的Redis配置就可以啦

代码语言:javascript
复制
<?php

return array(
    'redis' => array(
        'host'      =>  'dnmp-redis',
        'port'      =>  63789,
        'database'  =>  '0',
        'auth'      =>  false,
        'pass'      =>  'guest',
        'prefix'    =>  'PHPCreeper', 
        'connection_timeout' => 5,
        'read_write_timeout' => 0,
    ),
);

由于以多worker模式运行时依赖Redis服务,所以需要配置Redis服务,否则会有以下错误提示

代码语言:javascript
复制
| ERROR | 服务巡检:Connection refused [tcp://dnmp-redis:63798], 
以多worker模式运行时依赖[redis-server]服务, 
请检查[redis-server]服务有没有启动 或 防火墙有没有放行服务端口, 当前进程将在休眠10秒之后自动退出并重启后继续运行.

开始服务

确保以上配置都没问题,就可以开启webman服务进行爬虫了

开启服务

爬虫数据

代码语言:javascript
复制
Array
(
    [0] => Array
        (
            [日子] => 2024-10-05 | 5日(今天)
            [天气] => 多云转阴
            [温度] => 21/13℃
        )

    [1] => Array
        (
            [日子] => 2024-10-05 | 6日(明天)
            [天气] => 小雨
            [温度] => 18/10℃
        )

    [2] => Array
        (
            [日子] => 2024-10-05 | 7日(后天)
            [天气] => 晴
            [温度] => 21/8℃
        )

    [3] => Array
        (
            [日子] => 2024-10-05 | 8日(周二)
            [天气] => 晴转多云
            [温度] => 21/9℃
        )

    [4] => Array
        (
            [日子] => 2024-10-05 | 9日(周三)
            [天气] => 多云转晴
            [温度] => 22/12℃
        )

    [5] => Array
        (
            [日子] => 2024-10-05 | 10日(周四)
            [天气] => 晴
            [温度] => 21/10℃
        )

    [6] => Array
        (
            [日子] => 2024-10-05 | 11日(周五)
            [天气] => 晴
            [温度] => 22/10℃
        )

)

其他下载

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-10-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源技术小栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 特性
  • 安装
  • 快速开始
    • 创建生产器
      • 创建下载器
        • 创建解析器
        • 配置
          • 自定义进程配置
            • Redis配置
            • 开始服务
            相关产品与服务
            云数据库 Redis®
            腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档