新しいことにはウェルカム

技術 | 電子工作 | ガジェット | ゲーム のメモ書き

Puppeteerのクローリングで、Tableタグの表のデータをCSV出力する方法

クローラーとしてPuppeteerを使っています。

クロールしていて、サイトのTableタグで作られた表のデータを取得したい時があります。

Pythonなら、htmlからTableタグ以下のみを抽出し、それからPandasを使ってDataFrameを生成することにより、エレガントにできちゃいます。

JavaScriptにも、TableタグのDOMから表データを作成し、それをCSV出力するライブラリはいくつかあります。

しかし、クロール先のサイトでライブラリを使うことはできないし、 クロール先のサイトページのDOMを、PuppeteerからNode.js側に直接持ってくることもできないので、 渋々自分でDOMをパースしていました。

でも、よくよく考えたら、Pythonのやり方同様、一旦サイトをhtmlで取得して、それを解析すればよかったんですね。

試しにやったらうまくいきました。

Wikipediaから、国別オリンピックメダル獲得数の表を、CSV出力してみます。

Puppeteerのクローリングで、Tableタグの表をCSV出力する方法

ページにある全てのTableタグをCSV出力するのは下記のようになります。

index.ts

import * as puppeteer from 'puppeteer';
import { JSDOM } from 'jsdom';
import * as XLSX from 'xlsx';
import * as fs from 'fs';

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

    const page = await browser.newPage();
    const url = `https://en.wikipedia.org/wiki/All-time_Olympic_Games_medal_table`;
    const res = await page.goto(url);

    const html = await res.buffer();

    const dom = new JSDOM(html);

    const tables = dom.window.document.querySelectorAll('table');

    for (let i = 0, len = tables.length; i < len; ++i) {
        const ws = XLSX.utils.table_to_sheet(tables[i]);
        fs.writeFileSync(
            `./tmp/${String(i).padStart(2, '0')}.csv`,
            XLSX.utils.sheet_to_csv(ws)
        );
    }

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

このページには16個のTabeleがありました。

Puppeteerのクローリングで、Tableタグの表をCSV出力する方法

一番ファイルサイズの大きい「01.csv」が、国別のオリンピックメダル数のTableでした。

Puppeteerのクローリングで、Tableタグの表をCSV出力する方法

解説

  • page.goto()のレスポンスが、ページのhtmlになります。
  • jsdomを使って、htmlからDOMを作成しています。
  • Document.querySelectorAll()で全てのTableタグのDOMを取得しています。
  • xlsxを使って、TableタグのDOMから、Excelのワークシートを作成しています。
  • xlsxを使って、ExcelのワークシートからCSVを出力しています。

応用

ページ全体を取り込むのではなく、取り出したいTableタグのみを抽出することも可能です。

クロール先のサイト内でXMLSerializer.serializeToString()を使ってTableタグ以下をhtmlにして、 それをNode.js側に持ってきて、Node.js側でDOMを再構築します。

import * as puppeteer from 'puppeteer';
import { JSDOM } from 'jsdom';
import * as XLSX from 'xlsx';
import * as fs from 'fs';

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

    const page = await browser.newPage();
    const url = `https://en.wikipedia.org/wiki/All-time_Olympic_Games_medal_table`;

    await page.goto(url);

    const html = await page.$eval('div.legend + table', (dom: any) => {
        return new XMLSerializer().serializeToString(dom);
    });

    const dom = new JSDOM(html);

    const table = dom.window.document.querySelector('table');

    const ws = XLSX.utils.table_to_sheet(table);
    fs.writeFileSync(
        `./tmp/medal_table.csv`,
        XLSX.utils.sheet_to_csv(ws)
    );

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

感想

これでDOMのパースのストレスから開放されました!

DOMはNode.js側にあるので、ライブラリを使って他にも色々できそうです。