Selenium 是怎么指挥浏览器运行的

2025/04/18 计算机网络

写过爬虫或者做自动化测试的相信对Selenium不会陌生,但Selenium 官方只提供少数几种语言的库,使用其它“小众”语言的只能眼馋。既然如此,那就自己琢磨一下能不能搞个类似的吧,毕竟大家都是图灵完备的语言,除了少数一些领域实在没办法或者几乎不可能做到之外,其它的都大差不差。

“小小”的语言能唤起浏览器,有经验的大家都知道还有个前提就是下载浏览器驱动。以Chrome为例,Selenium启动chromedriverHTTP服务,自身通过HTTP客户端与其通信;至于chromedriverChrome之间则通过CDP(Chrome DevTools Protocol)通信,通俗来说,CDP协议就是基于WebSocketJSON指令协议,例如:

{
    "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');

效果如下图:

simulate_selenium

另外,PHP并不是没有类似Selenium的库,只不过流行度相对没那么大罢了。像Symfony就提供了https://github.com/symfony/Panther

Search

    Table of Contents