CakePHP5入門【WebAPI編⑩】データ自動更新①
A子
B美が前に言ってたよね?
「自動で更新する方法がある」…って
B美
(本当はやりたくないんだけど…)
|
cd html/numapp[Enter]
composer require chrome-php/chrome[Enter] |
これを「MATE端末」上で実行してね
C菜
「Skipped installation ・・・」って~
B美
んで、次にrootになってからaptコマンドでパッケージをインストールします
|
su -[Enter]
apt update[Enter] apt install chromium libnss3-tools[Enter] |
必ず事前に「apt update」を実行してね
(じゃないと、エラーが出るかも…)
A子
B美
まだまだ足りないわ
次は以下のパッケージをインストールしてね
|
apt install fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc-s1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 xdg-utils[Enter]
apt install fonts-noto fonts-noto-cjk fonts-ipafont fonts-ipaexfont[Enter] exit[Enter] |
あ、どちらも途中で「Y」を押すこと
(二つ目については、フォントのインストールに時間がかかるから分けてます)
A子
まぁ、それはともかく、クーロンで自動実行するわけだからコマンドクラスを用意すれば良いんだよね?
B美
| bin/cake bake command lottery[Enter] |
これで「src/Command/LotteryCommand.php」が作られるわね
C菜
(テーブルのフェッチです~)
|
private $Numbers;
public function initialize(): void { parent::initialize(); $this->Numbers = $this->fetchTable('Numbers'); $this->loadComponent('SixSevenWeek'); } |
データベース登録時に「曜日」や「六曜」計算が必要なので、「SixSevenWeek」コンポーネントのロードも記述しておきます~
B美
コンポーネントって、コントローラーから共有できるライブラリなんだけど、コマンドクラスから使うには少し工夫が必要なの
いくつかの方法があるんだけど、私はいつもこうしてるわ
|
use Cake\Controller\ComponentRegistry;
use App\Controller\Component\SixSevenWeekComponent; |
を先頭に記述してから、「initialize」メソッドには以下の一文を追加
| $this->SixSevenWeek = new SixSevenWeekComponent(new ComponentRegistry()); |
あ、クラスフィールド($SixSevenWeek)の宣言もしておいたほうが良いかもね
(無くても動くけど…)
あとは従来通り
| $six = $this->SixSevenWeek::getSix('2026-03-31'); |
…って感じで使えるのよ
C菜
|
private $Numbers;
private $SixSevenWeek; public function initialize(): void { parent::initialize(); $this->Numbers = $this->fetchTable('Numbers'); $this->SixSevenWeek = new SixSevenWeekComponent(new ComponentRegistry()); } |
これでいかがでしょうか~?
B美
それじゃ、次はWebページへのアクセスについて実装していきましょう
|
use HeadlessChromium\BrowserFactory;
use HeadlessChromium\Exception\BrowserException; |
この二行を先頭に記述したあと、「execute」メソッド内にはこう書くの
|
//時間がかかるかもしれないので、タイムアウトを無効にする
set_time_limit(0); //ナンバーズ3サイトのURL $url3 = 'https://www.mizuhobank.co.jp/takarakuji/check/numbers/numbers3/index.html'; //ナンバーズ4サイトのURL $url4 = 'https://www.mizuhobank.co.jp/takarakuji/check/numbers/numbers4/index.html'; //エラー発生の有無 $error_flag = false; //引数として「which chromium」の結果ではダメ(本当の場所を指定) $browserFactory = new BrowserFactory('/usr/lib/chromium/chromium'); //仮ホームディレクトリ $userDataDir = sys_get_temp_dir().'/chrome-profile'; //ブラウザ生成 $browser = $browserFactory->createBrowser([ 'headless' => true, 'noSandbox' => true, 'startupTimeout' => 60, 'connectionDelay' => 30, 'args' => [ '--user-data-dir='.$userDataDir, '--disable-blink-features=AutomationControlled', '--disable-gpu', '--disable-dev-shm-usage', '--disable-features=VizDisplayCompositor', '--no-sandbox', '--disable-setuid-sandbox', '--single-process', '--no-zygote', '--disable-software-rasterizer', '--disable-extensions', '--disable-background-networking', '--disable-sync', '--metrics-recording-only', '--mute-audio', ] ]); //0.5秒待機 usleep(500000); try { //ブラウザページを作成(これを使ってURLにアクセスする) $page3 = $browser->createPage(); //ヘッドレス検知回避 $page3->addPreScript(" Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); "); //ユーザエージェント偽装 $page3->setUserAgent( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' . '(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' ); //ナンバーズ3サイトをアクセス $page3->navigate($url3)->waitForNavigation(); $this->waitForSelector($page3, '.js-lottery-number-pc'); $text3 = $page3->evaluate('document.documentElement.outerHTML')->getReturnValue(); } catch (BrowserException $e) { $error_flag = true; } finally { $page3->close(); } try { //ブラウザページを作成(これを使ってURLにアクセスする) $page4 = $browser->createPage(); //ヘッドレス検知回避 $page4->addPreScript(" Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); "); //ユーザエージェント偽装 $page4->setUserAgent( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' . '(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' ); //ナンバーズ4サイトをアクセス $page4->navigate($url4)->waitForNavigation(); $this->waitForSelector($page4, '.js-lottery-number-pc'); $text4 = $page4->evaluate('document.documentElement.outerHTML')->getReturnValue(); } catch (BrowserException $e) { $error_flag = true; } finally { $page4->close(); } $browser->close(); |
A子
まず「BrowserFactory」クラスをインスタンス化してる箇所のコメントって何?
B美
| which chromium[Enter] |
「/usr/bin/chromium」って出るんだけど、それを引数に渡しても動かないのよ
本当の場所は「/usr/lib/chromium/chromium」なので、そっちを指定しないといけないってわけ
(もちろん「/usr/bin/chromium」も存在するんだけど…)
C菜
B美
多くのWebサイトがスクレイピングを許可しないよう、アクセス制限をかけてるからね
(みずほ銀行のWebサイトも2026年3月17日から対策済み)
A子
C菜
あ、「try catch finally」って何ですか~?
B美
んで、もしも例外が発生したらcatchで捕捉するってわけ
あと、finallyブロックは例外の有る無しにかかわらず、必ず最後に実行される処理なのよ
A子
要するに、エラーが発生してもプログラムが止まらないし、必ず実行しなきゃいけない処理(finally)についても最後に確実に実行できるってことかー
(便利な仕組みじゃん)
B美
ライブラリを使うときには、割とよく記述するから覚えておいてね
あと、「waitForSelector」というメソッドを呼び出している箇所があるわよね
それをprivateメソッドとして記述します
|
//Webページ内にJavaScriptによる値の埋め込みが完了するまで待つ(デフォルト値は最大30秒)
private function waitForSelector($page, string $selector, int $timeoutMs = 30000) { $start = microtime(true); while (true) { $exists = $page->evaluate( 'document.querySelectorAll("' . $selector . '").length' )->getReturnValue(); if ($exists > 0) { return; } if ((microtime(true) - $start) * 1000 > $timeoutMs) { throw new \RuntimeException("Timeout waiting for: $selector"); } usleep(500000); } } |
A子
B美
それでOKよ
さて、どういうHTMLデータを取得できたのか、webrootの下にファイル出力してみましょうか
|
file_put_contents(WWW_ROOT.'numbers3.txt', $text3);
file_put_contents(WWW_ROOT.'numbers4.txt', $text4); |
この二行を「execute」メソッドの末尾に書きましょう
(ちなみに、コントローラーでは「WWW_ROOT」の記述は不要なんだけど、コマンドクラスでは必要だから注意してね)
C菜
| bin/cake lottery[Enter] |
あ、ファイルができましたよ~
A子
うーん、テキスト量が多すぎる…(苦笑)
なんとか当選番号データの箇所を見つけたよ
C菜
簡単に見つけられるような方法ってあるのでしょうか~?
B美
(でないとスクレイピングなんてやってられないわ)
ただ、長くなったので今回はここまでにしておきましょう
次回にスクレイピング方法を解説して、データベース登録までの流れをやっていくからね
C菜
それにしてもB美部長がやりたくないっておっしゃっていた理由が分かりましたね~
A子


