2021.01.15 Web制作
システム開発 - Chrome PHP 覚書
参照:chrome-php / headless-chromium-php
※インストール方法などは他サイトなどを参考にしてください。
特徴
- PHPからChromeまたはChromeブラウザを開くことができる
- ページを作成し、ページへ移動することができる
- スクリーンショットを撮ることができる
- ページ内のJavaScriptの実行結果を評価できる
- PDFを作成することができる
- マウスをエミュレートできる
- キーボードをエミュレートできる
- 常にIDEフレンドリーである
基本的な使用方法
シンプルかつ理解しやすいAPIにより、Chromeの起動、ページを開く、スクリーンショットを撮る、Webサイトをクロールするなど、Chrome上で人ができることのほとんどを行うことができます。
use HeadlessChromium\BrowserFactory;
$browserFactory = new BrowserFactory();
// headless chromeの開始
$browser = $browserFactory->createBrowser();
try {
// 新しいページを作成し、指定のURLへ移動する
$page = $browser->createPage();
$page->navigate('http://example.com')->waitForNavigation();
// ページタイトルの取得
$pageTitle = $page->evaluate('document.title')->getReturnValue();
// スクリーンショットを撮る
$page->screenshot()->saveToFile('/foo/bar.png');
// PDFの作成
$page->pdf(['printBackground' => false])->saveToFile('/foo/bar.pdf');
} finally {
// headless chromeの終了
$browser->close();
}
別のChrome実行ファイルを指定する
ファクトリを開始すると、chromeの実行ファイルとして環境変数"CHROME_PATH"
を探します。変数が見つからない場合には、実行ファイルとして"chrome"
(デフォルト)が使用されます。
任意の実行ファイルを指定できます。例では、"chromium-browser"
を指定しています。
use HeadlessChromium\BrowserFactory;
// デフォルトの'chrome'から'chromium-browser'に変更
$browserFactory = new BrowserFactory('chromium-browser');
デバッグ
下記の例では、デバッグを容易にするために、ヘッドレスモードを無効にしています。
use HeadlessChromium\BrowserFactory;
$browserFactory = new BrowserFactory();
$browser = $browserFactory->createBrowser([
'headless' => false, // headlessモードを無効にする
]);
他のデバッグオプション
[
// chromeに送信される各命令の間に0.8秒の遅延を追加します
// add 0.8 second of delay between each instruction sent to chrome,
'connectionDelay' => 0.8,
// 冗長モードを有効にします
'debugLogger' => 'php://stdout',
]
'debugLogger'
は、Psr\Log(monolog、apix/logなど)のLoggerInterface
を実装するリソース文字列、リソース、またはオブジェクトを指定できます。
API
Browser Factory
use HeadlessChromium\BrowserFactory;
$browserFactory = new BrowserFactory();
$browser = $browserFactory->createBrowser([
'windowSize' => [1920, 1000],
'enableImages' => false,
]);
オプション
BrowserFactoryで使用できるオプションは次の通りです。
Option name | Default | Description |
---|---|---|
connectionDelay | 0 | デバッグを目的とした各操作間に適用する遅延時間 |
customFlags | none | コマンドラインに渡すフラグの配列。例)['--option1', '--option2=someValue'] |
debugLogger | null | デバッグ用メッセージを出力するための文字列(「php:// stdout」など)、リソース、またはPSR-3ロガーのインスタンス |
enableImages | true | 画像の読み込み |
headless | true | ヘッドレスモード |
ignoreCertificateErrors | false | SSLエラーを無視するかどうか |
keepAlive | false | スクリプトの終了時にchromeインスタンスを維持する場合にはtrue |
noSandbox | false | Dockerコンテナを実行する場合に最適 |
sendSyncDefaultTimeout | 5000 | 同期メッセージを送信するためのデフォルトのタイムアウト時間(ミリ秒) |
startupTimeout | 30 | Chromeが起動するのに待機する最大時間(秒) |
userAgent | none | ブラウザで使用するユーザーエージェントを指定(置換方法はapiページを参照) |
userDataDir | none | Chromeユーザのデータディレクトリ(デフォルト:新しい空のディレクトリが一時的に生成されます) |
windowSize | none | ウインドウサイズ。使用法:$width, $height - Page::setViewportも参照 |
ブラウザAPI
新しいページの作成(タブ)
$page = $browser->createPage();
// 目的のページを指定
$uri = 'http://example.com';
$page = $browser->createPage($uri);
ブラウザを閉じる
$browser->close();
ブラウザで作成された全ページで遷移する前に実行されるスクリプトの設定
$browser->setPagePreScript('// ナビゲーターとしての権限を装う
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ?
Promise.resolve({ state: Notification.permission }) :
originalQuery(parameters)
);');
Page API
URLに移動
// ページ遷移
$navigation = $page->navigate('http://example.com');
// ページが読み込まれるのを待つ
$navigation->waitForNavigation();
$navigation->waitForNavigation()
を使用する場合、ページイベント の"loaded"が発動するまで30秒待機します。待ち受けるタイムアウト時間やイベントは変更できます。
// "DOMContentLoaded"イベントが発動するまで10秒待つ
$navigation->waitForNavigation(Page::DOM_CONTENT_LOADED, 10000);
利用可能なイベント(発動順):
Page::DOM_CONTENT_LOADED
:domが完全に読み込まれるPage::LOAD
:(デフォルト)ページとすべてのリソースが読み込まれるPage::NETWORK_IDLE
:ページが読み込まれ、少なくとも500ミリ秒間ネットワーク上の通信が発生していない
ページを移動するのを待ちたい場合、発生する可能性のある2つの主な問題があります。一つは、ページが長すぎて読み込み終わらず、次に読み込まれたページで置き換えられてしまうことです。良いニュースは、古き良きtry catchを使用してこれらの問題を処理できることです。
use HeadlessChromium\Exception\OperationTimedOut;
use HeadlessChromium\Exception\NavigationExpired;
try {
$navigation->waitForNavigation()
} catch (OperationTimedOut $e) {
// 読み込むのに長すぎた場合
} catch (NavigationExpired $e) {
// 他のページが読み込まれた場合
}
ページ上のスクリプトの評価
ページ遷移が完了すると、そのページで任意のスクリプトを評価できます。
// ページ遷移
$navigation = $page->navigate('http://example.com');
// ページが読み込まれるのを待つ
$navigation->waitForNavigation();
// ブラウザでスクリプトを評価
$evaluation = $page->evaluate('document.documentElement.innerHTML');
// 値が返るのを待って値を取得
$value = $evaluation->getReturnValue();
評価するスクリプトがリンクをクリックしたり、フォームを送信したりする場合があります。この場合、ページは再読み込みされるので、新しいページが読み込まれるのを待つ必要があります。
$page->evaluate('ページを再読み込みするjs')->waitForPageReload()
を使用することで実現可能です。例はform-submit.phpで入手できます。
関数を呼び出す
この方法は、ページのコンテキスト内で指定された引数を使用して、指定された関数を呼び出すことができるevaluate
の代替手段です。
$evaluation = $page->callFunction(
"function(a, b) {\n window.foo = a + b;\n}",
[1, 2]
);
$value = $evaluation->getReturnValue();
scriptタグの追加
下記は、jQuery(またはその他のもの)をページに追加する場合に使用します。
$page->addScriptTag([
'content' => file_get_contents('path/to/jquery.js')
])->waitForResponse();
$page->evaluate('$(".my.element").html()');
src属性にURLを設定することも可能です。
$page->addScriptTag([
'url' => 'https://code.jquery.com/jquery-3.3.1.min.js'
])->waitForResponse();
$page->evaluate('$(".my.element").html()');
ページのHTMLを取得
getHtml
メソッドを使用して、ページのHTMLを文字列として取得できます。
$html = $page->getHtml();
ページナビゲーション時に評価するスクリプトの追加
$page->addPreScript('// ナビゲーターとしての権限を装う
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(); // 設定の完了を待つ
スクリーンショットの作成
// ページ遷移
$navigation = $page->navigate('http://example.com');
// ページが読み込まれるのを待つ
$navigation->waitForNavigation();
// スクリーンショットを撮る
$screenshot = $page->screenshot([
// デフォルト:'png' / 他:'png', 'jpeg',
'format' => 'jpeg',
// 'jpeg'の場合 / デフォルト:100
'quality' => 80,
]);
// スクリーンショットの保存
$screenshot->saveToFile('/some/place/file.jpg');
エリアの選択
スクリーンショットの領域を選択するために、"clip"オプションを使用します(例 TODO)
ページ全体のスクリーンショットを撮る
$page->getFullPageClip
を使用して、完全なレイアウト(レイアウトだけでなく)のスクリーンショットを撮ることもできます(例 TODO)。
TODO Page.getFullPageClip();
use HeadlessChromium\Clip;
// ページ遷移
$navigation = $page->navigate('http://example.com');
// ページが読み込まれるのを待つ
$navigation->waitForNavigation();
// left及びcenterの座標+幅及び高さを指定して長方形を作成
// 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);
// スクリーンショットを撮る(メモリバイナリ内)
$screenshot = $page->screenshot([
'clip' => $clip,
]);
// スクリーンショットを保存
$screenshot->saveToFile('/some/place/file.jpg');
PDFの作成
// ページ遷移
$navigation = $page->navigate('http://example.com');
// ページが読み込まれるのを待つ
$navigation->waitForNavigation();
$options = [
// デフォルト:false
'landscape' => true,
'printBackground' => true,
'displayHeaderFooter' => true,
// デフォルト:false(@pageから直接パラメータを読み取る)
'preferCSSPageSize' => true,
// デフォルト:~0.4(浮動小数点、値はインチ)
'marginTop' => 0.0,
'marginBottom' => 1.4,
'marginLeft' => 5.0,
'marginRight' => 1.0,
// デフォルト:8.5(浮動小数点、値はインチ)
'paperWidth' => 6.0,
'paperHeight' => 6.0,
// 詳細は下記参照
'headerTemplate' => '<div>foo</div>',
'footerTemplate' => '<div>foo</div>',
// デフォルト:1.0(浮動小数点)
'scale' => 1.2,
];
// PDFの作成(メモリバイナリ内)
$pdf = $page->pdf($options);
// PDFの保存
$pdf->saveToFile('/some/place/file.pdf');
// 又は保存せずに直接出力
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()
// マウスの位置をx=10/y=20に移動
->move(10, 20)
// 上記位置に移動し、左クリック
->click()
// 5pxずつマウスをx=100/y=200に移動
->move(100, 200, ['steps' => 5])
// 上記位置に移動し、右クリック
->click(['button' => Mouse::BUTTON_RIGHT];
// 最後のクリックがリンク上であった場合
// リンクのクリック後、ページが読み込まれるのを待つ
$page->waitForReload();
マウスホイールをエミュレートして、ページ、フレーム、または要素を上下にスクロールできます。
$page->mouse()
// 下方向に100pxスクロール
->scrollDown(100)
// 上方向に50pxスクロール
->scrollUp(50);
キーボードAPI
キーボードAPIは、ページインスタンスに依存しており、実際のユーザーのように入力できます。
$page->keyboard()
// 「Tab」のような実際のキーを入力
->typeRawKey('Tab')
// テキスト"bar"の入力
->typeText('bar');
実際のユーザーになりすますには、setKeyInterval
メソッドを使用して、各キーストロークの間に遅延を追加します。
// キーストロークに10ミリ秒の遅延を設定
$page->keyboard()->setKeyInterval(10);
Cookie API
ページのCookieを設定および取得できます。
クッキーの設定
use HeadlessChromium\Cookies\Cookie;
$page = $browser->createPage();
// 例1)特定のドメインにクッキーを設定
$page->setCookies([
Cookie::create('name', 'value', [
'domain' => 'example.com',
// 期限1日
'expires' => time() + 3600
])
])->await();
// 例2)現在のページにクッキーを設定
$page->navigate('http://example.com')->waitForNavigation();
$page->setCookies([
Cookie::create('name', 'value', ['expires'])
])->await();
クッキーの取得
use HeadlessChromium\Cookies\Cookie;
$page = $browser->createPage();
// 例1)ブラウザの全てのクッキーを取得
$cookies = $page->getAllCookies();
// 例2)現在のページのクッキーを取得
$page->navigate('http://example.com')->waitForNavigation();
$cookies = $page->getCookies();
// name == 'foo'のクッキーにフィルタリング
$cookiesFoo = $cookies->filterBy('name', 'foo');
// name == 'bar'のクッキーを探す
$cookieBar = $cookies->findOneBy('name', 'bar');
if ($cookieBar) {
// do something
}
ユーザーエージェントの設定
ページごとにユーザーエージェントを設定できます。
$page->setUserAgent('my user agent');
ブラウザ全体でuserAgent
を設定するには、BrowserFactoryのオプションも参照してください。
高度な使用法
ライブラリには、すべての通信ロジックを非表示にするツールが付属していますが、内部で使用されるツールを使用して、Chromeデバッグプロトコルと直接通信できます。
例:
use HeadlessChromium\Communication\Connection;
use HeadlessChromium\Communication\Message;
// chrome devtools uri
$webSocketUri = 'ws://127.0.0.1:9222/devtools/browser/xxx';
// 接続を作成
$connection = new Connection($webSocketUri);
$connection->connect();
// "Target.activateTarget"メソッドの送信
$responseReader = $connection->sendMessage(new Message('Target.activateTarget', ['targetId' => 'xxx']));
// 応答を1000ミリ秒待つ
$response = $responseReader->waitForResponse(1000);
セッションを作成し、ターゲットにメッセージを送信
// ターゲットIDを設定
$targetId = 'yyy';
// ターゲットのセッションを作成(attachToTarget)
$session = $connection->createSession($targetId);
// ターゲットにメッセージを送信(Target.sendMessageToTarget)
$response = $session->sendMessageSync(new Message('Page.reload'));
デバッグ
各操作が行われる前に遅延を設定することで、容易にデバッグを行えます。
// デバッグのために各操作間で500ミリ秒待つ
$connection->setConnectionDelay(500);
ブラウザ(スタンドアロン)
use HeadlessChromium\Communication\Connection;
use HeadlessChromium\Browser;
// chrome devtools uri
$webSocketUri = 'ws://127.0.0.1:9222/devtools/browser/xxx';
// WebソケットのURIを指定して接続を作成
$connection = new Connection($webSocketUri);
$connection->connect();
// ブラウザを作成
$browser = new Browser($connection);
WEBサイト制作のお問い合わせ、お見積り依頼、ご質問は
こちらのお問い合わせフォームよりお願いいたします
フライング・ハイ・ワークスの紹介
フライング・ハイ・ワークスは、東京のホームページ制作・Web制作会社・システム開発会社です。東京都及びその近郊(首都圏)を中心として、SEO対策を意識したPC及びスマホのサイトをワンソース(レスポンシブ対応)で制作します。
実績
デザイナーチームは、グラフィックデザインやイラストの制作も得意としており、著作権を意識しない素材の提供が可能です。システム・コーディングチームでは、Laravelなどを使用したスクラッチからのオリジナルシステム開発を始め、WordPressのカスタマイズを得意としております。
また、SEOやランディングページ(LP)、広告向けバナーなどを他社様でやっていた作業の引継ぎでも問題ありません。制作実績は多数ございますので、お客様に合わせたご提案が可能です。
500点以上のフライング・ハイ・ワークスの制作実績ページをご覧ください!
東京のホームページ制作・Web制作のお問い合わせ、お見積り依頼、相談、質問は
こちらのお問い合わせフォームよりお願いいたします