fagundes/chrome

从PHP中启动无头Chrome/Chromium实例

v0.8.1 2020-02-20 20:36 UTC

README

Build Status Latest Stable Version License

这个库允许您从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 (例如 monologapix/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());

选项 headerTempaltefooterTempalte

应该是有效的 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 作为灵感的来源。

路线图

作者

  • Soufiane Ghzal - 初始工作 - gsouf

有关参与此项目的 贡献者 列表。

许可证

该项目采用 公平许可证 许可。