ヘッドレスブラウザを使用したWebスクレイピング:Puppeteerチュートリアル
公開: 2022-03-11この記事では、ヘッドレスブラウザを使用する従来とは異なる方法でWebスクレイピング(Web自動化)を実行するのがいかに簡単であるかを説明します。
ヘッドレスブラウザとは何ですか?なぜそれが必要なのですか?
ここ数年、Webは裸のHTMLとCSSで構築された単純なWebサイトから進化してきました。 今では、AngularやReactなどのフレームワークで構築されることが多い美しいUIを備えたインタラクティブなWebアプリがはるかに増えています。 言い換えれば、今日のJavaScriptは、Webサイトでやり取りするほとんどすべてのものを含め、Webを支配しています。
私たちの目的では、JavaScriptはクライアント側の言語です。 サーバーは、HTML応答に挿入されたJavaScriptファイルまたはスクリプトを返し、ブラウザーはそれを処理します。 さて、これは、ある種のWebスクレイピングまたはWeb自動化を行っている場合に問題になります。これは、多くの場合、表示またはスクレイピングしたいコンテンツが実際にはJavaScriptコードによってレンダリングされ、生のHTML応答からアクセスできないためです。サーバーが提供するもの。
上で述べたように、ブラウザはJavaScriptを処理して美しいWebページをレンダリングする方法を知っています。 では、この機能をスクレイピングのニーズに活用し、プログラムでブラウザーを制御する方法があればどうでしょうか。 それこそが、ヘッドレスブラウザの自動化のステップです。
ヘッドレス? すみません? はい、これはグラフィカルユーザーインターフェイス(GUI)がないことを意味します。 マウスやタッチデバイスなど、通常の方法で視覚要素を操作する代わりに、コマンドラインインターフェイス(CLI)を使用してユースケースを自動化します。
ヘッドレスクロームとパペッティア
Zombie.jsやSeleniumを使用したヘッドレスFirefoxなど、ヘッドレスブラウジングに使用できる多くのWebスクレイピングツールがあります。 しかし、今日は、2018年の初めにリリースされた比較的新しいプレーヤーであるPuppeteerを介してヘッドレスChromeを探索します。編集者注:別の新しいプレーヤーであるIntoliのリモートブラウザについて言及する価値がありますが、それは別の対象となる必要があります記事。
Puppeteerとは正確には何ですか? これは、ヘッドレスChromeまたはChromiumを制御したり、DevToolsプロトコルと対話したりするための高レベルのAPIを提供するNode.jsライブラリです。 ChromeDevToolsチームと素晴らしいオープンソースコミュニティによって維持されています。
十分に話しましょう。コードに飛び込んで、Puppeteerのヘッドレスブラウジングを使用してWebスクレイピングを自動化する方法の世界を探りましょう。
環境の準備
まず、マシンにNode.js8+をインストールする必要があります。 ここにインストールできます。または、私のようなCLIが好きで、Ubuntuで作業したい場合は、次のコマンドに従ってください。
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - sudo apt-get install -y nodejs
また、システムで使用できる場合とできない場合があるいくつかのパッケージも必要になります。 安全のために、次のものをインストールしてみてください。
sudo apt-get install -yq --no-install-recommends libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 libnss3
ヘッドレスクロームとパペッティアをセットアップする
ライブラリでの動作が保証されている安定した最新のChromiumバージョンも含まれているため、 npm
を使用してPuppeteerをインストールすることをお勧めします。
プロジェクトのルートディレクトリで次のコマンドを実行します。
npm i puppeteer --save
注:PuppeteerはChromiumをバックグラウンドでダウンロードしてインストールする必要があるため、これにはしばらく時間がかかる場合があります。
さて、これですべての設定と構成が完了したので、楽しみを始めましょう!
自動WebスクレイピングにPuppeteerAPIを使用する
基本的な例からPuppeteerチュートリアルを始めましょう。 ヘッドレスブラウザで選択したWebサイトのスクリーンショットを撮るスクリプトを作成します。
プロジェクトディレクトリにscreenshot.js
という名前の新しいファイルを作成し、お気に入りのコードエディタで開きます。
まず、スクリプトにPuppeteerライブラリをインポートしましょう。
const puppeteer = require('puppeteer');
次に、コマンドライン引数からURLを取得しましょう。
const url = process.argv[2]; if (!url) { throw "Please provide a URL as the first argument"; }
ここで、PuppeteerはPromiseベースのライブラリであることに注意する必要があります。Puppeteerは、内部でヘッドレスChromeインスタンスへの非同期呼び出しを実行します。 async / awaitを使用して、コードをクリーンに保ちましょう。 そのためには、最初にasync
関数を定義し、そこにすべてのPuppeteerコードを配置する必要があります。
async function run () { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); await page.screenshot({path: 'screenshot.png'}); browser.close(); } run();
全体として、最終的なコードは次のようになります。
const puppeteer = require('puppeteer'); const url = process.argv[2]; if (!url) { throw "Please provide URL as a first argument"; } async function run () { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); await page.screenshot({path: 'screenshot.png'}); browser.close(); } run();
プロジェクトのルートディレクトリで次のコマンドを実行することで実行できます。
node screenshot.js https://github.com
ちょっと待って、ブーム! ヘッドレスブラウザがscreenshot.png
という名前のファイルを作成したところ、GitHubホームページがレンダリングされているのがわかります。 すばらしい、Chromeウェブスクレイパーが機能しています。
ちょっと立ち止まって、上記のrun()
関数で何が起こるかを調べてみましょう。
まず、新しいヘッドレスブラウザインスタンスを起動し、次に新しいページ(タブ)を開いて、コマンドライン引数で指定されたURLに移動します。 最後に、スクリーンショットを撮るためにPuppeteerの組み込みメソッドを使用し、保存するパスを指定するだけで済みます。 また、自動化が完了したら、ヘッドレスブラウザを必ず閉じる必要があります。
基本を説明したので、もう少し複雑なことに移りましょう。
2番目のパペッティアスクレイピングの例
Puppeteerチュートリアルの次の部分では、HackerNewsから最新の記事を削除したいとします。
ycombinator-scraper.js
という名前の新しいファイルを作成し、次のコードスニペットに貼り付けます。
const puppeteer = require('puppeteer'); function run () { return new Promise(async (resolve, reject) => { try { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto("https://news.ycombinator.com/"); let urls = await page.evaluate(() => { let results = []; let items = document.querySelectorAll('a.storylink'); items.forEach((item) => { results.push({ url: item.getAttribute('href'), text: item.innerText, }); }); return results; }) browser.close(); return resolve(urls); } catch (e) { return reject(e); } }) } run().then(console.log).catch(console.error);
さて、前の例と比較して、ここではもう少し進んでいます。
最初に気付くかもしれないのは、 run()
関数がpromiseを返すようになったため、 async
プレフィックスがpromise関数の定義に移動したことです。
また、すべてのコードをtry-catchブロックでラップして、promiseが拒否される原因となるエラーを処理できるようにしました。
そして最後に、 evaluate()
と呼ばれるPuppeteerの組み込みメソッドを使用しています。 このメソッドを使用すると、DevToolsコンソールで実行しているかのようにカスタムJavaScriptコードを実行できます。 その関数から返されたものはすべて、promiseによって解決されます。 この方法は、情報を取得したり、カスタムアクションを実行したりする場合に非常に便利です。
evaluate()
メソッドに渡されるコードは、オブジェクトの配列を構築する非常に基本的なJavaScriptであり、各オブジェクトには、https://news.ycombinator.com/に表示されるストーリーのURLを表すurl
フィールドとtext
フィールドがあります。
スクリプトの出力は次のようになります(ただし、元々30エントリあります)。
[ { url: 'https://www.nature.com/articles/d41586-018-05469-3', text: 'Bias detectives: the researchers striving to make algorithms fair' }, { url: 'https://mino-games.workable.com/jobs/415887', text: 'Mino Games Is Hiring Programmers in Montreal' }, { url: 'http://srobb.net/pf.html', text: 'A Beginner\'s Guide to Firewalling with pf' }, // ... { url: 'https://tools.ietf.org/html/rfc8439', text: 'ChaCha20 and Poly1305 for IETF Protocols' } ]
かなりきちんとしていると思います!

さて、先に進みましょう。 返品されたアイテムは30個だけでしたが、他にも多くのアイテムがあります。それらは他のページにあります。 結果の次のページをロードするには、「詳細」ボタンをクリックする必要があります。
スクリプトを少し変更して、ページネーションのサポートを追加しましょう。
const puppeteer = require('puppeteer'); function run (pagesToScrape) { return new Promise(async (resolve, reject) => { try { if (!pagesToScrape) { pagesToScrape = 1; } const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto("https://news.ycombinator.com/"); let currentPage = 1; let urls = []; while (currentPage <= pagesToScrape) { let newUrls = await page.evaluate(() => { let results = []; let items = document.querySelectorAll('a.storylink'); items.forEach((item) => { results.push({ url: item.getAttribute('href'), text: item.innerText, }); }); return results; }); urls = urls.concat(newUrls); if (currentPage < pagesToScrape) { await Promise.all([ await page.click('a.morelink'), await page.waitForSelector('a.storylink') ]) } currentPage++; } browser.close(); return resolve(urls); } catch (e) { return reject(e); } }) } run(5).then(console.log).catch(console.error);
ここで行ったことを確認しましょう。
- メインの
run()
関数にpagesToScrape
という単一の引数を追加しました。 これを使用して、スクリプトがスクレイプするページ数を制限します。 - 現在調べている結果のページ数を表す
currentPage
という名前の新しい変数がもう1つあります。 最初は1
に設定されています。 また、evaluate()
関数をwhile
ループでラップし、currentPage
がpagesToScrape
以下である限り実行を継続できるようにしました。 - 新しいページに移動し、ページが読み込まれるのを待ってから
while
ループを再開するためのブロックを追加しました。
page.click()
メソッドを使用して、ヘッドレスブラウザで[その他]ボタンをクリックするようにしました。 また、 waitForSelector()
メソッドを使用して、ページのコンテンツが読み込まれるまでロジックが一時停止されていることを確認しました。
これらは両方とも、すぐに使用できる高レベルのPuppeteerAPIメソッドです。
Puppeteerでスクレイピング中に発生する可能性のある問題の1つは、ページが読み込まれるのを待つことです。 Hacker Newsの構造は比較的単純で、ページの読み込みが完了するのを待つのはかなり簡単でした。 より複雑なユースケースの場合、Puppeteerにはさまざまな組み込み機能が用意されており、GitHubのAPIドキュメントで調べることができます。
これはすべてかなりクールですが、Puppeteerチュートリアルではまだ最適化について説明していません。 Puppeteerをより高速に実行する方法を見てみましょう。
Puppeteerスクリプトの最適化
一般的な考え方は、ヘッドレスブラウザに余分な作業をさせないことです。 これには、画像の読み込み、CSSルールの適用、XHRリクエストの起動などが含まれる場合があります。
他のツールと同様に、Puppeteerの最適化は正確なユースケースに依存するため、これらのアイデアの一部はプロジェクトに適していない可能性があることに注意してください。 たとえば、最初の例で画像の読み込みを回避した場合、スクリーンショットが希望どおりに表示されなかった可能性があります。
とにかく、これらの最適化は、最初のリクエストでアセットをキャッシュするか、ウェブサイトによって開始されたHTTPリクエストを完全にキャンセルすることで実現できます。
最初にキャッシングがどのように機能するかを見てみましょう。
新しいヘッドレスブラウザインスタンスを起動すると、Puppeteerはそのプロファイル用の一時ディレクトリを作成することに注意してください。 ブラウザを閉じると削除され、新しいインスタンスを起動すると使用できなくなります。したがって、保存されているすべての画像、CSS、Cookie、およびその他のオブジェクトにアクセスできなくなります。
Puppeteerに、Cookieやキャッシュなどのデータを保存するためのカスタムパスを使用するように強制できます。これらのデータは、期限切れになるか手動で削除されるまで、再実行するたびに再利用されます。
const browser = await puppeteer.launch({ userDataDir: './data', });
最初のリクエストで多くのCSSと画像がデータディレクトリにキャッシュされ、Chromeはそれらを何度もダウンロードする必要がないため、これによりパフォーマンスが大幅に向上するはずです。
ただし、これらのアセットは、ページをレンダリングするときに引き続き使用されます。 Y Combinatorのニュース記事のスクレイピングのニーズでは、画像を含むビジュアルについて心配する必要はありません。 ベアHTML出力のみを考慮しているので、すべてのリクエストをブロックしてみましょう。
幸いなことに、Puppeteerは、カスタムフックのサポートが付属しているため、この場合は非常に便利です。 リクエストごとにインターセプターを提供し、本当に必要のないインターセプターをキャンセルすることができます。
インターセプターは次のように定義できます。
await page.setRequestInterception(true); page.on('request', (request) => { if (request.resourceType() === 'document') { request.continue(); } else { request.abort(); } });
ご覧のとおり、開始されるリクエストを完全に制御できます。 resourceType
に基づいて特定のリクエストを許可または中止するカスタムロジックを作成できます。 また、 request.url
などの他の多くのデータにもアクセスできるため、必要に応じて特定のURLのみをブロックできます。
上記の例では、リソースタイプが"document"
のリクエストのみがフィルターを通過できるようにしています。つまり、元のHTML応答以外のすべての画像、CSS、およびその他すべてをブロックします。
最終的なコードは次のとおりです。
const puppeteer = require('puppeteer'); function run (pagesToScrape) { return new Promise(async (resolve, reject) => { try { if (!pagesToScrape) { pagesToScrape = 1; } const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.setRequestInterception(true); page.on('request', (request) => { if (request.resourceType() === 'document') { request.continue(); } else { request.abort(); } }); await page.goto("https://news.ycombinator.com/"); let currentPage = 1; let urls = []; while (currentPage <= pagesToScrape) { await page.waitForSelector('a.storylink'); let newUrls = await page.evaluate(() => { let results = []; let items = document.querySelectorAll('a.storylink'); items.forEach((item) => { results.push({ url: item.getAttribute('href'), text: item.innerText, }); }); return results; }); urls = urls.concat(newUrls); if (currentPage < pagesToScrape) { await Promise.all([ await page.waitForSelector('a.morelink'), await page.click('a.morelink'), await page.waitForSelector('a.storylink') ]) } currentPage++; } browser.close(); return resolve(urls); } catch (e) { return reject(e); } }) } run(5).then(console.log).catch(console.error);
レート制限で安全を確保
ヘッドレスブラウザは非常に強力なツールです。 彼らはほぼすべての種類のWeb自動化タスクを実行でき、Puppeteerはこれをさらに簡単にします。 すべての可能性にもかかわらず、システムを悪用しないように、Webサイトの利用規約を遵守する必要があります。
この側面はアーキテクチャに関連しているため、このPuppeteerチュートリアルではこれについて詳しく説明しません。 とはいえ、Puppeteerスクリプトの速度を落とす最も基本的な方法は、それにスリープコマンドを追加することです。
js await page.waitFor(5000);
このステートメントにより、スクリプトは5秒間(5000ミリ秒)スリープ状態になります。 これは、 browser.close()
の前のどこにでも置くことができます。
サードパーティのサービスの使用を制限するのと同じように、Puppeteerの使用を制御するためのより堅牢な方法は他にもたくさんあります。 1つの例は、限られた数のワーカーでキューシステムを構築することです。 Puppeteerを使用するたびに、新しいタスクをキューにプッシュしますが、その中のタスクで作業できるワーカーの数は限られています。 これは、サードパーティのAPIレート制限を処理する場合のかなり一般的な方法であり、PuppeteerWebデータスクレイピングにも適用できます。
動きの速いウェブにおけるパペッティアの場所
このPuppeteerチュートリアルでは、Webスクレイピングツールとしての基本的な機能を示しました。 ただし、ヘッドレスブラウザのテスト、PDFの生成、パフォーマンスの監視など、さまざまなユースケースがあります。
Webテクノロジーは急速に進歩しています。 一部のWebサイトはJavaScriptレンダリングに非常に依存しているため、単純なHTTPリクエストを実行してそれらをスクレイプしたり、何らかの自動化を実行したりすることはほぼ不可能になっています。 幸いなことに、Puppeteerのようなプロジェクトとその背後にある素晴らしいチームのおかげで、ヘッドレスブラウザーは、自動化のすべてのニーズを処理するためにますますアクセスしやすくなっています。