写过爬虫或者做自动化测试的相信对Selenium
不会陌生,但Selenium
官方只提供少数几种语言的库,使用其它“小众”语言的只能眼馋。既然如此,那就自己琢磨一下能不能搞个类似的吧,毕竟大家都是图灵完备的语言,除了少数一些领域实在没办法或者几乎不可能做到之外,其它的都大差不差。
“小小”的语言能唤起浏览器,有经验的大家都知道还有个前提就是下载浏览器驱动。以Chrome
为例,Selenium
启动chromedriver
的HTTP
服务,自身通过HTTP
客户端与其通信;至于chromedriver
和Chrome
之间则通过CDP(Chrome DevTools Protocol)
通信,通俗来说,CDP
协议就是基于WebSocket
的JSON
指令协议,例如:
{
"method": "Page.navigate",
"params": {
"url": "https://example.com"
}
}
在chromedriver
启动浏览器时,会自动为浏览器启动这个服务接收CDP
协议,进而通过这些命令来控制它。相信喜欢思考的人已经发现了,我们可以直接跳过chromedriver
这一步,自己启动Chrome
与其通信,这样性能更好。像puppeteer
等工具就是直接使用CDP
,不使用浏览器驱动的。当然本文是浅浅探索一下Selenium
的原理,这些就不做展开。
了解原理就会豁然开朗,动起手吧。
<?php
class SimulateSelenium
{
public static string $webdriverURL = 'http://localhost:9515';
public static string $findDriver = "ps ajx | grep chromedriver | grep -v grep";
public static function get($url = '')
{
$pid = pcntl_fork();
if ($pid < 0) {
exit("fork error");
} else if ($pid == 0) {
echo "starting chromedriver...\n";
if (!shell_exec(self::$findDriver)) {
pcntl_exec("/usr/bin/env", ["chromedriver", "--port=9515", "&"], ["PATH" => "/Users/wu/Bin"]);
}
} else {
$timeout = 10;
while ($timeout > 0) {
if (shell_exec(self::$findDriver)) {
break;
}
echo "waiting for chromedriver...\n";
sleep(1);
$timeout--;
}
$session = self::request('POST', self::$webdriverURL . "/session", [
'capabilities' => [
'alwaysMatch' => [
'browserName' => 'chrome',
],
]
]);
if (!isset($session['value']['sessionId'])) {
echo "无法创建浏览器 session\n";
exit(1);
}
$sessionId = $session['value']['sessionId'];
self::request('POST', self::$webdriverURL . "/session/$sessionId/url", [
'url' => $url,
]);
$title = self::request('GET', self::$webdriverURL . "/session/$sessionId/title");
echo "页面标题: " . $title['value'] . "\n";
self::request('DELETE', self::$webdriverURL . "/session/$sessionId");
}
}
private static function request($method, $url, $data = null)
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if ($data !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$res = curl_exec($ch);
curl_close($ch);
return json_decode($res, true);
}
}
SimulateSelenium::get('https://baidu.com');
效果如下图:
另外,PHP
并不是没有类似Selenium
的库,只不过流行度相对没那么大罢了。像Symfony
就提供了https://github.com/symfony/Panther。