自動化厨のプログラミングメモブログ │ CODE:LIFE

Python/VBA/GAS/JavaScript/Raspberry Piなどで色んなことを自動化

ブラウザ×JavaScriptだけで複数ページに渡る株価データを自動取得 Webスクレイピング超入門⑤

ブラウザ操作の自動化は別にPythonやSelenium/Playwright/WebDriverとか使わなくてもブラウザだけで出来るよという話。

「自動化して業務効率アップ!」 とか「株価データを簡単スクレイピング!」みたいな話にはだいたいRPAツール使うとか、Python使えばこんなに楽にとかの情報が出てきますが、実はブラウザ操作の自動化に限って言えばそういうの要らないです。

そもそもブラウザはJavaScriptを実行できるので、ページの情報を取るのも、入力するのも、ボタンを押すのもJavaScriptでできます。

↓とりあえず動くサンプル。コードの解説は後半で書きます。

youtu.be

ブラウザだけで複数ページをスクレイピング

本日の株価上昇率ランキングを全部取ってくるやつ

// スリープ関数
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// スクレイピング用のサブウィンドウを生成
const url = "https://kabutan.jp/warning/?mode=2_1&market=0&capitalization=-1&stc=&stm=0&page=1";
const subWindow = window.open(url, "scraper", "width=800,height=800,scrollbars=yes");

// データの入れ物を用意
let nextPageLink;
let scrapedData = [["コード", "銘柄名", "市場", "株価", "前日比", "前日比(%)", "出来高", "PER", "PBR", "利回り"]];

// 次へリンクがなくなるまで繰り返し
do {
  // 読み込み完了を待機
  do {
    await sleep(500);
  } while (subWindow.document.querySelector(".stock_table.st_market") === null);

  // テーブル取得
  const table = subWindow.document.querySelector(".stock_table.st_market");
  [...table.querySelectorAll("tbody tr")].forEach((tr) => {
    const r = [...tr.querySelectorAll("td,th")].map((td) => td.innerText);
    scrapedData.push([r[0], r[1], r[2], r[5], r[7], r[8], r[9], r[10], r[11], r[12]]);
  });
  console.log(`${scrapedData.length - 1} rows scraped.`);

  // 次へリンクがあればクリック
  const aTagList = [...subWindow.document.querySelectorAll(".pagination a")];
  nextPageLink = aTagList.find((a) => a.innerText === "次へ>");
  if (nextPageLink) nextPageLink.click();
} while (nextPageLink);

// サブウィンドウを閉じる
subWindow.close();

// 出力
console.table(scrapedData);

スプレッドシートやExcelに貼り付けられる形式でコピーするやつ

// 2次元配列をTSVに変換してコピー
const arrayToTSV = (arr) => arr.map((row) => row.join("\t")).join("\n");
copy(arrayToTSV(data));

解説

前述のスクリプトを上から順に解説していきます。

①sleep関数

JavaScriptにはPythonの sleep(1) のような簡単な書き方が無いのですが、ES6では以下の書き方でOK。昔に比べるとちょっと楽ですね。

// スリープ関数
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

ミリ秒単位なので1秒待機させたい場合は await sleep(1000) のように記述します。 今回のスクリプトではページの読み込み完了を待機させるために用いています。

②window.open(これが大事)

JavaScriptでは window.open(url, "scraper", "width=800,height=800,scrollbars=yes"); のように書くと小さな別ウィンドウを開くことができます。いわゆるポップアップです。

複数ページのスクレイピングを行いたい と 次へ リンクをクリックしたり、 location.href にURLを代入したりするとその時点でJavaScriptが動作しているページから移動してしまうため2ページ目以降に処理を引き継ぐことができません。

そこでサブウィンドウを開いて親ウィンドウからサブウィンドウを操作することでページング処理を可能にしています。

// スクレイピング用のサブウィンドウを生成
const url = "https://kabutan.jp/warning/?mode=2_1&market=0&capitalization=-1&stc=&stm=0&page=1";
const subWindow = window.open(url, "scraper", "width=800,height=800,scrollbars=yes");

サブウィンドウを開く

