今更感はあるのですが、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を直列で順番にスクレイピングしていく、というサンプルを作っていきます。
先に全体のコードを置いておきます。
const puppeteer = require('puppeteer');
const path = require('path');
const fs = require('fs');
// スクレイピング対象のURL
const urls = ['https://qiita.com/', 'https://developer.mozilla.org/en-US/'];
// スクレイピング
const crawl = async (url: string) => {
// ファイル名用の現在日付作成
const now = (() => {
const d = new Date();
return `${d.getFullYear()}_${(d.getMonth()+1)}_${d.getDate()}_${d.getHours()}-${d.getMinutes()}-${d.getSeconds()}`;
})();
// ブラウザー開く
const browser = await puppeteer.launch({
headless: false,
slowMo: 50,
defaultViewport: {
width: 1280,
height: 800
}
});
// 新規タブ
const page = await browser.newPage();
// URLへアクセス
await page.goto(url);
// ScreenShot保存
const imgPath = path.join('./ss', `${now}.png`);
await page.screenshot({
path: imgPath,
fullPage: true,
});
// ドキュメントの情報を取得
const metaData = await page.evaluate(() => {
return {
'title': document.querySelector('title')?.textContent,
'description': (<HTMLMetaElement>document.querySelector('meta[name="description"]'))?.content,
'h1': document.querySelector('h1')?.textContent,
};
});
// セッション終了
await browser.close();
return {
img: imgPath,
...metaData
}
};
// 対象URL分スクレイピング処理を実行する
const handleCrawler = async () => {
const r = [];
for (let v of urls) {
r.push(await crawl(v));
}
console.log(r);
};
(async () => {
// スクリーンショット保存用のディレクトリがない場合
if (!fs.existsSync('ss')) {
// ScreenShot保存ディレクトリ作成後、実行
fs.mkdir('ss', () => {
handleCrawler();
});
}
// 保存用ディレクトリが既存の場合、そのまま実行
else {
handleCrawler();
}
})();
何をやっているか
まずは、Puppeteerを使って、Chromeを起動します。
// 〜略〜
// ブラウザー開く
const browser = await puppeteer.launch({
headless: false,
slowMo: 50,
defaultViewport: {
width: 1280,
height: 800
}
});
今回、実際にChromeが起動しているところを確認したいので、headless
にfalseを指定して、Chromeがnon-headlessで起動するように指定しています。また、slowMo
を指定することで、指定されたミリ秒数分、操作を遅延させています。
その後、タブを開いて対象URLに遷移し、スクリーンショットを保存します。
// 〜略〜
// ScreenShot保存
const imgPath = path.join('./ss', `${now}.png`);
await page.screenshot({
path: imgPath, // ここでスクリーンショットを保存するローカルのパスを指定
fullPage: true,
// type: 'jpeg',
// quality: 0
});
オプションにローカルのパスを指定すると、そこにスクリーンショットが保存されます。
qualityオプションを渡すことで画像の解像度を指定することができます。
試しに使ってみたところ、ページ全体のスクリーンショットが1.3MBほどあったページも、quality: 0
を指定すると88KBほどになりました。サーバーの容量に制限があるときなどには使えるかも知れません。
続いて、ドキュメントの情報を取得しています。
// ドキュメントの情報を取得
const metaData = await page.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スクレイピングを実装できました。
扱いやすいライブラリにめちゃくちゃ感謝です。(つづりが難しい...