今更感はあるのですが、Puppeteerでスクレイピングしてみました。
環境
Node.js 12.18.3
Puppeteer 5.5.0
TypeScript 4.1.3
やったこと
環境の準備
まず、適当なディレクトリを初期化して、必要なモジュールをインストールしていきます。
$ npm init -y
$ npm i puppeteer
$ npm i -D typescript ts-node @types/node @types/puppeteer
$ npx tsc --init
今回、TypeScriptはts-nodeを使って実行します。
そのため、package.json
に下記のスクリプトを追記します。
〜略〜"scripts":{"start":"ts-node src/index.ts"},〜略〜
実際のコード
配列で用意しておいたURLを直列で順番にスクレイピングしていく、というサンプルを作っていきます。
先に全体のコードを置いておきます。
constpuppeteer=require('puppeteer');constpath=require('path');constfs=require('fs');// スクレイピング対象のURLconsturls=['https://qiita.com/','https://developer.mozilla.org/en-US/'];// スクレイピングconstcrawl=async(url:string)=>{// ファイル名用の現在日付作成constnow=(()=>{constd=newDate();return`${d.getFullYear()}_${(d.getMonth()+1)}_${d.getDate()}_${d.getHours()}-${d.getMinutes()}-${d.getSeconds()}`;})();// ブラウザー開くconstbrowser=awaitpuppeteer.launch({headless:false,slowMo:50,defaultViewport:{width:1280,height:800}});// 新規タブconstpage=awaitbrowser.newPage();// URLへアクセスawaitpage.goto(url);// ScreenShot保存constimgPath=path.join('./ss',`${now}.png`);awaitpage.screenshot({path:imgPath,fullPage:true,});// ドキュメントの情報を取得constmetaData=awaitpage.evaluate(()=>{return{'title':document.querySelector('title')?.textContent,'description':(<HTMLMetaElement>document.querySelector('meta[name="description"]'))?.content,'h1':document.querySelector('h1')?.textContent,};});// セッション終了awaitbrowser.close();return{img:imgPath,...metaData}};// 対象URL分スクレイピング処理を実行するconsthandleCrawler=async()=>{constr=[];for(letvofurls){r.push(awaitcrawl(v));}console.log(r);};(async()=>{// スクリーンショット保存用のディレクトリがない場合if(!fs.existsSync('ss')){// ScreenShot保存ディレクトリ作成後、実行fs.mkdir('ss',()=>{handleCrawler();});}// 保存用ディレクトリが既存の場合、そのまま実行else{handleCrawler();}})();
何をやっているか
まずは、Puppeteerを使って、Chromeを起動します。
// 〜略〜// ブラウザー開くconstbrowser=awaitpuppeteer.launch({headless:false,slowMo:50,defaultViewport:{width:1280,height:800}});
今回、実際にChromeが起動しているところを確認したいので、headless
にfalseを指定して、Chromeがnon-headlessで起動するように指定しています。また、slowMo
を指定することで、指定されたミリ秒数分、操作を遅延させています。
その後、タブを開いて対象URLに遷移し、スクリーンショットを保存します。
// 〜略〜// ScreenShot保存constimgPath=path.join('./ss',`${now}.png`);awaitpage.screenshot({path:imgPath,// ここでスクリーンショットを保存するローカルのパスを指定fullPage:true,// type: 'jpeg',// quality: 0});
オプションにローカルのパスを指定すると、そこにスクリーンショットが保存されます。
qualityオプションを渡すことで画像の解像度を指定することができます。
試しに使ってみたところ、ページ全体のスクリーンショットが1.3MBほどあったページも、quality: 0
を指定すると88KBほどになりました。サーバーの容量に制限があるときなどには使えるかも知れません。
続いて、ドキュメントの情報を取得しています。
// ドキュメントの情報を取得constmetaData=awaitpage.evaluate(()=>{return{'title':document.querySelector('title')?.textContent,'description':(<HTMLMetaElement>document.querySelector('meta[name="description"]'))?.content,'h1':document.querySelector('h1')?.textContent,};});
今回ここで少しハマったのですが、素直に
'description':document.querySelector('meta[name="description"]')?.content,
としてしまうと
Property 'content' does not exist on type 'Element'.
と怒られてしまいました。
HTMLElement
のインターフェースにはcontent
というプロパティが無いことが原因なようで、HTMLMetaElement
にキャストしてあげる必要があったようです。
大変助かりました。>https://qiita.com/vsanna/items/201d4af29086a01b6b12
実行
実際に上記のソースコードを実行してみます。npm start
で実行されます。
$ npm start
実行したターミナルの標準出力に、スクレイピングの結果が出力されました。
[
{
img: 'ss/2020_12_16_16-56-39.png',
title: 'Qiita',
description: 'Qiitaは、プログラマのための技術情報共有サービスです。 プログラミングに関するTips、ノウハウ、メモを簡単に記録 & 公開することができます。',
h1: 'How developers code is here.'
},
{
img: 'ss/2020_12_16_16-56-44.png',
title: 'MDN Web Docs',
description: 'The MDN Web Docs site provides information about Open Web technologies including HTML, CSS, and APIs for both Web sites and progressive web apps. It also has some developer-oriented documentation for Mozilla products, such as Firefox Developer Tools.',
h1: 'Resources for developers, by developers.'
}
]
/ss
配下にはスクリーンショットが保存されているのが確認できました。
$ ls -la ss
total 3512
drwxr-xr-x 4 xxxx staff 128 Dec 16 16:56 .
drwxr-xr-x 9 xxxx staff 288 Dec 16 16:56 ..
-rw-r--r-- 1 xxxx staff 1340232 Dec 16 16:56 2020_12_16_16-56-39.png
-rw-r--r-- 1 xxxx staff 453752 Dec 16 16:56 2020_12_16_16-56-44.png
最後に
Puppeteer自体の使い方もとてもシンプルで、思っていたよりも簡単にWebスクレイピングを実装できました。
扱いやすいライブラリにめちゃくちゃ感謝です。(つづりが難しい...