このように subWindow に代入されたwindowは subWindow.document でDOMにアクセスが可能です。(あちこちで言ってますが本当にもっと早くこれに気付けば良かったと悔やんでます...)

③データの入れ物を用意

nextPageLink はその名の通り「次へ>」のリンクタグを代入するためのものです。繰り返し処理の条件にも使うのでdo-whileの外側で定義しています。 scrapedData は取得したテーブルのデータを格納しておくための配列です。ヘッダー行は直接定義しています。この変数も外側で定義。

// データの入れ物を用意
let nextPageLink;
let scrapedData = [["コード", "銘柄名", "市場", "株価", "前日比", "前日比(%)", "出来高", "PER", "PBR", "利回り"]];

④読み込み完了を待機

最初に宣言した sleep 関数をループ内で呼び出しています。 ループ条件を「取得対象のテーブル要素が null」とすることで、取りたいデータが見つかるまで0.5秒ずつ待機します。

  // 読み込み完了を待機
  do {
    await sleep(500);
  } while (subWindow.document.querySelector(".stock_table.st_market") === null);

⑤テーブル取得

ここが少々ごちゃっとしてますが、テーブルを取得→テーブル行ループ→必要な列のテキストを取り出して scrapedData にどんどん追加しています。

r[0] というのは1つ目の列(株探の コード 列)指定。チャートや詳細へのリンクは不要なので 3, 4 は飛ばしてます。

列インデックス番号を指定して不要な列は除外

  // テーブル取得
  const table = subWindow.document.querySelector(".stock_table.st_market");
  [...table.querySelectorAll("tbody tr")].forEach((tr) => {
    const r = [...tr.querySelectorAll("td,th")].map((td) => td.innerText);
    scrapedData.push([r[0], r[1], r[2], r[5], r[7], r[8], r[9], r[10], r[11], r[12]]);
  });
  console.log(`${scrapedData.length - 1} rows scraped.`);

⑥次へリンクがあればクリック

.pagination a というセレクタでページ切り替え用のリンクを全て取得し、findメソッドで「次へ>」というテキストを含むものを探す。→ 見つかった場合はそれをクリックします。 同時にページごとのループ条件にもなっているのでクリックできたら ④読み込み完了を待機 から再度テーブルデータの取得が行われることになります。

  // 次へリンクがあればクリック
  const aTagList = [...subWindow.document.querySelectorAll(".pagination a")];
  nextPageLink = aTagList.find((a) => a.innerText === "次へ>");
  if (nextPageLink) nextPageLink.click();

⑦サブウィンドウを閉じてデータを出力

これは見たままですがサブウィンドウを閉じて、取得した全データをconsoleにテーブル表示します。

// サブウィンドウを閉じる
subWindow.close();

// 出力
console.table(scrapedData);

⑧スプレッドシートやExcelに貼り付け可能な形式でコピー

ここでは2次元配列をTSV形式に変換してからクリップボードに格納します。 CSVがカンマセパレーションなのに対して、TSVはタブセパレーションです。 スプレッドシートやExcelではTSVデータをいい感じにセルに貼り付けできるのでこの方法を採用しています。

// 2次元配列をTSVに変換してコピー
const arrayToTSV = (arr) => arr.map((row) => row.join("\t")).join("\n");
copy(arrayToTSV(scrapedData));

※これは⑦までの処理が終わったら改めて実行します。 copy を用いると簡単にデータをクリップボードに格納できて良いのですが、何かの制限があり別途実行する必要がありそうでした。

まとめ

このやり方のいいところは「RPAソフトのインストールやPythonの環境構築不要」「WebDriverもブラウザの特殊設定も不要」つまりブラウザさえあればどんな環境でも動かせるという点。

今まではページを移動する処理が入る場合はPythonでやるしか無いと思い込んでいましたが、サブウィンドウを開けば良いんだ...!! と気づいてからはわざわざPythonで書くことは減りました。自動実行したければTampermonkeyで設定するもよし、ブックマークレットに変換しておいて実行するもよし。

複数のPCを持っている場合はチャットでコードを送るだけで良いので大変手軽です。

是非お試しあれ。