gsouf / chromium
Requires
- php: ^7.4.15 || ^8.0.2
- chrome-php/wrench: ^1.6
- evenement/evenement: ^3.0.1
- monolog/monolog: ^1.27.1 || ^2.8 || ^3.2
- psr/log: ^1.1 || ^2.0 || ^3.0
- symfony/filesystem: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/polyfill-mbstring: ^1.26
- symfony/process: ^4.4 || ^5.0 || ^6.0 || ^7.0
Requires (Dev)
- bamarni/composer-bin-plugin: ^1.8.2
- phpunit/phpunit: ^9.6.3 || ^10.0.12
- symfony/var-dumper: ^4.4 || ^5.0 || ^6.0 || ^7.0
- 1.12.x-dev
- 1.11.x-dev
- v1.11.0
- 1.10.x-dev
- v1.10.1
- v1.10.0
- 1.9.x-dev
- v1.9.1
- v1.9.0
- 1.8.x-dev
- v1.8.1
- v1.8.0
- 1.7.x-dev
- v1.7.2
- v1.7.1
- v1.7.0
- 1.6.x-dev
- v1.6.2
- v1.6.1
- v1.6.0
- 1.5.x-dev
- v1.5.0
- 1.4.x-dev
- v1.4.1
- v1.4.0
- 1.3.x-dev
- v1.3.1
- v1.3.0
- 1.2.x-dev
- v1.2.1
- v1.2.0
- 1.1.x-dev
- v1.1.1
- v1.1.0
- 1.0.x-dev
- v1.0.1
- v1.0.0
- 0.11.x-dev
- v0.11.2
- v0.11.1
- v0.11.0
- v0.10.0
- v0.9.0
- v0.8.1
- v0.8.0
- v0.7.0
- v0.6.0
- v0.5.0
- v0.4.0
- v0.3.0
- v0.2.4
- v0.2.3
- v0.2.2
- v0.2.1
- v0.2.0
- v0.1.4
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
This package is auto-updated.
Last update: 2024-06-19 22:00:27 UTC
README
Chrome PHP
此库允许您从 PHP 中开始使用 chrome/chromium 的无头模式。
可以同步和异步使用!
特性
- 从 PHP 打开 Chrome 或 Chromium 浏览器
- 创建页面并导航到页面
- 截图
- 在页面上评估 JavaScript
- 制作 PDF
- 模拟鼠标
- 模拟键盘
- 始终对 IDE 友好
快乐的浏览!
要求
需要 PHP 7.4-8.3 和 Chrome/Chromium 65+ 可执行文件。
请注意,此库仅在 Linux 上进行了测试,但与 macOS 和 Windows 兼容。
安装
此库可以使用 Composer 安装,并在 Packagist 上的 chrome-php/chrome 下可用。
$ composer require chrome-php/chrome
用法
它使用简单易懂的 API 来启动 Chrome,打开页面,截图,爬取网站...以及您作为人类可以做的几乎所有事情。
use HeadlessChromium\BrowserFactory; $browserFactory = new BrowserFactory(); // starts headless Chrome $browser = $browserFactory->createBrowser(); try { // creates a new page and navigate to an URL $page = $browser->createPage(); $page->navigate('http://example.com')->waitForNavigation(); // get page title $pageTitle = $page->evaluate('document.title')->getReturnValue(); // screenshot - Say "Cheese"! 😄 $page->screenshot()->saveToFile('/foo/bar.png'); // pdf $page->pdf(['printBackground' => false])->saveToFile('/foo/bar.pdf'); } finally { // bye $browser->close(); }
使用不同的 Chrome 可执行文件
启动时,工厂会查找环境变量 "CHROME_PATH"
以用作 Chrome 可执行文件。如果未找到变量,它将尝试根据您的操作系统猜测正确的可执行文件路径,或者使用 "chrome"
作为默认值。
您还可以在创建新对象时显式设置任何可执行文件。例如 "chromium-browser"
use HeadlessChromium\BrowserFactory; // replace default 'chrome' with 'chromium-browser' $browserFactory = new BrowserFactory('chromium-browser');
调试
以下示例禁用了无头模式以简化调试
use HeadlessChromium\BrowserFactory; $browserFactory = new BrowserFactory(); $browser = $browserFactory->createBrowser([ 'headless' => false, // disable headless mode ]);
其他调试选项
[ 'connectionDelay' => 0.8, // add 0.8 second of delay between each instruction sent to Chrome, 'debugLogger' => 'php://stdout', // will enable verbose mode ]
关于 debugLogger
:这可以是任何资源字符串、资源或实现 Psr\Log 的对象(例如 monolog 或 apix/log)。
API
浏览器工厂
直接在 createBrowser
方法中设置的选项仅用于单个浏览器创建。默认选项将被忽略。
use HeadlessChromium\BrowserFactory; $browserFactory = new BrowserFactory(); $browser = $browserFactory->createBrowser([ 'windowSize' => [1920, 1000], 'enableImages' => false, ]); // this browser will be created without any options $browser2 = $browserFactory->createBrowser();
使用 setOptions
和 addOptions
方法设置的选项将持久存在。
$browserFactory->setOptions([ 'windowSize' => [1920, 1000], ]); // both browser will have the same 'windowSize' option $browser1 = $browserFactory->createBrowser(); $browser2 = $browserFactory->createBrowser(); $browserFactory->addOptions(['enableImages' => false]); // this browser will have both the 'windowSize' and 'enableImages' options $browser3 = $browserFactory->createBrowser(); $browserFactory->addOptions(['enableImages' => true]); // this browser will have the previous 'windowSize', but 'enableImages' will be true $browser4 = $browserFactory->createBrowser();
可用选项
以下是浏览器工厂的可用选项
选项名称 | 默认值 | 描述 |
---|---|---|
connectionDelay |
0 |
调试目的之间应用的每个操作的延迟 |
customFlags |
无 | 传递给命令行的标志数组。例如:['--option1', '--option2=someValue'] |
debugLogger |
空 |
一个字符串(例如 "php://stdout"),或资源,或实现 Psr\Log 的 PSR-3 日志记录实例以打印调试消息 |
disableNotifications |
假 |
禁用浏览器通知 |
enableImages |
真 |
切换加载图片 |
envVariables |
无 | 传递给进程的环境变量数组(例如 DISPLAY 变量) |
headers |
无 | 自定义 HTTP 标头数组 |
headless |
真 |
启用或禁用无头模式 |
ignoreCertificateErrors |
假 |
设置 Chrome 忽略 SSL 错误 |
keepAlive |
假 |
设置为 true 以在脚本终止时保持 Chrome 实例活动状态 |
noSandbox |
假 |
启用非沙盒模式,适用于在Docker容器中运行 |
noProxyServer |
假 |
不使用代理服务器,始终建立直接连接。覆盖其他代理设置。 |
proxyBypassList |
无 | 指定要绕过代理设置并使用直接连接的主机列表 |
proxyServer |
无 | 要使用的代理服务器。用法:127.0.0.1:8080 (使用凭据进行授权无效) |
sendSyncDefaultTimeout |
5000 |
发送同步消息的默认超时时间(毫秒) |
startupTimeout |
30 |
等待Chrome启动的最大时间(秒) |
userAgent |
无 | 用于整个浏览器的用户代理(请参阅页面API以获取替代方案) |
userDataDir |
无 | Chrome用户数据目录(默认:生成一个新的临时空目录) |
userCrashDumpsDir |
无 | crashpad应将转储存储在该目录中(将自动启用崩溃报告器) |
windowSize |
无 | 窗口大小。用法:$width, $height - 参见Page::setViewport |
excludedSwitches |
无 | 要从中删除的Chrome标志数组(例如 --enable-automation) |
持久浏览器
此示例显示了如何为多个脚本共享单个Chrome实例。
首次启动脚本时,我们使用浏览器工厂来启动Chrome,之后我们将连接到该浏览器的uri保存到文件系统。
下一次调用脚本时,将从这个文件中读取uri以连接到Chrome实例,而不是创建一个新的实例。如果Chrome已关闭或崩溃,将再次启动新实例。
use \HeadlessChromium\BrowserFactory; use \HeadlessChromium\Exception\BrowserConnectionFailed; // path to the file to store websocket's uri $socket = \file_get_contents('/tmp/chrome-php-demo-socket'); try { $browser = BrowserFactory::connectToBrowser($socket); } catch (BrowserConnectionFailed $e) { // The browser was probably closed, start it again $factory = new BrowserFactory(); $browser = $factory->createBrowser([ 'keepAlive' => true, ]); // save the uri to be able to connect again to browser \file_put_contents($socketFile, $browser->getSocketUri(), LOCK_EX); }
浏览器API
创建一个新的页面(选项卡)
$page = $browser->createPage();
获取打开的页面(选项卡)
$pages = $browser->getPages();
关闭浏览器
$browser->close();
设置在浏览器创建的每个页面导航之前要评估的脚本
$browser->setPagePreScript('// Simulate navigator permissions; const originalQuery = window.navigator.permissions.query; window.navigator.permissions.query = (parameters) => ( parameters.name === 'notifications' ? Promise.resolve({ state: Notification.permission }) : originalQuery(parameters) );');
页面API
导航到URL
// navigate $navigation = $page->navigate('http://example.com'); // wait for the page to be loaded $navigation->waitForNavigation();
当使用$navigation->waitForNavigation()
时,您将等待30秒,直到页面事件“loaded”被触发。您可以更改超时或监听的事件
use HeadlessChromium\Page; // wait 10secs for the event "DOMContentLoaded" to be triggered $navigation->waitForNavigation(Page::DOM_CONTENT_LOADED, 10000);
可用事件(按触发顺序排列)
Page::DOM_CONTENT_LOADED
:dom已完全加载Page::FIRST_CONTENTFUL_PAINT
:当第一个非白色内容元素在屏幕上绘制时触发Page::FIRST_IMAGE_PAINT
:当第一个图像在屏幕上绘制时触发Page::FIRST_MEANINGFUL_PAINT
:当页面的主要内容对用户可见时触发Page::FIRST_PAINT
:当屏幕上的任何像素被绘制时触发,包括浏览器的默认背景颜色Page::INIT
:DevTools协议的连接初始化Page::INTERACTIVE_TIME
:脚本已加载完成,并且主线程不再被渲染或其他任务阻塞Page::LOAD
:(默认)页面和所有资源都已加载Page::NETWORK_IDLE
:页面已加载,至少500ms没有发生网络活动
当您想要等待页面导航时,可能会出现两个主要问题。首先,页面加载时间太长,其次,您等待加载的页面已被替换。好消息是,您可以使用古老的try-catch来处理这些问题
use HeadlessChromium\Exception\OperationTimedOut; use HeadlessChromium\Exception\NavigationExpired; try { $navigation->waitForNavigation() } catch (OperationTimedOut $e) { // too long to load } catch (NavigationExpired $e) { // An other page was loaded }
在页面上评估脚本
一旦页面完成导航,您就可以在这个页面上评估任意脚本
// navigate $navigation = $page->navigate('http://example.com'); // wait for the page to be loaded $navigation->waitForNavigation(); // evaluate script in the browser $evaluation = $page->evaluate('document.documentElement.innerHTML'); // wait for the value to return and get it $value = $evaluation->getReturnValue();
有时您评估的脚本会点击链接或提交表单,在这种情况下,页面将重新加载,并且您将想要等待新页面的重新加载。
您可以通过使用$page->evaluate('some js that will reload the page')->waitForPageReload()
来实现这一点。一个示例可以在form-submit.php中找到
调用函数
这是evaluate
的替代方案,它允许在页面上下文中使用给定的参数调用给定的函数
$evaluation = $page->callFunction( "function(a, b) {\n window.foo = a + b;\n}", [1, 2] ); $value = $evaluation->getReturnValue();
添加脚本标签
如果您想向页面添加jQuery(或其他任何东西),这很有用
$page->addScriptTag([ 'content' => file_get_contents('path/to/jquery.js') ])->waitForResponse(); $page->evaluate('$(".my.element").html()');
您还可以使用URL来填充src属性
$page->addScriptTag([ 'url' => 'https://code.jqueryjs.cn/jquery-3.3.1.min.js' ])->waitForResponse(); $page->evaluate('$(".my.element").html()');
设置页面HTML
您可以使用setHtml
方法手动将HTML注入到页面。
// Basic $page->setHtml('<p>text</p>'); // Specific timeout & event $page->setHtml('<p>text</p>', 10000, Page::NETWORK_IDLE);
当页面的HTML更新时,我们将等待页面卸载。您可以通过两个可选参数指定等待时间以及等待哪个事件。默认为3000毫秒和“load”事件。
请注意,此方法不会追加到当前页面的HTML,而是完全替换它。
获取页面HTML
您可以使用getHtml
方法将页面HTML作为字符串获取。
$html = $page->getHtml();
在页面导航时添加脚本
$page->addPreScript('// Simulate navigator permissions; const originalQuery = window.navigator.permissions.query; window.navigator.permissions.query = (parameters) => ( parameters.name === 'notifications' ? Promise.resolve({ state: Notification.permission }) : originalQuery(parameters) );');
如果您的脚本需要在运行前DOM完全填充,可以使用“onLoad”选项。
$page->addPreScript($script, ['onLoad' => true]);
设置视口大小
此功能允许更改当前页面视口(模拟)的大小,而不会影响浏览器中所有页面的尺寸(另请参阅BrowserFactory::createBrowser的"windowSize"
选项)。
$width = 600; $height = 300; $page->setViewport($width, $height) ->await(); // wait for the operation to complete
制作截图
// navigate $navigation = $page->navigate('http://example.com'); // wait for the page to be loaded $navigation->waitForNavigation(); // take a screenshot $screenshot = $page->screenshot([ 'format' => 'jpeg', // default to 'png' - possible values: 'png', 'jpeg', 'webp' 'quality' => 80, // only when format is 'jpeg' or 'webp' - default 100 'optimizeForSpeed' => true // default to 'false' - Optimize image encoding for speed, not for resulting size ]); // save the screenshot $screenshot->saveToFile('/some/place/file.jpg');
截图页面上的某个区域
您可以使用“clip”选项选择页面上的截图区域。
use HeadlessChromium\Clip; // navigate $navigation = $page->navigate('http://example.com'); // wait for the page to be loaded $navigation->waitForNavigation(); // create a rectangle by specifying to left corner coordinates + width and height $x = 10; $y = 10; $width = 100; $height = 100; $clip = new Clip($x, $y, $width, $height); // take the screenshot (in memory binaries) $screenshot = $page->screenshot([ 'clip' => $clip, ]); // save the screenshot $screenshot->saveToFile('/some/place/file.jpg');
全页截图
您也可以使用$page->getFullPageClip
和属性captureBeyondViewport = true
来对全页布局(而不仅仅是视口)进行截图。
// navigate $navigation = $page->navigate('https://example.com'); // wait for the page to be loaded $navigation->waitForNavigation(); $screenshot = $page->screenshot([ 'captureBeyondViewport' => true, 'clip' => $page->getFullPageClip(), 'format' => 'jpeg', // default to 'png' - possible values: 'png', 'jpeg', 'webp' ]); // save the screenshot $screenshot->saveToFile('/some/place/file.jpg');
打印为PDF
// navigate $navigation = $page->navigate('http://example.com'); // wait for the page to be loaded $navigation->waitForNavigation(); $options = [ 'landscape' => true, // default to false 'printBackground' => true, // default to false 'displayHeaderFooter' => true, // default to false 'preferCSSPageSize' => true, // default to false (reads parameters directly from @page) 'marginTop' => 0.0, // defaults to ~0.4 (must be a float, value in inches) 'marginBottom' => 1.4, // defaults to ~0.4 (must be a float, value in inches) 'marginLeft' => 5.0, // defaults to ~0.4 (must be a float, value in inches) 'marginRight' => 1.0, // defaults to ~0.4 (must be a float, value in inches) 'paperWidth' => 6.0, // defaults to 8.5 (must be a float, value in inches) 'paperHeight' => 6.0, // defaults to 11.0 (must be a float, value in inches) 'headerTemplate' => '<div>foo</div>', // see details above 'footerTemplate' => '<div>foo</div>', // see details above 'scale' => 1.2, // defaults to 1.0 (must be a float) ]; // print as pdf (in memory binaries) $pdf = $page->pdf($options); // save the pdf $pdf->saveToFile('/some/place/file.pdf'); // or directly output pdf without saving header('Content-Description: File Transfer'); header('Content-Type: application/pdf'); header('Content-Disposition: inline; filename=filename.pdf'); header('Content-Transfer-Encoding: binary'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); echo base64_decode($pdf->getBase64());
选项headerTemplate
和footerTemplate
应有效HTML标记,并使用以下类将打印值注入其中
- date:格式化的打印日期
- title:文档标题
- url:文档位置
- pageNumber:当前页码
- totalPages:文档中的总页数
保存下载
您可以设置保存下载文件的路径。
// After creating a page. $page->setDownloadPath('/path/to/save/downloaded/files');
鼠标API
鼠标API依赖于页面实例,允许您控制鼠标的移动、点击和滚动。
$page->mouse() ->move(10, 20) // Moves mouse to position x=10; y=20 ->click() // left-click on position set above ->move(100, 200, ['steps' => 5]) // move mouse to x=100; y=200 in 5 equal steps ->click(['button' => Mouse::BUTTON_RIGHT]; // right-click on position set above // given the last click was on a link, the next step will wait // for the page to load after the link was clicked $page->waitForReload();
您可以通过模拟鼠标滚轮在页面、框架或元素中上下滚动。
$page->mouse() ->scrollDown(100) // scroll down 100px ->scrollUp(50); // scroll up 50px
查找元素
find
方法将使用querySelector搜索元素并将光标移动到它上面的随机位置。
try { $page->mouse()->find('#a')->click(); // find and click at an element with id "a" $page->mouse()->find('.a', 10); // find the 10th or last element with class "a" } catch (ElementNotFoundException $exception) { // element not found }
此方法将尝试向右和向下滚动,将元素带到可见屏幕。如果元素位于内部可滚动部分中,首先尝试将鼠标移动到该部分内部。
键盘API
键盘API依赖于页面实例,允许您像真实用户一样输入。
$page->keyboard() ->typeRawKey('Tab') // type a raw key, such as Tab ->typeText('bar'); // type the text "bar"
为了模仿真实用户,您可能需要使用setKeyInterval
方法在每个按键之间添加延迟。
$page->keyboard()->setKeyInterval(10); // sets a delay of 10 milliseconds between keystrokes
键组合
可以使用press
、type
和release
方法发送键组合,如ctrl + v
。
// ctrl + a to select all text $page->keyboard() ->press('control') // key names are case insensitive and trimmed ->type('a') // press and release ->release('Control'); // ctrl + c to copy and ctrl + v to paste it twice $page->keyboard() ->press('Ctrl') // alias for Control ->type('c') ->type('V') // upper and lower cases should behave the same way ->release(); // release all
您可以连续按同一个键几次,这相当于用户按住键。但是,释放事件每个键只会发送一次。
键别名
键 | 别名 |
---|---|
Control | Control 、Ctrl 、Ctr |
Alt | Alt 、AltGr 、Alt Gr |
Meta | Meta 、Command 、Cmd |
Shift | Shift |
Cookie API
您可以设置和获取页面的cookie。
设置Cookie
use HeadlessChromium\Cookies\Cookie; $page = $browser->createPage(); // example 1: set cookies for a given domain $page->setCookies([ Cookie::create('name', 'value', [ 'domain' => 'example.com', 'expires' => time() + 3600 // expires in 1 hour ]) ])->await(); // example 2: set cookies for the current page $page->navigate('http://example.com')->waitForNavigation(); $page->setCookies([ Cookie::create('name', 'value', ['expires']) ])->await();
获取Cookies
use HeadlessChromium\Cookies\Cookie; $page = $browser->createPage(); // example 1: get all cookies for the browser $cookies = $page->getAllCookies(); // example 2: get cookies for the current page $page->navigate('http://example.com')->waitForNavigation(); $cookies = $page->getCookies(); // filter cookies with name == 'foo' $cookiesFoo = $cookies->filterBy('name', 'foo'); // find first cookie with name == 'bar' $cookieBar = $cookies->findOneBy('name', 'bar'); if ($cookieBar) { // do something }
设置用户代理
您可以按页面设置用户代理。
$page->setUserAgent('my user-agent');
有关在整个浏览器中设置用户代理的信息,请参阅BrowserFactory选项userAgent
。
高级用法
该库包含用于隐藏所有通信逻辑的工具,但您可以使用内部使用的工具直接与Chrome调试协议通信。
示例
use HeadlessChromium\Communication\Connection; use HeadlessChromium\Communication\Message; // Chrome devtools URI $webSocketUri = 'ws://127.0.0.1:9222/devtools/browser/xxx'; // create a connection $connection = new Connection($webSocketUri); $connection->connect(); // send method "Target.activateTarget" $responseReader = $connection->sendMessage(new Message('Target.activateTarget', ['targetId' => 'xxx'])); // wait up to 1000ms for a response $response = $responseReader->waitForResponse(1000);
创建会话并向目标发送消息
// given a target id $targetId = 'yyy'; // create a session for this target (attachToTarget) $session = $connection->createSession($targetId); // send message to this target (Target.sendMessageToTarget) $response = $session->sendMessageSync(new Message('Page.reload'));
调试
您可以在每次操作之前设置延迟以简化调试。
$connection->setConnectionDelay(500); // wait for 500ms between each operation to ease debugging
浏览器(独立版)
use HeadlessChromium\Communication\Connection; use HeadlessChromium\Browser; // Chrome devtools URI $webSocketUri = 'ws://127.0.0.1:9222/devtools/browser/xxx'; // create connection given a WebSocket URI $connection = new Connection($webSocketUri); $connection->connect(); // create browser $browser = new Browser($connection);
与DOM交互
使用CSS选择器在页面上查找一个元素
$page = $browser->createPage(); $page->navigate('http://example.com')->waitForNavigation(); $elem = $page->dom()->querySelector('#index_email');
使用CSS选择器在另一个元素内部查找所有元素
$elem = $page->dom()->querySelector('#index_email'); $elem->querySelectorAll('a.link');
通过XPath选择器查找页面上的所有元素
$page = $browser->createPage(); $page->navigate('http://example.com')->waitForNavigation(); $elem = $page->dom()->search('//div/*/a');
通过CSS选择器等待元素
$page = $browser->createPage(); $page->navigate('http://example.com')->waitForNavigation(); $page->waitUntilContainsElement('div[data-name=\"el\"]');
如果将字符串传递给Page::waitUntilContainsElement
,则Page::waitForElement
会为您创建一个CSSSelector
实例。要使用其他选择器,您可以传递所需Selector
的实例。
通过XPath选择器等待元素
use HeadlessChromium\Dom\Selector\XPathSelector; $page = $browser->createPage(); $page->navigate('http://example.com')->waitForNavigation(); $page->waitUntilContainsElement(new XPathSelector('//div[contains(text(), "content")]'));
您可以向元素发送文本或点击它
$elem->click(); $elem->sendKeys('Sample text');
您可以从输入上传文件
$elem->sendFile('/path/to/file');
您可以获取元素的文本或属性
$text = $elem->getText(); $attr = $elem->getAttribute('class');
贡献
有关贡献详情,请参阅CONTRIBUTING.md。
许可证
本项目采用MIT许可证(MIT)。