Puppeteer を使ってウェブスクレイピングしてみた

Puppeteer を使って初めてウェブスクレイピングしたことをメモします。

【目次】

[1]はじめに

ある事がきっかけでウェブスクレイピングをやる機会がありました。

私にとっては初めての経験だったのですが、面白かったので(ほぼ自分用ですが)メモしておくことにしました。

さて、ウェブスクレイピングにもいろいろなやり方があるようですが、まとまった量のデータを目的とするデータの形式で収集しようとすると、プログラムを作るのが手っ取り早いです。

また、対象がとてもシンプルなウェブページであれば、HTTP GET のレスポンスをライブラリを使って解析するのが効率的だと思いますが、JavaScriptが動作に関係する(例えば、ボタンをクリックして動作する)ようなサイトは、欲しいページ情報を得るまでが少々面倒だったりします。

そのような場合は、実際のブラウザを使って人が行う操作を自動化するのが手っ取り早いように思います。

そこで、今回は Puppeteer というツール(ライブラリ)を利用することにしました。

上記 Puppeteer のページには以下のように書かれています(Google翻訳による)。
  • Puppeteer は、DevTools プロトコルを介して Chrome/Chromium を制御するための高レベル API を提供する Node.js ライブラリです。 Puppeteer はデフォルトでヘッドレス モードで実行されますが、完全な (非ヘッドレス) Chrome/Chromium で実行するように構成できます。

また、FAQによると、Chrome DevTools チームがライブラリを管理しているとのことです。

Puppeteer 以外にも有名なツールはありますが、今回の目的は Chrome でページ情報を取得できれば十分であること、そしてサンプルコードを見ると比較的シンプルな印象だったため、まずは Puppeteer を利用することにしました。

当初は「ウェブスクレイピングなんて面倒だなー」という印象でしたが、実際にやってみると「なかなか面白い分野だなー」という印象に変わりました(笑)。

また、セマンティックウェブ系の本で「Webは巨大な知識や情報のデータベース」といった旨の記述を読んだ気がしますが、高度なモデルを持ち出さなくても、スクレイピングツールがそのクエリ言語のような働きをする部分がある事を垣間見た気がします。

[2]感想など

(1)Puppeteer に関する感想

最初に感想を書いておきます。

正直言って、「すばらしい!」です。

Puppeteer のサイトのコードサンプルを参考にして、目的のサイトに適用してみると、ほぼ動いてしまったので(笑)、他のツールを検討することもなく作業終了してしまいました(笑)。

やろうとしていたことが単純だったこともあり Puppeteer の使い方というか、Puppeteer に依存する部分は、私のようなウェブスクレイピング初心者でも半日程度でプログラムが書けました。
(但し、私は JavaScript / Node.js の知識が多少あり、Chrome の DevTools も少しは使っているという予備知識はありました。)

(2)ウェブスクレイピングにおける留意点など

Puppeteer は予想以上に簡単に使えましたが、それよりも、ウェブスクレイピングの本質的なところで時間をとってしまいました。

これは勉強になったという事でもありますので、備忘録としてメモしておきます。

①利用規約などの問題

サイトに利用規約やポリシーのようなものが明示されていれば、スクレイピングの可否判断や取得したデータの利用範囲が分かりやすいのですが、明示されていないサイトもあります。

明示されてなくても、Google のクローラが収集して検索結果に表示されているサイトなら問題ない気もしますが、何らか法的問題が疑われる場合は、それなりの検討と対応が必要だと思います(スクレイピングしなければよいだけですけど)。

もっとも、今回私はこの問題で悩んだわけではありませんが、初めてウェブスクレイピングをやるにあたって、一番気を使ったところでした。

②スクレイピングにもシステム面の設計が必要

数ページ程度のサンプル収集を行った時に気付いたことですが、目的によっては、効率面やデータ容量の問題などを考えておく必要があります。

1ページ当たりどれくらいの時間がかかり、どれくらいの容量が必要かがわかると、全部でどれくらいの時間とディスク容量が必要かの目安が分かります。

予想時間によっては並列度を上げるなどの対策も必要になるかもしれませんが、収集側の都合だけでなく、サイト側への攻撃になってしまうような動作はまずいので、程よい調整が必要だと思います。

