JavaScript
Node.js
GoogleAppsScript
cloudfunctions
puppeteer

Cloud Functions with Puppeteer + Google Apps Script でスクレイピングサーバーをサクッと作る

2018/08/16 に Google Cloud Functions が Puppeteer をサポートしました :tada:
Introducing headless Chrome support in Cloud Functions and App Engine

Puppeteer が使えるようになったので、ヘッドレス Chrome を利用したスクレイピングが Cloud Functions でおこなえるようになりました。 Node v8 にも対応したので async/await も使えますよ!

しかし Cloud Functions の単体利用は使い勝手がちょっと悪いです。
それを補うために Google Apps Script(GAS)と併用します。
GAS を利用することでスプレッドシートに書き込んでデータの保存場所としたり、メールを送ったりなどが簡単に行なえます。

この2つを使う最大のメリットは、環境構築が不要で、Web ブラウザだけでスクレイピングサーバーが作れるところです :v:

本記事では例として、Qiita の人気記事を取得してスプレッドシートに書き出すというのを実際に作って解説していきます。

流れとしては次のようになります。

  1. (GAS) Cloud Functions を HTTP でキック
  2. (Cloud Functions) Puppeteer を利用し Web サイト をスクレイピング
  3. (Cloud Functions) スクレイピングの結果を JSON で返却
  4. (GAS) JSON を整形してスプレッドシートに格納

image.png

では早速作っていきましょう!

スプレッドシートを作成

スプレッドシートを作成し、GAS のスクリプトエディタを開きます。
image.png

GCP プロジェクトに移動

プロジェクト名を設定し、 Cloud Platform プロジェクトを開きます。
image.png

image.png

GAS に紐付いた GCP プロジェクトの画面が表示されます
image.png

Cloud Functions の画面に移動

左上のメニューから Cloud Functions の画面を開きます。
image.png

Billing 設定

Cloud Functions を使うには課金設定が必要です。
無料枠を超える分を使うとお金がかかってくるので注意しましょう。
※ ちょっとしたスクレイピングならかかりません。1
image.png

筆者は課金設定が済んでいるので、Enable を押すだけで終わりですが、はじめての人は請求アカウントの作成が必要です。
請求アカウントを作ったら、 Billing Alert を設定しましょう。
Billing Alertを設定して、クラウド死を防ごう! - Qiita

Cloud Functions を作成

Enable API をクリックし、Cloud Functions を作成しましょう
image.png

image.png

Qiita の人気記事を取得する Functions を作成

次のように入力します。

種類 設定値
Name GetTrends
Memory allocated 1GB
Trigger HTTP
Runtime Node.js 8(Beta)

image.png

つづいて、次の値を入力します。

種類 設定値
Functions to Execute getTrends
index.js 下のコード参照
package.json 下のコード参照

index.js に次のコードを入力します。

index.js
const puppeteer = require('puppeteer');
let page;

async function getBrowserPage() {
  // Launch headless Chrome. Turn off sandbox so Chrome can run under root.
  const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
  return browser.newPage();
}

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

  if (!page) {
    page = await getBrowserPage();
  }

  await page.goto('https://qiita.com');

  const result = await page.evaluate(getTrendsJson);

  res.set('Content-Type', 'application/json');
  res.send(result);
};

function getTrendsJson() {
  return [...document.querySelectorAll('.tr-Item_body')].map(article => {
    const a = article.querySelector('a');
    const href = a.href;
    const title = a.textContent;

    const author = article.querySelector('.tr-Item_author').textContent;
    const time = article.querySelector('time').textContent;
    const like = article.querySelector('.tr-Item_likeCount').textContent;
    console.log(href, title, author, time, like);  

    return {href, title, author, time, like};
  });
}

image.png

package.json に次のコードを入力します。
※ puppeteer を追加しています

package.json
{
  "name": "sample-http",
  "version": "0.0.1",
  "dependencies": {
    "puppeteer": "^1.6.2"
  }
}

image.png

最後に Create を押して作成完了です。
image.png

Cloud Functions の動作確認

作成が終わったら緑のチェックマークが付きます。数分かかるので待ちましょう。
チェックマークがついたら Function 名をクリックします。
image.png

Trigger タブを選択し、URLをクリックします。
image.png

ヘッドレス Chrome の起動が遅いのか 20秒程度 かかります。
※お手軽ですが、ガッツリ動かすのには向いていないかも。

スクレイピングが行われて JSON が返却されていることを確認しましょう。
image.png

GAS を作成

つづいて GAS を作成します。
先ほど作成した Cloud Functions を HTTP でキックしてスプレッドシートに書き出すコードを入力します。
※ URL は適宜自分で作成したものに書き換えてください

コード.gs
var URL = 'https://us-central1-project-id-6338972891167330241.cloudfunctions.net/GetTrends';

function writeTrends() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var response = UrlFetchApp.fetch(URL).getContentText();
  var articles = JSON.parse(response);
  var values = articles.map(function (article) {
    return Object.keys(article).map(function (key) {
      return article[key];
    });
  });

  var range = sheet.getRange(1, 1, values.length, values[0].length);
  range.setValues(values);  
}

入力したら実行して見ましょう。

※途中で次のような画面が出てきたら、詳細を押したあとに出てくるいリンクから承認ができます。
image.png

image.png

実行が完了したらスプレッドシートを開き、スクレイピング結果が格納されていることを確認しましょう。
image.png

有効活用への道

GAS は時間単位などで定期実行すること可能です。
また、メールの送信、Googleカレンダーへの登録なども簡単に行なえます。
外部の API も叩けるので Slack への通知なんかも出来ちゃいます。

ぜひスクレイピングを有効活用してみてください。

例として、筆者は RSS が提供されていない Web サイトを Puppeteer で定期巡回して、新しい投稿があったら(DOMに変更があったら)Slackに通知するようにしてたりします。
※ 前回取得した DOM をスプレッドシートに退避しておいて最新の DOM と比較しています。

あとがき

スクレイピングは用法・用量を守って正しくお使いください


  1. 200,000 GHz-seconds of compute time がまっさきに無料枠使うと思うので 200,000 / 1.4 / 60 / 60 = 40時間/月 ??