puppeteerを使ってJavascriptなサイトをクロールする

インストール

早速実践していきます
まずはインストール...

yarn add puppeteer
# or "npm i puppeteer"

インストールが完了すればnode_modulesフォルダの中に色々入ります

これでOKです

チュートリアル

githubのexampleをさらいます

// example.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({ path: 'example.png' });

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

実行

node example.js

指定したexample.comのページのスクショの保存ができました
なんてことでしょう! pythonで同様のことする場合、ブラウザやドライバの依存関係とpythonでの設定に詰まったりしますがpuppeteerを使えば簡単にできてしまいました

クロール

では実際にjavascriptなサイトのクローリングを行います

対象は弊社GRIの技術本を取得することにしました
一般公開しているのでよかったらどういうものがあるのか見てみてください(結構あります)
https://gri.jp/ai-info

f:id:gri-blog:20210721145623p:plain

このページの本棚をクリックすると裏でjavascriptが動いてます(あたりまえ)
ページは無限スクロールになっており、スクロール毎に新しい本が表示されるので単純なhtmlのリクエストだと全部の本の取得はできません

ここでpuppeteerの出番

まずはこのサイトに入ることにしましょう

const puppeteer = require('puppeteer');
const url = 'https://gri.jp/ai-info';

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto(url);
  await page.screenshot({ path: 'book.png' });

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

本棚のページは ul 要素、クラス名が library_items となっていますね

f:id:gri-blog:20210721145648p:plain

この要素をクリックして本棚ページに入るコードを書きました

各プロセスを見逃したくないのでデバッグモードで実行します
slowMoを1000とすることで、1つ1つの命令は1秒おきに実行されます

const puppeteer = require("puppeteer");
const url = 'https://gri.jp/ai-info';

(async () => {
    const browser = await puppeteer.launch({
    headless: false,
    devtools: true,
    slowMo: 1000
    });

    const page = await browser.newPage();
    await page.goto(url, { waitUntil: "domcontentloaded" });
    await page.click("ul[class=library_items]");

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

クリック後ページが別タブで開かれた後ブラウザを閉じているのが見えたと思います

次は、本が全て出てくるまでスクロールし、終わったら本のタイトルを取得するように実装

Divのidが front になっている要素が本棚、それぞれの本は子要素Divのクラス名が item-wrapper の構成なので、 子要素の数を毎スクロール時にカウント、差分がなくなった時点で終了し各要素のタイトルを取得する

結果はoutput.txtファイルに書き込むとしました

また、本棚ページにクリックで飛んでたのを直接ページを見にいくように変更しております

const puppeteer = require("puppeteer");
const fs = require('fs');

const url = 'https://gri.jp/ai-info';


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

    const page = await browser.newPage();
    await page.goto(url, { waitUntil: "domcontentloaded" });

    const bookUrl = await page.evaluate(() => 
        document.querySelector('.library_item > a').href
    );
    await page.goto(bookUrl, { waitUntil: "domcontentloaded" });

    await page.waitForTimeout(1000)

    var elemCount = 0;

    // スクロール部分
    while (true) {
        // 最後の本の要素をviewにもってくる
        await page.$eval('.item-wrapper:last-child', elem => { 
            elem.scrollIntoView();
        });

        await page.waitForTimeout(1000)

        // 再度各本の要素を取得
        var length = await page.$$eval('.item-wrapper', elems => { 
            return elems.length;
        });
    
        if (elemCount === length) {
            break;
        } else {
            elemCount = length;
        }
    };

    var resultElems = await page.evaluate(() => {
        const elems = Array.from(document.querySelectorAll('.item-area-img'));
        return elems.map(elem => elem.title);
    });
    
    fs.writeFileSync('./output.txt', resultElems.join('\n'));
    
    await browser.close();
})();

結果

f:id:gri-blog:20210721144305p:plain

全ての本のタイトルが取得できました

まとめ

今回はpuppeteerで簡単にjavascriptのサイトをクロールしてみました
データをparseしたり複雑な処理を必要としないのであればjsを採用しても良いかもしれませんね

出力結果を見てみると、 スケーラブル リアルタイム データ分析入門 という興味をそそる本を見つけたので、拝借してみます。クロールが役に立ちました

............無かった..

higashi kunimitsu