もっとも、私の場合、攻撃できるようなコンピュータリソースを持ち合わせていないため、自分の手持ちのリソースに優しい形が落としどころでした(笑)。

それよりもデータ容量や取得後の格納構造の問題に頭を使いました。

テキストデータだけであれば、容量は問題にならないこともあると思いますが、画像なども対象にすると、何も考えず、そのまま保存し続けると、ディスクをかなり消費します。OSによってはディレクトリ内のファイル数上限もあるため、格納方法も検討しておく方がよいと思います。また、後からデータを整理するにしても、無駄なデータが多いと大変です。

結果として、収集後の利用シナリオを先に検討し、保存するデータのレイアウトを決めてから実行しました。

これは考えて見れば当たり前のことですが、ウェブスクレイピングにおいては、欲しい情報がそのまま使える状態で揃っているわけではないので、取得するデータの構成だけではなく、何を捨てるか?という観点も大事だと感じました。

ウェブスクレイピングを初めてやってみてわかったことは、実は Puppeteer を使うためにかけた時間はあまり多くなくて、結局はそれ以外のシステム設計面に時間がかかった気がします。

しかし恐らく一番時間がかかって難しいのは、収集したデータをどのように整理、再構成して新しい知識を作り出すか?などの、スクレイピング後の応用面だと思います。

でも、スクレイピングしてデータが集まると、データが無い時に比べて、何だか1つステージが進んだような気がします(錯覚?)。

[3]Puppeteer の使い方など

Puppeteer 初心者の私が書くのも気が引けますが、自分の備忘録をかねて、私が利用した範囲で Tips 的にメモしておきます。

(1)参考にしたサイト

Puppeteer はネット検索すれば沢山情報がありますので困ることは無いと思いますが、当然古くなった情報もありますので、まず私が参考にしたのはやはり本家サイトです。

ここの Getting Started や Usage を読むと(読むというより見ると)、インストール方法や簡単なコード例が書かれていますので、雰囲気が分かります。

加えて、上記サイトでも紹介されている記事ですが、以下の記事は大変参考になりました。

(2)インストール

これは拍子抜けするくらい簡単で、以下のコマンドだけで Chromium も一緒にセットアップされます。Node.js のプログラム開発環境があれば、直ぐに使えます。

npm i puppeteer
# or `yarn add puppeteer`
# or `pnpm i puppeteer`

なお、同梱された Chromium 以外も利用できます。

(3)コードの骨格

Get Started にもある通り、超基本的な骨格は以下のようなコードです。

import puppeteer from 'puppeteer';

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.goto(対象サイトのURL');

  // page に対する操作。

  await browser.close();
})();

やることは簡単で、ブラウザを起動して(launch)、対象サイトのページに移動して(page.goto)、必要な操作(情報を取ったり、文字入力やボタンをクリックする等のアクションを実行したり、ページ遷移させたり、等々)を行って、終了(close)します。

(4)ページ操作のメソッドなど

page オブジェクトから情報を抽出する方法として、evaluate、$、.$eval、$$、$$eval など、いろいろありますが、私は殆ど evaluate メソッドだけで事足りました。

以下は Usage のサンプルコードから evaluate メソッドを利用している部分の抜粋です。

const links = await page.evaluate( resultsSelector => {
    return [...document.querySelectorAll( resultsSelector ) ].map( anchor => {
        const title = anchor.textContent.split('|')[0].trim();
        return `${title} - ${anchor.href}`;
    });
}, resultsSelector);

evaluate メソッドは、evaluateに渡している関数をブラウザ側で実行し、その結果を Node.js 側に戻してくれます。

私の場合は、Node.js 側のプログラムを書く前に、対象サイトのページを Chrome で表示し、DevTools を使って欲しい情報を抽出して JSON 形式で返す JavaScript 関数を作りました。これを evaluate の引数に与える形で作業を行いました。

DevTools を使うと、画面の要素に対応するセレクタを出力してくれたりするので、個人的にはコードの作成効率が良いように思えます。

