Node.js で selenium-webdriver と chromedriver を使って Chrome ブラウザを自動操作してみる

Angular に組み込まれている、Protractor という E2E テストツールを通じて、Selenium Webdriver を少し触ってきていた。今回はテストツールとしてではなく、ウェブスクレイピングを行うための手段として、Selenium Webdriver と、Chrome を操作するための ChromeDriver を利用してみようと思う。

目次

selenium-webdriver と chromedriver のインストール

作業ディレクトリを作ったら $ npm init -ypackage.json を生成しよう。selenium-webdriver は以下のようにインストールする。

$ npm install -S selenium-webdriver
+ selenium-webdriver@4.0.0-alpha.1

selenium-webdriver は単体では動作せず、動かしたいブラウザに対応する WebDriver を別途インストールしてやる必要がある。Chrome ブラウザを操作する際は ChromeDriver が必要になる。

chromedriver は、バイナリをダウンロードして PATH を通してやる必要がある。設定が面倒くさいので、今回は npm でグローバルインストールすることで、PATH を通して実行権限を付与してやる。

$ npm install -g chromedriver
/usr/local/bin/chromedriver -> /usr/local/lib/node_modules/chromedriver/bin/chromedriver

> chromedriver@2.44.1 install /usr/local/lib/node_modules/chromedriver
> node install.js

ChromeDriver binary exists. Validating...
ChromeDriver 2.44.609545 (c2f88692e98ce7233d2df7c724465ecacfe74df5)

ChromeDriver is already available at '/var/folders/5g/lmm9nvkx4y90m8v0lc2_vw0c0000gp/T/chromedriver/chromedriver'.
Copying to target path /usr/local/lib/node_modules/chromedriver/lib/chromedriver
Fixing file permissions
Done. ChromeDriver binary available at /usr/local/lib/node_modules/chromedriver/lib/chromedriver/chromedriver
+ chromedriver@2.44.1
added 98 packages from 74 contributors in 5.21s

ChromeDriver を手動でダウンロードする際は、以下よりダウンロードできる。

Firefox や IE を動かしたい場合は、別途それらの WebDriver を用意する必要がある。以下の、selenium-webdriver の API ドキュメントのトップページにて紹介されているので、それを参照のこと。

ブラウザを起動して任意のページを表示するまで

最も単純なコードを掲載する。とりあえず Chrome ブラウザを開いて、Google のトップページを表示したら、5秒後に終了する、というコードだ。

const { Builder } = require('selenium-webdriver');

let driver;
(async () => {
  try {
    driver = await new Builder().forBrowser('chrome').build();
    
    await driver.get('https://www.google.co.jp/');
    await driver.sleep(5000);
  }
  catch(error) {
    console.error(error);
  }
  finally {
    if(driver) {
      await driver.quit();
    }
  }
})();

このスクリプトを main.js のように保存し、$ node main.js と実行してやれば、Chrome ブラウザが勝手に開いてゴニョゴニョ動作してくれるはずだ。

ヘッドレスモードにする

見た目上ブラウザを開かずに、裏方で作業させたい場合は、ヘッドレスモードにする。

const webdriver = require('selenium-webdriver');
const { Builder } = webdriver;

let driver;
(async () => {
  try {
    const capabilities = webdriver.Capabilities.chrome();
    capabilities.set('chromeOptions', {
      args: [
        '--headless',     // ヘッドレスモードにする (見た目上のブラウザが開かなくなる)
        '--disable-gpu',  // UI を安定させるため、余計なアクセラレーションを動作させないようにする
        '--no-sandbox',   // 保護機能を無効にする
        '--window-size=1980,1200'  // ウィンドウサイズを変更する
      ]
    });
    driver = await new Builder().withCapabilities(capabilities).build();
    
    await driver.get('https://www.google.co.jp/');  // 裏で Google が開くものの、実行中に画面上では確認できない
    await driver.sleep(5000);
  }
  catch(error) {
    console.error(error);
  }
  finally {
    if(driver) {
      await driver.quit();
    }
  }
})();

スクレイピングなどする際はデスクトップ上にブラウザを開いてあれやこれや動かさなくても良いので、ヘッドレスモードが有効だ。

ところで asyncawait って何

さて、今回のコード、何やら即時関数の中に async とか await とかいう単語が並んでいる。コレは何なのかというと、戻り値が Promise な関数を同期的な関数のように記述できる asyncawait という仕組みだ。

結局は戻り値は Promise なので、asyncawait を使わず then() で繋げて記述しても問題はない。ただ、要素特定のための findElements() や、wait()getText() など、ほとんどの API が Promise を返すので、ココで asyncawait に慣れておいた方が、簡潔にコードを記述できるようになるだろう。

今回はココまで

ココから、要素を特定して、フォームに文字列を入力したり、リンクをクリックして画面遷移したりして、実際のウェブスクレイピングを組んでいくことになる。

要素特定のための findElement()By、要素の存在チェックをするための until など、Selenium Webdriver の API を覚えて使いこなす必要があるので、公式の API リファレンスは要熟読。

その他、基礎の基礎は以下の文献が参考になった。