常见问题汇总
01、在哪里编写业务代码?
在每个应用的Start目录【/path/to/PHPCreeper-Application/Application/Spider/爬虫项目名称/Start/】下面列有不同的应用实例启动脚本,
这些脚本里分别对应有各自的onXXX业务回调,所以说除了基础的配置代码之外,所有的业务代码都是在onXXX回调里编写。
02、合理设计回调返回值有助于对付各种复杂应用场景
举个例子:比如使用无头浏览器回调API:
<?php
$downloader = new Downloader();
$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;
};
03、是否可以脱离爬山虎应用框架进行自由定制开发?
当然可以,PHPCreeper-Application【即爬山虎应用框架】类似于传统的MVC框架的意义,主要是方便开发者进行敏捷开发,所以说这个框架并不是必须的,不过作者推荐使用爬山虎应用框架进行开发,相对更加快捷高效;当然啦,开发者也完全可以单独引入爬山虎引擎,即脱离爬山虎应用框架进行自由定制开发,一样简单高效。
04、如何脱离爬山虎应用框架进行自由定制开发?
请参考第5.2章节离框开发。
05、如何发送 application/x-www-form-urlencoded 类型的POST表单请求?
<?php
$producer = new Producer();
$producer->onProducerStart = function($producer){
    $context['form_params'] = [
        'name1' => 'value1',
        'name2' => 'value2',
    ];
    $task = [
        'url'     => 'http://yourdomain.com/post',
        'method'  => 'post',
        'context' => $context,
    ];
    $producer->createTask($task);
};
06、如何发送 multipart/form-data 类型的POST表单请求?
注意:contents 字段的类型任何时候都只能是字符串,如果希望contents表达的是流数据,那么需要新增配置字段contents_as_stream为true,这样就相当于启用了文件上传功能。
<?php
$producer = new Producer();
$producer->onProducerStart = function($producer){
    $context['multipart'] = [
		[
			'name'     => 'k1',
			'contents' => 'v1',
		],
		[
			'name'     => 'k2',
			'contents' => '/path/to/file',
			'contents_as_stream' => true,
			'filename' => 'custom_filename.txt'
		],
    ];
    $task = [
        'url'     => 'http://yourdomain.com/post',
        'method'  => 'post',
        'context' => $context,
    ];
    $producer->createTask($task);
};
07、如何设置http请求头?
主要用途之一就是浏览器伪装,默认引擎会自动伪装成各种常见的随机User-Agent,有两种设置方法:
【注意:如果同时使用了两种方法,则API接口的优先级高于context配置,即高者会覆盖低者配置】
方法一:通过context上下文配置实现
<?php
$context['headers'] = [
    'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
    'Accept'     => 'text/html,*/*;',
];
方法二:通过API接口实现
<?php
$downloader = new Downloader();
$downloader->onBeforeDownload = function($downloader, $task){
    //注意 setHeaders() 方法同时支持数组和字符串类型的参数,
    //一般从fiddler或charles软件直接复制头字符串粘贴过来即可.
    $downloader->httpClient->setHeaders([
        'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
        'Accept'     => 'text/html,*/*;',
    ]);
};
08、无头浏览器常用的配置参数
| 配置项 | 默认值 | 备注说明 | 
|---|---|---|
| headless | true | 要不要启用无头模式 | 
| noSandbox | true | 要不要禁用sandbox模式,在docker容器中运行时有用 | 
| keepAlive | false | 要不要在脚本终止时保活即不杀死chrome子进程 | 
| sendSyncDefaultTimeout | 10000 | 发送同步消息的默认超时(单位:毫秒) | 
| headers | none | 自定义HTTP请求头数组 | 
| userAgent | none | 设置浏览器的User-Agent | 
| enableImages | true | 要不要加载图片 | 
| customFlags | none | 要传递给无头浏览器的自定义参数数组。例如:['--option1', '--option2=someValue'] | 
| debugLogger | null | 设置用于打印调试消息的组件。例如:"php://stdout" | 
| disableNotifications | false | 要不要禁用浏览器通知 | 
| ignoreCertificateErrors | true | 将Chrome设置为忽略SSL错误 | 
| noProxyServer | false | 不要使用代理服务器,始终进行直连。 | 
| proxyServer | none | 使用代理服务器,例如: 127.0.0.1:8080 | 
| proxyServerAuth | none | 提交用户名和密码凭据给代理服务器,注意:因依赖库不支持,所以该参数及相应功能暂在爬山虎层面实现。 | 
| proxyBypassList | none | 绕过代理设置并使用直接连接的主机列表 | 
| startupTimeout | 30 | 等待Chrome启动的最长时间(单位:秒) | 
| userDataDir | none | Chrome用户数据目录(默认临时生成一个新空目录) | 
09、关于无头回调API使用注意事项
- 
爬山虎默认会使用内部的无头API来自动完成无头浏览任务,同时也可以劫持无头回调API onHeadlessBrowserOpenPage来完成类似的任务, 但是注意如果项目中没有使用此无头回调API,建议最好注释或删除本回调API的定义代码。
- 
无头浏览器的配置参数 keepAlive的值强烈建议配置为false,否则爬山虎组件进程退出后可能会引起意外的chrome子进程总数持续增长的问题。
- 
无头浏览器的配置参数和普通的httpClient的配置参数不是共享的,即无头浏览器的配置参数是独有的,见本节 表格-08段说明。
10、无头模式下如何访问设有用户名和密码鉴权的代理服务器?
由于爬山虎依赖的无头库尚不支持访问设有用户名和密码鉴权凭据的代理服务器,所以无头配置参数proxyServerAuth及相应功能暂在爬山虎层面提供实现,
具体的样板代码为:
<?php
$config['task']['context'] = [
    'headless_browser' => [
        'headless' => true,
        'proxyServer' => "http://127.0.0.1:8080",
        'proxyServerAuth' => ['username'=>'替换成用户名', 'password'=>'替换成密码'],
    ],
];