また evaluate は、ブラウザ内でコードを実行する際のパラメータ(上記のコード例だと resultsSelector)を渡すことができるし、情報の取得に限らず、DOM を使っていろいろなアクションを実行することができます。(但し、制約もある。)

ページ毎に異なる処理を、ブラウザ側の JavaScript を利用して標準化した JSON 形式で返すように evaluate を利用することで、Node.js 側はページに依存しないデータを扱えるように構成できました。

また evaluate を利用すると、Puppeteer に深入りすることなく、Puppeteer を使うことができます(笑)。これは Puppeteer の学習時間が短かかった要因かも。

とはいいつつも、evaluate だけでは制御が難しい事もあります。

例えば、検索ボックスなどに文字列を入力してボタンをクリックし、結果画面に切り替わるのを待つ、というパターンです。

これは以下のようなコードで対応しました。

// 文字をテキストボックスに入力
await page.type( 'セレクタ', 入力文字列 );

// ボタンをクリックしてページ遷移を待つ
await Promise.all([
    page.waitForNavigation(),
    page.click( ‘セレクタ’ ),
]);

詳しくは以下のページに説明があります。

(5)画像のダウンロードなど

Puppeteer でアクセスしたページに画像のリンクが含まれている場合、それをダウンロードしたいこともあります。

ネットで検索するといろいろとやり方が出てきますので、Puppeteer でできることは間違いないと思いますが、私は今回は利用していません。

理由は、(私にとって)不要な画像や、リンクの重複が多くある事が予め分かっていたためです。重複は判断しやすいですが、不要かどうかは都度判断は難しい場合があります。かといって、全ダウンロードするとディスクの無駄ですし、後から削除するのも面倒です。

そこで、2つのフェーズで実行するようにしました。

まず、Puppeteer を使ってコンテンツとともに、画像へのリンク情報も収集します。
(imgタグのsrc属性を取れば、元ソースは相対パスで書かれていても、絶対パスに展開してリンクを取得できます)。

続いて、対象ページを全てクロールした後に、(私にとって)不要なリンクの削除と重複を取り除いたダウンロード用の URL リストを作るプログラムを作成しました。

この URL リストをもとに、Puppeteer を使わず、画像をダウンロードするだけの普通の Node.js プログラム(axios を利用)を作成して実行(ダウンロード)しました。

これは関心の分離にも少し関係しますが、Puppeteer で全てやるよりも、今回は判断と実行を分離して考えることで、結果としてプログラムもスッキリしたし、Puppeteer の学習時間も少なかった要因かと思います。(ケースバイケースですけど。)

(6)非ヘッドレスでの実行

Puppeteer のデフォルト動作はヘッドレスモードなので、Puppeteer がサイトを訪れている状況を見た目では把握できません。

ヘッドレスモードの方がパフォーマンス面で有利なため、それは問題ないのですが、非ヘッドレスモードで動作させることもできます。

const browser = await puppeteer.launch( {headless: false} );

このように起動すると、画面の動きを見ることができるので、動作確認ができます。

個人的には Chromium がパコパコ動いているのを見ることができて(人間には操作できない速度で動いて)、楽しかったです(笑)。

「Puppeteer」という単語は、辞書によると「操り人形師」、「〈比喩的〉裏で糸を引く人、黒幕」という意味のようで、うまいネーミングだなー、と感心しました。

そして少し楽しんだ後に、ヘッドレスモードで実行して頂きました(笑)。

(7)その他

Puppeteer は、Chrome を細かく操作できるし、動作速度も比較的早いと思うので、情報収集ツールとしては非常に強力だと感じました。

今回はサイトのページから情報を収集することが主目的でしたが、Puppeteer の利用用途はいろいろあります。

Puppeteer のサイトにもいろいろ記載されていますが、分かりやすい用途としては、サイトのスナップショットを取ったり PDF 保存もできます。

個人的に興味を持ったのはテストの自動化です。ただ、製品レベルのテストに利用する場合は、クロスブラウザ対応などを考えると、他のツールも検討してみたほうがよいのかも。。。

コメント

このブログの人気の投稿

Google Document AIで画像から表形式データを抽出する(Vision API OCRとの違い)

VirtualBoxのスナップショット機能

オントロジーエディタ Protégé を使ってみる