BETA

Cloue Run + Puppeteer + Cloud Schedulebr でサーバーレスなクロールを定期実行する

投稿日:2019-11-23
最終更新:2019-11-23

つくるもの

  • 手軽に定期実行するクローラを作りたかった
  • 題材として,あすけん のスコア(健康度)をPixelaに転記する処理を1日1回実行する自動化システムをつくる

転記の結果

つまづきメモ

AWS LambdaをやめてGCP Cloud Runを選択

  • まず最初に,ローカルでPuppeteerを用いてaskenをcrawl → pixelaへ記録する機能を開発
  • これをベースにPuppeterr on Lambdaでサーバーレスな定期クロールを実現するつもりだった
  • が,Page.$$eval()の返り値が通常のPuppeteerとLambda用のPuppeteerとで違った…
    • 通常のPuppeteer: Array<Element>が返ってきた
    • Lambda用のPuppeteer: Array<ElementHandle>が返ってきた
  • このギャップを埋める修正が面倒なので,ローカルで動いているコード(=普通のPuppeteer)をサーバーレスで動かせばいいのでは? → Cloud Runがある!
    • Cloud Run: リクエストに応じてコンテナを起動・処理するサーバーレスなGCPのサービス

Puppeteerのevalなどで渡す関数内処理のロギング

  • PuppeteerのPage.$$eval()などの引数で渡す関数内でconsole.log()でログを取りたかったが,そのまま書いてもスコープが変わるためコンソール出力されない
  • これに対し,consleイベントをdispatchすることができたため,Pageオブジェクトに対して下記処理を実施し,Page.$$eval()内のログ出力に成功
const browser = await puppeteer.launch();  
const page = await browser.newPage();  
page.on('console', consoleMessageObject => function (consoleMessageObject) {  
    if (consoleMessageObject.type() !== 'warning') {  
        console.debug(consoleMessageObject.text())  
    }  
});  

await page.goto('https://google.com');  
const result = await page.evaluate(() => {  
    console.log('Browser scope.');  
    return 'Normal scope.';  
});  
console.log(result)  

その他細かいところ

  • DockerfileでAZを指定しないと日付がUTCで処理される
    • Cloud SchedulerがJST 11/23 1:00に起動しても,UTC 11/22 16:00で処理が進む
  • TypeScriptアプリをDockerに乗せるときの作法を知らない…
    • とりあえず今回はローカルでトランスパイルして,生成されたindex.jsだけをDockerにのせた

開発環境

GCPアカウント/プロジェクトは作成済みとする.

$ gcloud version  
Google Cloud SDK 271.0.0  
beta 2019.05.17  
bq 2.0.50  
core 2019.11.08  
gsutil 4.46  

$ yarn --version  
1.19.1  

$ tsc --version  
Version 3.6.4  

開発詳細

1. Puppeteer on Cloud Runの動作確認

Expressでリクエストを受けてPuppeteerを実行する設定・プログラムを準備

  • package.json
{  
  "name": "asken2pixela-cloudrun",  
  "version": "1.0.0",  
  "description": "asken2pixela on GCP Cloud Run",  
  "main": "index.js",  
  "scripts": {  
    "start": "tsc -p . && node .",  
    "build": "tsc -p .",  
    "server": "node index.js"  
  },  
  "author": "",  
  "license": "MIT",  
  "dependencies": {  
    "express": "^4.17.1",  
    "puppeteer": "^2.0.0"  
  },  
  "devDependencies": {  
    "@types/express": "^4.17.2",  
    "@types/puppeteer": "^1.20.2",  
    "typescript": "^3.7.2"  
  }  
}  
  • index.ts
import puppeteer from 'puppeteer'  
import express from "express";   

const app = express();  

const askenUrl = 'http://asken.jp/login'; // put your own target  

app.get('/', async (req, res) => {  
  console.log('Request received');  

  const browser = await puppeteer.launch();  
  const page = await browser.newPage();  
  await page.goto(askenUrl);  
  const title = await page.title();  
  await page.close();  

  // response message with web page title  
  res.send(`Access to ${title}`);  
});  

