Puppeteer + GCP Functionsでサーバレスなスクレイピング

サーバレスなScraperを運用したい人向け。


GCP FunctionsでPuppeteerが動くぞ!

2018年の8月から、GCPのGoogle App EngineとGoogle Functionsで Puppeteerサポートされました。

Puppeteerは、nodejsで動くHeadless Chrome(Chromium) Driverです。


準備

インストール

$ npm init

$ npm install -S puppeteer


コード

まず、エントリーポイントになるindex.jsはこんな感じ。


index.js

const scraper = require('./functions/scraper');

exports.scraper = scraper;

あとはfunctionsディレクトリの下にscraper.jsを作り、そこにコードを作成していきます。

今回はasyncを使いたいのでnode8系で書きます。


scraper.js

module.exports = async (req, res) => {

const USERNAME = process.env['USERNAME'];
const PASSWORD = process.env['PASSWORD'];
const LOGIN_URL = 'https://example.com';

// Chromiumを起動し、ページをつくる
const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const page = await browser.newPage();
page.setViewport({ width: 1280, height: 1024 });

// ログインページに移動し、ログインする
await page.goto(`${LOGIN_URL}/login`, { waitUntil: 'domcontentloaded', timeout: 20000 });
await page.type('input#email', USERNAME);
await page.type('input#password', PASSWORD);
await page.click('#login-btn-sumit');
await page.waitForNavigation();
await page.goto('`${LOGIN_URL}/users`', { waitUntil: 'domcontentloaded', timeout: 20000 });

// スクレイピングの一例
// user_info_fieldsクラスのtrタグをパースします
const elements = await page.$$('tr.user_info_fields');
const users = [];
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const userid= await element.$eval('th', e => e.innerText);
const username = await element.$eval('td.username span', e => e.innerText);
const email = await element.$eval('td.email', e => e.innerText);
users.push({
userid: userid,
username: username,
email: email
});
}
res.send(users);
}



環境変数

Functionsでは.env.yamlに記述しておけば、デプロイ時に環境変数に設定できます。


.env.yaml

USERNAME : 'me@example.com'

PASSWORD : 'passwd1234!'


デプロイ

GCP SDKとFunctionsエミュレータをインストールしておいてください。

Functionsエミュレータは次の通り。

$ npm install -g @google-cloud/functions-emulator

$ functions start

ローカルでの実行とデプロイは次の通り。

// ローカルデプロイ

$ functions deploy scraper --trigger-http"
// ローカル実行
$ functions call scraper

// 本番デプロイ
$ gcloud beta functions deploy scraper --trigger-http

Functions側をnode v8に設定しないとデプロイがコケます。

詳しいオプションは、こちらとかを見てください。


まとめ

お疲れ様でした。

Functions特有の癖もありますが、サーバレスでHeadless Chromiumを動かせるのはいろいろ応用が広がりそうです。FunctionsからGCSやCloud Store, BigQueryなどにも入れられるので、大規模な運用もできるかもしれません。


参考