読者です 読者をやめる 読者になる 読者になる

Itsukaraの日記

最新IT技術を勉強・実践中。最近はDeep Learningに注力。

NodeJS + Selenium IDE/WebDriverで最強スクレイピング

ITのお勉強 スクレイピング でんき家計簿 シンギュラリティ Javascript

ここ数日、「DIGAの番組表」と「東京電力の『でんき家計簿』」でスクレイピング用のプログラムを作成してみましたが、NodeJS+CasperJS+PhantomJSよりも、NodeJS+Selenium IDE/WebDriverの方が、簡単でした。スクレイピングには、NodeJS+Selenium IDE/WebDriverが最強な気がします。そこで、NodeJS+Selenium IDE/WebDriverを使ったスクレイピングの簡単な手順をメモとして残しておきたいと思います。

NodeJS + Selenium IDE/WebDriverのうれしい点

Seleniumでは、スクレイピングしたいサイトにアクセスする手順をSelenium IDEが自動的に記録できるます。また、この記録結果を、適切な形式でエクスポートすると、ほぼそのままの形で、NodeJS + Selenium WebDriverで流用できます。以下、簡単に説明します。

アクセス手順をSelenium IDEで自動的記録

FireFoxSelenium IDE(FireFoxのPlugin)がインストール済みの前提で記載します。

Selenium IDEをインストールすると、FireFoxの画面右上にSelenium IDEのボタンが現れます。(下図のマウスカーソルの部分)
f:id:Itsukara:20160411232142j:plain

Selenium IDEのボタンをクリックするとSelenium IDEの画面が現れて、対象となるサイトへの操作が自動的に記録されます。下記は、「東京電力の『でんき家計簿』」にログインし、電気使用量の時間別グラフを表示、「前日」のデータを表示するまでの操作手順です(IDとPASSWORDは伏せています)。
f:id:Itsukara:20160412001329j:plain

どこをクリックすればよいか、何を入力すればよいのか、Selenium IDEが自動的に記録するので、非常に便利です。また、Selenium IDEでは、記録した操作手順(テストケースと呼んでいます)を実行して試すことができます。テストケースを試すには、「アクション」メニューの「現在のテストケースを実行」を選択するか、画面左上の緑三角ボタンをクリックします。

テストケースがうまくいかない場合は、エラーとなったコマンドが赤く表示されるので、直ぐに分かります。Selenium IDEでは、エラーとなったコマンドを削除・修正したり、途中にコマンドを挿入したり、など、色々な試行が、画面を見ながら簡単にできます。この点は、CasperJSと比べて段違いに便利と思います。(CasperJSでは画面ショットを取らないと、現在の状態が分からない...)

下記は、「DIGAの番組表」での操作手順(テストケース)を、実際に動くように編集したものです。画面表示を待つためにコマンド「pause(3000)」を幾つか挿入してます。また、記録されていたコマンド「selectWindow(name=leftframe)」は、エラーとなるために削除しました。(削除しても問題なく動きました)
f:id:Itsukara:20160412001330j:plain

Selenium IDEのテストケースをエクスポート

NodeJS + Selenium WebDriverで流用できるようなコマンドを得るためには、Selenium IDEのテストケースを、「Java / JUnit 4 / WebDriver」という形式でエクスポートします(下図参照)。Javaの形式でエクスポートするのですが、NodeJSで、その一部をほぼそのままの形で流用できます。
f:id:Itsukara:20160411232156j:plain

下記は、「東京電力の『でんき家計簿』」でのアクセス手順を上記形式でエクスポートした結果のうち、流用できそうな部分を抜粋したものです。

  public void setUp() throws Exception {
    driver = new FirefoxDriver();
    baseUrl = "https://www.kakeibo.tepco.co.jp/";
    driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
  }

  @Test
  public void test1() throws Exception {
    driver.get(baseUrl + "/dk/aut/login/");
    driver.findElement(By.id("idId")).clear();
    driver.findElement(By.id("idId")).sendKeys("xxxxxxxx");
    driver.findElement(By.id("idPassword")).clear();
    driver.findElement(By.id("idPassword")).sendKeys("xxxxxxxx");
    driver.findElement(By.id("idLogin")).click();
    driver.findElement(By.id("idNotEmptyImg_contents01.jpg")).click();
    driver.findElement(By.id("bt_time_view.jpg")).click();
    driver.findElement(By.id("doPrevious")).click();
  }

上記を次のような形に少し追加・修正し、JSファイルとして保存し、nodeで実行すると、うまく動きます(idIdとidPasswordの設定値は修正が必要)。後は、下記の関数「extract_function()」の部分で、FireFox側で実行する情報取得スクリプトを追加するだけです。これに関しては、Developer toolsで色々と試せばよいので、非常に簡単です。

// Initial Setting
var webdriver = require('selenium-webdriver'),
    By = webdriver.By,
    until = webdriver.until;

var driver = new webdriver.Builder()
    .forBrowser('firefox')
    .build();

// @Before
    baseUrl = "https://www.kakeibo.tepco.co.jp/";
    driver.manage().timeouts().implicitlyWait(3000);

// @Test
    driver.get(baseUrl + "/dk/aut/login/");
    driver.findElement(By.id("idId")).clear();
    driver.findElement(By.id("idId")).sendKeys("xxxxxxxx");
    driver.findElement(By.id("idPassword")).clear();
    driver.findElement(By.id("idPassword")).sendKeys("xxxxxxxx");
    driver.findElement(By.id("idLogin")).click();
    driver.findElement(By.id("idNotEmptyImg_contents01.jpg")).click();
    driver.findElement(By.id("bt_time_view.jpg")).click();

// Extract
driver.executeScript(extract_function, "test1", "test2").then(function(extracted_data) {
    // return value of extract_function() can be accessed by extracted_data
    console.log(extracted_data); // "test1,test2" will be written
});

// Function executed in FireFox
function extract_function(arg1, arg2) {
  var extracted_data;
  // Here, extract data from web page to extracted_data
  extracted_data = arg1 + "," + arg2;
  
  return extracted_data;
}

なお、上記では、1つのページの情報を取得するのみでしたが、「前日」のデータを表示するための操作である「driver.findElement(By.id("doPrevious")).click();」など、他の操作も組み合わせれば、全ての日のデータを取得できます。

あとがき

最初のうちは、上記の一部しか把握していなかったため、手作業でNodeJS用のスクリプトを書いていました。そもそも、Java向けにエクスポートしたファイルが、ほとんどそのままでJavascriptで使えるとは思っていませんでした。試してみたら、そのまま使えるので驚きました。

ここに書いた内容が、少しでも役立てば幸いです。

なお、上記で書いた内容とは少し異なりますが、「東京電力の『でんき家計簿』」のデータ取得用スクリプトは、下記をご覧ください。
itsukara.hateblo.jp