fagundes / chrome
从PHP中启动无头Chrome/Chromium实例
Requires
- php: >=7.0
- apix/log: ^1.2
- evenement/evenement: ^3.0.1
- psr/log: ^1.0
- symfony/filesystem: ^3|^4|^5
- symfony/process: ^3|^4|^5
- wrench/wrench: ^2.0
Requires (Dev)
- phpunit/phpunit: ^6.0
- squizlabs/php_codesniffer: ^3.1
- symfony/var-dumper: ^3.3
README
这个库允许您从PHP中开始使用Chrome/Chromium的无头模式。
/!\ 该库仍处于早期阶段,您可能需要的某些功能可能尚未提供。我们将在收到功能请求时添加功能,如果您想看到库支持新功能,请随时提出问题。此外,该库遵循semver。这意味着在版本1.0.0之前,可能会发生很多变化。
⚠️寻找维护者⚠️
许多人对该库表示出兴趣,但我没有太多时间维护它。我正在寻找一些永久性的维护者来帮助。
这可以包括处理问题、添加文档、添加新功能、修复错误等...
功能
- 从PHP中打开chrome或chromium浏览器
- 创建页面并导航到页面
- 截图
- 在页面中评估JavaScript
- 制作PDF
- 模拟鼠标
- TODO 模拟键盘
- 始终对IDE友好
享受浏览!
要求
需要PHP 7和Chrome/Chromium可执行文件。
截至Chrome/Chromium的版本65,该库已被证明可以正确工作。请尽量使用Chrome的最新版本。
请注意,该库仅在Linux上进行了测试,但与osX和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(); // 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'); // 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
浏览器工厂
use \HeadlessChromium\BrowserFactory; $browserFactory = new BrowserFactory(); $browser = $browserFactory->createBrowser([ 'windowSize' => [1920, 1000], 'enableImages' => false ]);
选项
以下是浏览器工厂可用的选项
浏览器API
创建一个新的页面(标签页)
$page = $browser->createPage(); // destination can be specified $uri = 'http://example.com'; $page = $browser->createPage($uri);
关闭浏览器
$browser->close();
设置在由该浏览器创建的每个页面导航之前评估的脚本
$script = '// Simulate navigator permissions; const originalQuery = window.navigator.permissions.query; window.navigator.permissions.query = (parameters) => ( parameters.name === 'notifications' ? Promise.resolve({ state: Notification.permission }) : originalQuery(parameters) );' $browser->setPagePreScript($script);
页面API
导航到URL
// navigate $navigation = $page->navigate('http://example.com'); // wait for the page to be loaded $navigation->waitForNavigation();
当使用 $navigation->waitForNavigation()
时,您将等待30秒,直到页面事件 "loaded" 触发。您可以更改超时或要监听的事件
// wait 10secs for the event "DOMContentLoaded" to be triggered $navigation->waitForNavigation(Page::DOM_CONTENT_LOADED, 10000)
可用事件(按触发顺序排列)
Page::DOM_CONTENT_LOADED
:dom已完全加载Page::LOAD
:(默认)页面和所有资源都已加载Page::NETWORK_IDLE
:页面已加载,且至少500毫秒内没有网络活动
当您想要等待页面导航时,可能会出现两个主要问题。首先,页面加载时间太长;其次,您等待加载的页面已被替换。好消息是,您可以使用古老的 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) { window.foo = a + b; }', [1, 2] ); $value = $evaluation->getReturnValue();
添加 script 标签
如果您想在页面上添加 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()');
在页面导航时添加脚本
$script = '// Simulate navigator permissions; const originalQuery = window.navigator.permissions.query; window.navigator.permissions.query = (parameters) => ( parameters.name === 'notifications' ? Promise.resolve({ state: Notification.permission }) : originalQuery(parameters) );' $page->addPreScript($script);
如果您的脚本在运行前需要 DOM 完全填充,则可以使用 "onLoad" 选项
$page->addPreScript($script, ['onLoad' => true]);
设置视口大小
此功能允许更改当前页面的视口大小(模拟),而不会影响浏览器所有页面的大小(另请参阅 BrowserFactory::createBrowser 的 "windowSize" 选项)。
$width = 600; $height = 300; $page->setViewport($width, $height) ->await(); // wait for 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', 'quality' => 80 // only if format is 'jpeg' - default 100 ]); // save the screenshot $screenshot->saveToFile('/some/place/file.jpg');
选择区域
您可以使用 "clip" 选项来选择截图区域(待示例)
全页截图
您也可以使用 $page->getFullPageClip
来截取全布局截图(不仅是布局)(待示例)
待完成 Page.getFullPageClip();
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');
打印为 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 float, value in inches) 'marginBottom' => 1.4, // defaults to ~0.4 (must be float, value in inches) 'marginLeft' => 5.0, // defaults to ~0.4 (must be float, value in inches) 'marginRight' => 1.0 // defaults to ~0.4 (must be float, value in inches) 'paperWidth' => 6 // defaults to 8.5 (must be float, value in inches) 'paperHeight' => 6 // defaults to 8.5 (must be float, value in inches) 'headerTemplate' => "<div>foo</div>", // see details bellow 'footerTemplate' => "<div>foo</div>", // see details bellow 'scale' => 1.2, // defaults to 1 ]; // 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());
选项 headerTempalte
和 footerTempalte
应该是有效的 HTML 标记,其中使用了以下类将打印值注入其中
- date: 格式化的打印日期
- title: 文档标题
- url: 文档位置
- pageNumber: 当前页码
- totalPages: 文档中的总页数
鼠标 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();
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 day ]) ])->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');
另请参阅浏览器工厂选项 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); if ($response) { // ok }else { // not ok }
创建会话并向目标发送消息
// 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 500 ms 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 web socket uri $connection = new Connection($webSocketUri); $connection->connect(); // create browser $browser = new Browser($connection);
贡献
有关贡献详情,请参阅 CONTRIBUTING.md
致谢
感谢 puppeteer 作为灵感的来源。
路线图
- 制作 PDF
- 创建 DOM 操作框架
- 检查网络跟踪
- 模拟键盘
- 添加 API 文档(https://github.com/victorjonsson/PHP-Markdown-Documentation-Generator/blob/master/docs.md)
作者
- Soufiane Ghzal - 初始工作 - gsouf
有关参与此项目的 贡献者 列表。
许可证
该项目采用 公平许可证 许可。