const port = process.env.PORT || 8080;  
app.listen(port, () => {  
  console.log('Listening on port', port);  
});  
  • Dockerfile
# Use the official lightweight Node.js 10 image.  
# https://hub.docker.com/_/node  
FROM node:10-slim  

# Create and change to the app directory.  
WORKDIR /usr/src/app  

# Copy application dependency manifests to the container image.  
COPY package.json yarn.lock ./  

# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)  
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \  
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \  
    && apt-get update \  
    && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \  
      --no-install-recommends \  
    && rm -rf /var/lib/apt/lists/*  

# Install puppeteer so it's available in the container.  
RUN yarn add puppeteer \  
    # Add user so we don't need --no-sandbox.  
    # same layer as npm install to keep re-chowned files from using up several hundred MBs more space  
    && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \  
    && mkdir -p /home/pptruser/Downloads \  
    && chown -R pptruser:pptruser /home/pptruser \  
    && chown -R pptruser:pptruser ./node_modules  

# Set environment variables  
ENV TZ Asia/Tokyo  
ENV PORT 8080  

# Install production dependencies.  
RUN yarn install --production  

# Copy local code to the container image.  
COPY index.js ./  
RUN chown -R pptruser:pptruser ./*  

# Run everything after as non-privileged user.  
USER pptruser  

# Run the web service on container startup.  
EXPOSE 8080  
CMD [ "yarn", "run", "server" ]  

用意したものをデプロイ

$ yarn install  

# build TypeScript code  
$ yarn build  

# push Dockerfile to GCP  
$ gcloud builds submit --tag gcr.io/<your-projectr-id>/<your-image-name>  

# create Cloud Run (Puppeteerを動かすため,メモリサイズを512Mに設定)  
$ cloud beta run deploy <your-cloudrun-name> --image gcr.io//<your-projectr-id>/<your-image-name> --platform managed --region asia-northeast1 --allow-unauthenticated --memory 512Mi  
  • 最後にCloud Runのエンドポイントが表示されるのでメモしておく
    • 当該URLにブラウザアクセス or curlで動作確認可能

2. Cloud Schedulerの作成・動作確認

毎日AM1:00に上記Cloud Runを実行するCloud Schedulerジョブを作成しテスト実行

# create Cloud Scheduler job  
$ gcloud scheduler jobs create http <your-job-name> --schedule "00 01 * * *" --time-zone "Asia/Tokyo" --http-method=GET --uri="<your-cloudrun-endpoint>" --headers=Content-Type=application/json  

# test run  
$ gcloud scheduler jobs run <your-job-name>  
  • その後Cloud Run, Cloud Schedulerのログを確認し動作確認

3. Puppeteer処理の実装

利用パッケージ追加

$ yarn add axios  

メイン処理

長いので下記参照 (puppeterでcrawlして取得した結果をaxiosでpostしてるだけ)
asken2pixela/index.ts GitHub

Dockerfileに環境変数設定を追加

...  
# Set environment variables  
ENV TZ Asia/Tokyo  
ENV PORT 8080  
ENV ASKEN_URL http://asken.jp/login  
ENV ASKEN_EMAIL <your-login-email-address  
ENV ASKEN_PASS <your-login-password>  
ENV PIXELA_USER <your-pixela-user-id  
ENV PIXELA__GRAPH <your-pixela-graph-id  
ENV PIXELA_TOKEN <your-pixela-token>  
...  

再デプロイ

# build & push Dockerfile to GCP  
$ yarn build && gcloud builds submit --tag gcr.io/<your-projectr-id>/<your-image-name>  

# create Cloud Run (Puppeteerを動かすため,メモリサイズを512Mに設定)  
$ cloud beta run deploy <your-cloudrun-name> --image gcr.io//<your-projectr-id>/<your-image-name> --platform managed --region asia-northeast1 --allow-unauthenticated --memory 512Mi  

完成!

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@jagijagijag1の技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう