Node.js で Web スクレイピング


突然ですが、社内のシステム(Web アプリ)が非常に使いにくいです。社外から見えるのはいいけどスマホ用のデザインがないとか、API がないから自動化しにくいとか。

些細なことですが日々ストレスは溜まっていき、先日うっかり自分で作ってしまえばいいのでは?と思い立ってしまいました。ひとまず、Webアプリからデータを抜き出す部分について書き出しておこうと思います。

まず、どうやってデータを取得するのかですが、いわゆる Web スクレイピングという手法を使います。大層な名前がついていますが、単純に HTTP の GET や POST を投げて、返ってきた HTML を解析するだけです。

今回はなぜか Node.js を使います。(最近の私の流行りなので)
node.js と npm は入っている前提ですすめます。

使ったもの一覧

Node.js 6.9.4
cheerio 0.22.0
request 2.76.0

request は HTTP 通信をする時に、 cheerio はサーバーからとってきた HTML を jquery のようにアクセスできるようにするために使います。

導入

適当なフォルダを作り、そこで以下のコマンドをたたく

npm init
npm install request -S
npm install cheerio -S

ログイン画面の突破

社内システムなのでやっぱり認証があります。ふつうに ID とパスワードを入力する欄とログインボタンがあるタイプです。ログインすると Cookie にセッション ID が保存されます。攻撃に耐えられるのかと非常に不安になるような見た目をしています。

まずは実際にログインページをブラウザで開き、開発者ツールを使いどこにどんな情報を送っているか調べます。それを元に以下のコードを書き換えてください。

'use strict';

const request = require('request');
const cheerio = require('cheerio');

const jar = request.jar(); // Cookie を格納するジャー(広口のびん)

new Promise((resolve, reject) => {
  const option = {
    url: 'http://zu-min.com', // ログイン情報の送り先
    jar: jar,
    form: { // POST で送る内容
      'id' :'username',       // ユーザー名(キーは input タグの name 属性を見て書き換える)
      'pass' : 'password',    // パスワード(キーは input タグの name 属性を見て書き換える)
      // 他に何か送っていれば追記
    },
    headers: { // HTTP ヘッダー
      'content-type': 'application/x-www-form-urlencoded',
      // システムによっては特殊なヘッダーを送っている場合もあるので注意
    },
    followAllRedirects: true,
  };

  request.post(option, function(error, response, body) {
    if (!error && response.statusCode === 200) {
      resolve(body);  // 取得した HTML を返却
    } else {
      reject(new Error('error: '+ response.statusCode));
    }
  });
}).then((body) => {
  const $ = cheerio.load(body); // jquery っぽくする
});

他のページを見る

セッションID が jar に保存されるので、その jar を使えばログイン後にしか見えないページも取得し放題です。

// 上のコードの続き(「;」をとるのを忘れないように)
.then(() => {
  const option = {
    url: 'http://zu-min.com', // 見たいページ
    jar: jar,                 // 先ほどの jar をセット
    qs: { // GET パラメーターで送る内容
      'action' :'list',       // ユーザー名(キーは input タグの name 属性を見て書き換える)
      // 他に何か送っていれば追記
    },
    followAllRedirects: true,
  };

  return new Promise((resolve, reject) => {
    request.get(option, function(error, response, body) {
      if (!error && response.statusCode === 200) {
        resolve(body); // 取得した HTML を返却
      } else {
        reject(new Error('error: '+ response.statusCode));
      }
    });
  });
}).then((body) => {
  const $ = cheerio.load(body); // jquery っぽくする
});

返ってきた内容を元に、欲しいデータを取得します。大体、このデータが入っている場所はこの id が付いているとか、N番目の table タグのX列目とかで決まっているはずです。実際のページをブラウザで見つつ、どのようなセレクタを使えば欲しい情報を取得できるか考えます。

Cookie を保管したい場合

一時的にセッションID を DB とかに保存したい場合は jar を一度 JSON にするといいです。request のドキュメントにはない方法なので、のちのち使えなくなるかもしれませんが・・・。

const tough = require('tough-cookie'); // request と一緒にインストールされる

// JSON 化
const json = jar._jar.toJSON();

// JSON から戻す
const newJar = request.jar();
newJar._jar = tough.CookieJar.fromJSON(json);