14
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NIJIBOXAdvent Calendar 2020

Day 17

Puppeteerでスクレイピング

Last updated at Posted at 2020-12-16

今更感はあるのですが、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に下記のスクリプトを追記します。

package.json
〜略〜
"scripts": {
  "start": "ts-node src/index.ts"
},
〜略〜

実際のコード

配列で用意しておいたURLを直列で順番にスクレイピングしていく、というサンプルを作っていきます。
先に全体のコードを置いておきます。

src/index.ts
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を起動します。

src/index.ts
// 〜略〜
// ブラウザー開く
const browser = await puppeteer.launch({
    headless: false,
    slowMo: 50,
    defaultViewport: {
        width: 1280,
        height: 800
    }
});

今回、実際にChromeが起動しているところを確認したいので、headlessにfalseを指定して、Chromeがnon-headlessで起動するように指定しています。また、slowMoを指定することで、指定されたミリ秒数分、操作を遅延させています。

その後、タブを開いて対象URLに遷移し、スクリーンショットを保存します。

src/index.ts
// 〜略〜
// 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ほどになりました。サーバーの容量に制限があるときなどには使えるかも知れません。

続いて、ドキュメントの情報を取得しています。

src/index.ts
// ドキュメントの情報を取得
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、ノウハウ、メモを簡単に記録 &amp; 公開することができます。',
    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スクレイピングを実装できました。
扱いやすいライブラリにめちゃくちゃ感謝です。(つづりが難しい...

14
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?