离框开发
1、爬山虎业务开发分两种:「离框开发」和「基于爬山虎应用框架开发」。
2、所谓的离框开发指的脱离爬山虎应用框架进行的自由定制开发。
3、注意:由于主机禁止出现敏感IP,请自行将 localhost 替换为 127.0.0.1。
将以下脚本保存到文件并命名为start.php
,然后执行:php start.php start
<?php
/**
* @script start.php
* @brief 这是脱离爬山虎应用框架的自定义启动脚本:
*
* 1. 本示例脚本是脱离爬山虎应用框架的自定义启动脚本;
* 2. 本示例脚本用于模拟抓取未来7天内北京的天气预报;
* 3. 如果希望使用爬山虎应用框架开发,请参考开发手册:
*
* >> http://www.phpcreeper.com/docs/
* >> http://www.blogdaren.com/docs/
*
* @author blogdaren<blogdaren@163.com>
* @link http://www.phpcreeper.com
* @create 2022-09-08
*/
//自动路由autoloader
$msg = PHP_EOL."找不到自动加载器autoloader, 请尝试运行: composer require blogdaren/phpcreeper".PHP_EOL.PHP_EOL;
false === ($files = @scandir(__DIR__, 1)) && exit($msg);
foreach($files as $k => $file){
if('vendor' === $file && is_dir(__DIR__ . DIRECTORY_SEPARATOR . $file)){
require_once dirname(__FILE__, 1) . "/vendor/autoload.php";break;
}elseif(false !== strpos(__DIR__, 'vendor/blogdaren/phpcreeper/Examples')){
require_once dirname(__FILE__, 5) . "/vendor/autoload.php";break;
}elseif(false !== strpos(__DIR__, 'Examples')){
require_once dirname(__FILE__, 2) . "/vendor/autoload.php";break;
}else{
(count($files) == ($k+1)) && exit($msg);
}
}
use PHPCreeper\PHPCreeper;
use PHPCreeper\Producer;
use PHPCreeper\Downloader;
use PHPCreeper\Parser;
use PHPCreeper\Server;
use PHPCreeper\Tool;
use PHPCreeper\Timer;
use PHPCreeper\Crontab;
use Logger\Logger;
/**
* enable the single worker mode so that we can run without redis, however, you should note
* it will be limited to run only all the downloader workers in this case【version >= 1.3.2】
* and the default is Multi-Worker run mode.
* 多worker运作模式开关,默认是多worker运作模式,支持两种运作模式【version >= 1.3.2】:
*
* 1、单worker运作模式:限定只能编写若干特定的downloader实例,即可完成所有的爬虫需求,
* 好处是开箱即用,不依赖redis服务,使用PHP内置队列,缺点是只能对付简单的爬虫需求;
* 2、多worker运作模式:支持自由编写任意多个业务worker实例,这是爬山虎默认的运作模式;
*/
//PHPCreeper::enableMultiWorkerMode(false);
/**
* switch runtime language between `zh` and `en`, default is `zh`【version >= 1.3.7】
* 多语言运行时环境开关:暂支持中文和英文,默认是中文【version >= 1.3.7】
*/
//PHPCreeper::setLang('en');
/**
* redirect all stdandard out to file when run as daemonize【version >= 1.7.0】
* 如果以守护进程方式运行,则所有向终端的输出(echo var_dump等)将会被重定向到指定的文件中;
* 如果以守护进程方式运行并且不设置,则所有终端输出将被重定向到/dev/null,即丢弃所有输出;
*/
//PHPCreeper::setStdoutFile("/tmp/stdout.log");
/**
* set the corresponding app log according to the component,
* and can also mask the log of the corresponding log level.
* 根据组件保存相应的应用日志,也可以屏蔽掉相应日志级别的日志。
*/
//PHPCreeper::setLogFile('/tmp/runtime.log');
//PHPCreeper::setLogFile('/tmp/runtime.log', 'producer');
//PHPCreeper::setLogFile('/tmp/runtime.log', 'downloader');
//PHPCreeper::setLogFile('/tmp/runtime.log', 'parser');
//PHPCreeper::disableLogLevel(['crazy','debug','info']);
//PHPCreeper::disableLogLevel(['crazy','debug','info'], 'producer');
//PHPCreeper::disableLogLevel(['crazy','debug','info', 'warn'], 'downloader');
/**
* set master pid file manually as needed【version >= 1.3.8】
* 设置主进程PID文件【version >= 1.3.8】
*/
//PHPCreeper::setMasterPidFile('master.pid');
/**
* note that `predis` will be the default redis client since【version >= 1.4.2】
* but you could still switch it to be `redis` if you prefer to use ext-redis
* 设置默认的redis客户端,默认为predis,也可切换为基于ext-redis的redis【version >= 1.4.2】
*/
//PHPCreeper::setDefaultRedisClient('redis');
/**
* set default timezone, default is `Asia/Shanghai`【version >= 1.5.4】
* 设置默认时区,默认为 Asia/Shanghai
*/
//PHPCreeper::setDefaultTimezone('Asia/Shanghai');
/**
* set default max file size to download, default is `0`【version >= 1.8.3】
* 设置默认最大下载文件大小, 默认为0, 如果大于0, 将会触发额外的HTTP.METHOD.HEAD请求.
* 注意:大文件机制尚待优化,当下载大文件遇到不可预期的问题时,尝试设置本参数为20MB.
*/
//PHPCreeper::setDefaultMaxFileSizeToDownload(20 * 1048576);
/**
* set default headless browser, default is `chrome`【version >= 1.8.7】
* 设置默认的无头浏览器,默认为 chrome,后续可能陆续支持 puppeteer 和 phantomjs.
*/
//PHPCreeper::setDefaultHeadlessBrowser('chrome');
/**
* Global-Redis-Config: just leave it alone when run as Single-Worker mode
* 仅单worker运作模式下不依赖redis,所以此时redis的配置可以忽略不管.
* 特别注意:自v1.6.4起,redis锁机制已升级并默认使用官方推荐的更安全的分布式红锁,
* 只有当所有的redis实例都显式的配置[use_red_lock === false]才会退化为旧版的锁机制.
*/
$config['redis'] = [
[
'host' => 'localhost',
'port' => 6379,
'database' => '0',
'auth' => false,
'pass' => 'guest',
'prefix' => 'PHPCreeper',
'connection_timeout' => 5,
'read_write_timeout' => 0,
//'use_red_lock' => true, //默认使用更安全的分布式红锁
],
/*[
'host' => 'localhost',
'port' => 6380,
'database' => '0',
'auth' => false,
'pass' => 'guest',
'prefix' => 'PHPCreeper',
'connection_timeout' => 5,
'read_write_timeout' => 0,
//'use_red_lock' => true, //默认使用更安全的分布式红锁
],*/
];
/**
* Global-Task-Config: the context member configured here is a global context,
* we can also set a private context for each task, finally the global context
* and task private context will adopt the strategy of merging and covering.
* free to customize various context settings, including user-defined.
*
* 注意: 此处配置的context是全局context上下文,我们也可以为每条任务设置私有context上下文,
* 其上下文成员完全相同,全局context与任务私有context最终采用合并覆盖的策略,具体参考手册。
* http://www.phpcreeper.com/docs/DevelopmentGuide/ApplicationConfig.html
* context上下文成员主要是针对任务设置的,但同时拥有很大灵活性,可以间接影响依赖性服务,
* 比如可以通过设置context上下文成员来影响HTTP请求时的各种上下文参数 (可选项,默认为空)
* HTTP引擎默认采用Guzzle客户端,兼容支持Guzzle所有的请求参数选项,具体参考Guzzle手册。
* 特别注意:个别上下文成员的用法是和Guzzle官方不一致的,一方面主要就是屏蔽其技术性概念,
* 另一方面面向开发者来说,关注点主要是能进行简单的配置即可,所以不一致的会注释特别说明。
*/
$config['task'] = array(
//任务爬取间隔,单位秒,最小支持0.001秒 (可选项,默认1秒)
//'crawl_interval' => 1,
//任务队列最大task数量, 0代表无限制 (可选项,默认0)
//'max_number' => 1000,
//当前Socket连接累计最大请求数,0代表无限制 (可选项,默认0)
//如果当前Socket连接的累计请求数超过最大请求数时,
//parser端会主动关闭连接,同时客户端会自动尝试重连
//'max_request' => 1000,
//限定爬取站点域,留空表示不受限
'limit_domains' => [],
//根据预期任务总量和误判率引擎会自动计算布隆过滤器最优的bitmap长度以及hash函数的个数
//'bloomfilter' => [
//'expected_insertions' => 10000, //预期任务总量
//'expected_falseratio' => 0.01, //预期误判率
//],
//全局任务context上下文 [注意每条任务都有各自的私有context上下文,最终采用合并覆盖策略]
'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,
//cookies成员的配置格式和guzzle官方不大一样,屏蔽了cookieJar,取值[false|array]
'cookies' => [
//'domain' => 'domain.com',
//'k1' => 'v1',
//'k2' => 'v2',
],
//除了内置参数之外,还可以自由配置自定义参数,在上下游业务链应用场景中十分有用
'user_define_key1' => 'user_define_value1',
'user_define_key2' => 'user_define_value2',
//无头浏览器,如果是动态页面考虑启用,否则应当禁用 [默认使用chrome且为禁用状态]
//更多其他无头参数详见手册[常见问题]章节
'headless_browser' => ['headless' => false],
//更多其他上下文参数详见官方手册
],
);
/**
* all components support distributed or separated deployment
* 所有组件支持分布式或分离式部署
*/
function startAppProducer()
{
global $config;
$producer = new Producer($config);
$producer->setName('AppProducer')->setCount(1);
//模拟抓取未来7天内北京的天气预报
$producer->onProducerStart = function($producer){
//任务私有context,其上下文成员与全局context完全相同,最终会采用合并覆盖策略
$private_task_context = [];
//在v1.6.0之前,爬山虎主要使用OOP风格的API来创建任务:
//$producer->newTaskMan()->setXXX()->setXXX()->createTask()
//$producer->newTaskMan()->setXXX()->setXXX()->createTask($task)
//$producer->newTaskMan()->setXXX()->setXXX()->createMultiTask()
//$producer->newTaskMan()->setXXX()->setXXX()->createMultiTask($task)
//自v1.6.0开始,爬山虎提供了更加短小便捷的API来创建任务, 而且参数类型更加丰富:
//注意:仅仅只是扩展,原有的API依然可以正常使用,提倡扩展就是为了保持向下兼容。
//1. 单任务API:$task参数类型可支持:[字符串 | 一维数组]
//2. 单任务API:$producer->createTask($task);
//3. 多任务API:$task参数类型可支持:[字符串 | 一维数组 | 二维数组]
//4. 多任务API:$producer->createMultiTask($task);
//使用字符串:不推荐使用,配置受限,需要自行处理抓取结果
//$task = "http://www.weather.com.cn/weather/101010100.shtml";
//$producer->createTask($task);
//$producer->createMultiTask($task);
//使用一维数组:推荐使用,配置丰富,引擎内置处理抓取结果
$task = array(
'active' => true, //是否激活当前任务,只有配置为false才会冻结任务,默认true
'url' => "http://www.weather.com.cn/weather/101010100.shtml",
'rule' => array( //如果该字段留空默认将返回原始下载数据
'time' => ['div#7d ul.t.clearfix h1', 'text', [], 'function($field_name, $data){
return "具体日子: " . $data;
}'], //关于回调字符串的用法务必详看官方手册
'wea' => ['div#7d ul.t.clearfix p.wea', 'text'],
'tem' => ['div#7d ul.t.clearfix p.tem', 'text'],
),
'rule_name' => '', //如果留空将使用md5($task_id)作为规则名
'refer' => '',
'type' => 'text', //已丧失原本的概念设定,可以自由设定类型
'method' => 'get',
'context' => $private_task_context,
);
$producer->createTask($task);
$producer->createMultiTask($task);
//使用二维数组: 推荐使用,配置丰富,因为是多任务,所以只能调用createMultiTask()接口
$task = array(
array(
"url" => "http://www.weather.com.cn/weather/101010100.shtml",
'rule' => array(
'time' => ['div#7d ul.t.clearfix h1', 'text'],
'wea' => ['div#7d ul.t.clearfix p.wea', 'text'],
'tem' => ['div#7d ul.t.clearfix p.tem', 'text'],
),
'rule_name' => 'r1', //如果留空将使用md5($task_id)作为规则名
"context" => $private_task_context,
),
array(
"url" => "http://www.weather.com.cn/weather/201010100.shtml",
'rule' => array(
'time' => ['div#7d ul.t.clearfix h1', 'text'],
'wea' => ['div#7d ul.t.clearfix p.wea', 'text'],
'tem' => ['div#7d ul.t.clearfix p.tem', 'text'],
),
'rule_name' => 'r2', //如果留空将使用md5($task_id)作为规则名
"context" => $private_task_context,
),
);
$producer->createMultiTask($task);
//使用无头浏览器爬取动态页面
$private_task_context['headless_browser']['headless'] = true;
$dynamic_task = array(
'url' => 'https://www.toutiao.com',
'rule' => array(
'title' => ['div.show-monitor ol li a', 'aria-label'],
'link' => ['div.show-monitor ol li a', 'href'],
),
'context' => $private_task_context,
);
$producer->createTask($dynamic_task);
};
}
/**
* all components support distributed or separated deployment
* 所有组件支持分布式或分离式部署
*/
function startAppDownloader()
{
global $config;
$downloader = new Downloader($config);
//$downloader->setTaskCrawlInterval(5);
$downloader->setName('AppDownloader')->setCount(1)->setClientSocketAddress([
'ws://localhost:8888',
]);
$downloader->onDownloaderStart = function($downloader){
};
//回调【onBeforeDownload】的新增别名是【onDownloadBefore】
$downloader->onDownloadBefore = function($downloader, $task){
//disable http ssl verify in any of the following two ways
//$downloader->httpClient->disableSSL();
//$downloader->httpClient->setOptions(['verify' => false]);
};
//回调【onStartDownload】的新增别名是【onDownloadStart】
$downloader->onDownloadStart = function($downloader, $task){
};
//回调【onAfterDownload】的新增别名是【onDownloadAfter】
$downloader->onDownloadAfter = function($downloader, $data, $task){
//Tool::debug($content, $json = true, $append = true, $filename = "debug", $base_dir = "")
//Tool::debug($task);
};
//回调【onFailDownload】的新增别名是【onDownloadFail】
$downloader->onDownloadFail = function($downloader, $error, $task){
//pprint($error, $task);
};
$downloader->onTaskEmpty = function($downloader){
};
//使用无头浏览器回调或者直接使用无头浏览器相关API
$downloader->onHeadlessBrowserOpenPage = function($downloader, $browser, $page, $url){
//注意:灵活设计特定类型的返回值有助于对付各种复杂的应用场景
//1. 返回false, 会触发中断后续的业务逻辑;
//2. 返回string,会触发中断后续的业务逻辑,一般多用于返回页面的HTML;
//3. 返回array, 会继续执行后续的业务逻辑,一般多用于返回无头浏览器选项参数;
//4. 返回其他, 会继续执行后续的业务逻辑,相当于是什么也没有发生;
//注意:一般无需调用如下几行代码,因为爬山虎内部默认会自动调用无头API做同样的工作.
//$page->navigate($url)->waitForNavigation('firstMeaningfulPaint');
//$html = $page->getHtml();
//return $html;
};
}
/**
* all components support distributed or separated deployment
* 所有组件支持分布式或分离式部署
*/
function startAppParser()
{
$parser = new Parser();
$parser->setName('AppParser')->setCount(1);
$parser->setServerSocketAddress('websocket://0.0.0.0:8888');
$parser->onParserExtractField = function($parser, $download_data, $fields){
pprint($fields);
};
}
/**
* General Server independ on [Producer|Downloader|Parser]
* 通用型服务器组件,完全独立于[Producer|Downloader|Parser]
*/
function startAppServer()
{
$server = new Server();
$server->onServerStart = function(){
/*
* just show how to use Linux-Style Crontab:
*
* (1) the only difference is that support the second-level;
* (2) the minimum time granularity is minutes if the second bit is omitted;
*
* the formatter looks like as below:
*
* 0 1 2 3 4 5
* | | | | | |
* | | | | | +------ day of week (0 - 6) (Sunday=0)
* | | | | +------ month (1 - 12)
* | | | +-------- day of month (1 - 31)
* | | +---------- hour (0 - 23)
* | +------------ min (0 - 59)
* +-------------- sec (0-59)[可省略,如果没有0位,则最小时间粒度是分钟]
*
* 防止重复造轮子且图省事完全照搬了walkor大大的workerman-crontab而来,
* 很小巧且为了方便所以将此库脱离了composer库并揉进了PHPCreeper内核,
* 高仿Linux风格的Crontab,语法层面除了支持秒级以外,其余用法基本一致,
* 所以平时crontab怎么用现在就怎么用,具体用法请参照workerman官方手册:
* https://www.workerman.net/doc/webman/components/crontab.html
*/
//每隔1秒执行一次任务
new Crontab('*/1 * * * * *', function(){
pprint("模拟每隔1秒打印一下当前时间:" . Tool::getHumanTime());
});
//每隔2分钟执行一次任务
new Crontab('*/2 * * * *', function(){
pprint("模拟每隔2分钟打印一下当前时间:" . Tool::getHumanTime());
});
};
}
//启动生产器组件
startAppProducer();
//启动下载器组件
startAppDownloader();
//启动解析器组件
startAppParser();
//启动通用型服务器组件,可按需自由定制一些服务,
//完全独立于 [Producer|Downloader|Parser] 组件.
startAppServer();
//启动爬山虎引擎
PHPCreeper::start();