chrome-php / chrome
从PHP中监测无头chrome/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-09-19 22:34:02 UTC
README
这个库允许您从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,打开页面,截图,爬取网站...几乎您可以像人类一样用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的LoggerInterface
的对象(例如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();
可用选项
以下是浏览器工厂可用的选项
持久浏览器
此示例展示了如何为多个脚本共享单个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更新时,我们将等待页面卸载。您可以通过两个可选参数指定等待时间和等待哪个事件。默认为3000ms和"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:URL地址
- pageNumber:当前页码
- totalPages:文档总页数
Save downloads:保存下载
您可以为保存的文件设置路径。
// 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
您可以连续多次按同一个键,这相当于用户按下并保持按键。但是,释放事件每个键只会发送一次。
键别名
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();
获取 Cookie
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) 许可。