Puppeteerシリーズ第3回。今回は、テキストボックス テキストエリア セレクトボックス チェックボックス ボタンを自動で選択したりクリックしたりしてみます。
まずおさらいPuppeteerは、人形遣いという意味ですが、Webの世界では、Chromium(Chrome)を自動化するNode.jsで動作するライブラリのことです。
GoogleのChrome DevTools Teamが作りました。
ChromiumはGoogle Chrome、Opera、Microsoft Edgeなど多くのブラウザのベースになっています。
第1回は準備編、第2回はスクレイピング編を書きましたのでご参考ください。
さて、今回はより自動化っぽくWebサイトにある操作可能な要素を操作してみます。
テキストボックスに入力
HTMLフォームの操作、送信を行うために重要な属性は、nameです。
例えばGoogleのテキストボックスに何らかの文字を自動で入力する場合、https://www.google.com/ のテキストボックスを見てみます。
すると、name属性の値は “q” でした。
次に、取りたいname属性の値がそのページ内に1つだけなのかを確認します。1つだけであればそれをズバリ指定してあげればよいということになります。
そこで、ディベロッパーツールのConsole画面から以下を入力、実行します。
document.querySelectorAll('input[name="q"]');
document.querySelectorAll は、Webページ内で指定したセレクタの要素をすべて取得するものです。結果は以下のようになりました。
NodeListというのは、Nodeの要素をリスト化したもので、今回はdocument.querySelectorAllの結果のリスト化を示します。
更にここの ▶0: が0番目の要素 という意味になります。
今回は、▶0: の下に▶1: がないので、要素は1つだけであると特定ができました。
テキストボックスに何らかの文字列を入力するというコードは以下になります。
const puppeteer = require('puppeteer');
/**
* メインロジック.
*/
(async () => {
// Puppeteerの起動.
const browser = await puppeteer.launch({
headless: false, // Headlessモードで起動するかどうか.
slowMo: 50, // 指定のミリ秒スローモーションで実行する.
});
// 新しい空のページを開く.
const page = await browser.newPage();
// view portの設定.
await page.setViewport({
width: 1200,
height: 800,
});
// googleのWebページにアクセス
await page.goto('http://www.google.co.jp/');
// 検索窓のテキストボックスに「Puppeteer」を入力
await page.type('input[name=q]', 'Puppeteer');
// 検索ボタンをクリック
await page.click('input[value="Google 検索"]');
// 表示確認のためにブラウザを終了させない = 手動でブラウザを終了させる必要がある.
// await browser.close();
})();
ちゃんと、
await page.type('input[name=操作したいDOM要素のname]', '入力したい文字列');
となっているのがわかります。
テキストエリアに入力
HTMLタグでいうtextareaタグの部分です。
例えばCompassというサイトのテキストエリアに入力させる場合を考えてみます。
textareaタグの部分、name=”feedback” となっていますのでこちらが使えそうです。
次に要素がいくつあるのかを確認します。 ‘textarea[name=”feedback”]’を指定してdocument.querySelectorAllをみると、▶0: だけなので要素は1つだけだとわかりますのでそのまま使えそうです。
ページにアクセスするまではテキストボックスの場合と同じなのでその後のコードは以下になります。
// compassのWebページにアクセス
await page.goto('https://connpass.com/');
// 検索窓のテキストエリアに「テキストエリアへの入力サンプルです。」を入力
await page.type('textarea[name=feedback]', 'テキストエリアへの入力サンプルです。');
ちゃんと、
await page.type('textarea[name=操作したいDOM要素のname]', '入力したい文字列');
となっているのがわかります。
ラジオボタンで選択
次はラジオボタンです。以下のサイトで見てみます。
ラジオボタンは、「普通車」と「大型車」があります。要素を見ると、name属性が”carType”だとわかりますので、document.querySelectorAllで要素の個数を確認します。
document.querySelectorAll('input[name=“carType]’)
▶0: と ▶1: があるので要素は2つります。
「普通車」と「大型車」があるのでその2つだと言うことは想像がつきますが、それではどうやって判別させるかが問題になります。
今回はidがついていましたのでそこで判定をしていきます。document.querySelectorAll(‘#truck’); とコンソールで入力し、個数を調べると1つだけでしたのでこれで取得をします。
すると以下のようになります。(ページにアクセスするまではテキストボックスの場合と同じなのでその後のコードです。)
// 首都高技術の道路交通情報サイト「mew-ti」のWebページにアクセス
await page.goto('https://search.shutoko-eng.jp/search.html');
// 車種指定の「大型車」ラジオボタンを選択
await page.click('#truck');
動的に生成されるラジオボタンの操作も同じです。単一のセレクタであることを確認し、そこを指定してあげるという流れになります。スクレイピングのときと同じで、idがあればベストですが!
await page.evaluate
await page.clickでエラーになるときには、await page.evaluateを使います。
// 全日本空輸のWebページにアクセス
await page.goto('https://www.ana.co.jp/');
// 航空券の「片道」ラジオボタンを選択
await page.click('#m_ticket02');
こうなっていてエラーになっているのであれば、
// 全日本空輸のWebページにアクセス
await page.goto('https://www.ana.co.jp/');
// 航空券の「片道」ラジオボタンを選択
await page.evaluate(() => {
document.querySelector('#m_ticket02').checked = true;
});
こうします。
await page.evaluate内のコードを実行して、Promiseが返ってくるまで待機するという挙動になります。事前に取得可能かをチェックしたセレクタのチェック状態を操作し、ONにしたというわけです。
セレクトボックスで選択
セレクトボックスは、selectタグを使い、mulitple属性を指定して複数選択が可能です。
気象庁の天気予報ページでセレクトボックスを制御してみます。
該当部分のname属性は、elarealist であることがわかります。
document.querySelectorAll('select[name="elarealist"]');
みると、select要素でname=”elarealist”なのは1つだけです。
関東地方にしたい場合は、value=”206″ なので、以下のようにします。
// 気象庁天気予報のWebページにアクセス
await page.goto('http://www.jma.go.jp/jp/yoho/');
// 地方のセレクトボックスから「関東地方」を選択
await page.select('select[name=elarealist]', '206');
書き方としては、こちらです。
await page.select(セレクタ, 選択する値)
ここもエラーが出る場合は、await page.evaluateを使って書きます。
// 地方のセレクトボックスで「関東地方」を選択した後、「関東地方」(リストの6番目)のページに遷移する
await page.evaluate(() => {
document.querySelector('select[name=elarealist]')[6].selected = true;
goNextArea(document.querySelector('select[name=elarealist]').options[6].value)
});
ここまでを抽象化すると、以下になります。
- 捜査対象のセレクトボックス内の選択したいリスト番号のselectedをtrueにする
- 選択したリスト番号の値を用いて、画面遷移のJavaScriptを実行する
チェックボックスをクリック
チェックボックスの属性は、type=”checkbox” です。
チェックボックスを作成する
nameは、フォーム部品に名前をつける
valueは、送信される値を指定する
checkedは、初期値で選択された状態にする(checked)
を意味しています。
今回は、キューピー3分クッキングのチェックボックスをクリックしてみます。
例えば「にんじん」だと、name=”vegetable[]” になりますので、
要素の個数を見てみます。
document.querySelectorAll('input[name="vegetable[]"]');
するとなんと15個もある。。。
どうするべきかというところですが、ここでディベロッパーツールにある、Copy selectorをクリックしてみます。以下のようにDOMツリーをたどったセレクタを取得できます。
※ただし、構造の変更に弱いのでどこかが少しでも変わったら取得できなくなる。
#main > div > div.inner > form > div:nth-child(6) > div:nth-child(3) > ul > li:nth-child(3) > label > input[type=checkbox]
ここを取得するように書くとこのようになります。(ページにアクセスするまではテキストボックスの場合と同じなのでその後のコードは以下になります。)
// キューピー3分クッキングの検索オプションのWebページにアクセス
await page.goto('http://3min.ntv.co.jp/3min/search_option/');
// 野菜のチェックボックスの中から「にんじん」にチェックを入れる
await page.click('#main > div > div.inner > form > div:nth-child(6) > div:nth-child(3) > ul > li:nth-child(3) > label > input[type="checkbox"]');
複数のチェックボックスをすべてチェック
複数のチェックボックスを自動でチェックできたら気持ちいですよね!
人間がやるのも面倒ですし。。。
そこで、変数を作ってJavaScript定義をしましょう。
vagitable.checked =true;
の設定で取得ができます。
このキューピー3分クッキングサイトでは、チェックボックス選択状態の表示をcss内で画像定義しているので、
vegetable.parentNode.className = 'cbxbd c_on';
を加えます。
そして最後にawait page.addScriptTagで実行させてみます。最初からコードを載せます。
const puppeteer = require('puppeteer');
const checkAllVegetableScript = `
const vegetables = document.querySelectorAll('[name="vegetable[]"]');
vegetables.forEach(vegetable => {
vegetable.checked = true;
vegetable.parentNode.className = 'cbxbd c_on';
});
`;
/**
* メインロジック.
*/
(async () => {
// Puppeteerの起動.
const browser = await puppeteer.launch({
headless: false, // Headlessモードで起動するかどうか.
slowMo: 50, // 指定のミリ秒スローモーションで実行する.
});
// 新しい空のページを開く.
const page = await browser.newPage();
// view portの設定.
await page.setViewport({
width: 1200,
height: 800,
});
// キューピー3分クッキングの検索オプションのWebページにアクセス
await page.goto('http://3min.ntv.co.jp/3min/search_option/');
// 野菜のチェックボックスすべてにチェックを入れる
await page.addScriptTag({ content: checkAllVegetableScript });
// ブラウザの終了. (結果を確認しやすくするためにコメントアウトしてあります)
// await browser.close();
})();
const checkAllVegetableScript でJavaScriptの定義をし、スタイルの制御のため、vegetable.parentNode.className = ‘cbxbd c_on’; を記載しています。
定義したJavaScriptを実行するのが、await page.addScriptTag({ content: checkAllVegetableScript }); になります。
ボタンクリック
ボタンには2つのやり方があります。inputタグとbuttonタグです。それぞれの違いを明確にして適切な方を利用していきます。
inputタグは、ボタン上に表示する文字列はvalue値そのものでサーバに送信する値にもなります。
一方のbuttonタグだと、value値はあくまでサーバに送信する値なので、ボタン上に表示する文字列はvalue値とは別に設定できます。
例えば、こんな感じのHTMLです。
<body>
<form>
<label>inputタグ例</label><br>
<input type="submit" name="yesInput" value="はい">
<input type="submit" name="noInput" value="いいえ">
</form>
<form>
<label>buttonタグ例</label><br>
<input type="button" name="yesButton" value="はい" onclick="onclickYes();">
<input type="button" name="noButton" value="いいえ" onclick="onclickNo();">
</form>
</body>
それでは、livedoorのサイトでボタンクリックをやっていきます。
「検索」ボタンを見ると、 name=”search_btn” がそれに該当します。
検索窓はname=”q” ですのでそこに Puppetter API という文字を入れて検索ボタンをクリックします。
// livedoorのWebページにアクセス
await page.goto('http://www.livedoor.com/');
// 検索窓のテキストボックスに「Puppeteer API」を入力
await page.type('input[name="q"]', 'Puppeteer API');
// 検索ボタンをクリック
await page.click('button[name="search_btn"]');
まとめ
今回は、様々なボックスやテキストエリアやボタンの操作を見てきました。スクレイピングのときと同じで以下に唯一の要素を見つけてそれをどう処理してあげるかを考えて自動化を進めていくとPuppeteerユーザーになれそうです!