AWS LambdaによるスクレイピングでQiita Organizationの最新記事を取得しSlackに通知するBotを作った


Organizationページの「最新の投稿」をAWS Lambda(node.js)でスクレイピングし、新しい投稿の記事のタイトルとURLをSlackに投稿するBotを作ってみました

コードはここに置いてます(大分汚いです)
https://github.com/uji/qiita-organization-scraping/blob/master/app.js

AWS Lambdaの処理の流れ

  1. 「最新の投稿」にある記事をスクレイピングで取得
  2. AWS S3にあるtxtファイルを確認し、Slack未投稿の記事の有無を確認
  3. 未投稿の記事をSlackに投稿
  4. 最新記事のタイトルをtxtファイルでAWS S3に保存

投稿済みの記事を永続化する必要があるのですが、DB使うのは大げさな気がしたのでS3にtxtで保存します

スクレイピング

AWS Lambda 用のchromium chrome-aws-lambda を使ってスクレイピングしました
Google Cloud Functionsでも使えるっぽいです
puppeteerと使用感はほぼ同じです

.of-ItemLink_header-title classを取ってくると「最新の投稿」の要素を取ってこれます

const chromium = require("chrome-aws-lambda");
exports.handler = async (event, context) => {
  let browser = null;
  let elems = [];

  try {
    browser = await chromium.puppeteer.launch({
      args: chromium.args,
      defaultViewport: chromium.defaultViewport,
      executablePath: await chromium.executablePath,
      headless: chromium.headless,
    });

    let page = await browser.newPage();

    await page.goto("http://qiita.com/organizations/" + process.env.ORGANIZATION_NAME);
    const selector = ".of-ItemLink_header-title";
    elems = await page.$$eval(selector, es => es.map(e => [e.textContent, e.href]));
  } catch (error) {
    return context.fail(error);
  } finally {
    if (browser !== null) {
      await browser.close();
    }
  }

  return context.succeed(elems);
};

Slackに通知する

@slack/boltまたは@slack/web-apiでSlack APIを叩き、取得した記事をSlackに投げます
Slack Botでメッセージを送るには、singning secretbot tokenchannel id が必要になります

boltの場合

const { App, ExpressReceiver } = require("@slack/bolt");

const receiver = new ExpressReceiver({
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  receiver: receiver
});

app.client.chat.postMessage({
  channel: process.env.SLACK_CHANNEL,
  text: "message",
  token: process.env.SLACK_BOT_TOKEN
});

S3のテキストファイル読み込み、上書き

投稿済みの記事のタイトルを永続化します
aws-sdk を使うと結構簡単にS3にアクセスできます

読み込み
const AWS = require("aws-sdk");
const s3 = new AWS.S3({ region: "ap-northeast-1" });
let s3Params = {
  Bucket: process.env.BACKET_NAME,
  Key: process.env.FILE_NAME
};

s3.getObject(s3Params, (err, data) => {
  if (err) return context.fail(err);
  else latest = data.Body.toString();
});
上書き
const AWS = require("aws-sdk");
const s3 = new AWS.S3({ region: "ap-northeast-1" });
let s3Params = {
  Bucket: process.env.BACKET_NAME,
  Key: process.env.FILE_NAME,
  Body: "タイトル"
};

s3.putObject(s3Params, (err) => {
  if (err) return context.fail(err);
});

AWS Lambdaの設定

ランタイムにnode.jsを指定してLambdaを作成します

まあまあ時間のかかる処理なので、基本設定からメモリ、タイムアウト時間を変更しておきます
メモリは512MB、タイムアウト時間は1分にしました

node_modulesはzipにまとめてLayerに登録します
AWS Lambda レイヤー

AWS Lambdaで動かす

コードをLambdaに書いてテストを実行してみます

Slackに通知されました

AWS Cloud Watch Eventsで定期実行

AWS LambdaのトリガーにAWS Cloud Watch Eventsを追加して定期実行されるようにします

毎朝9時に実行される設定にしました

まとめ

今回はスクレイピングで取得しましたがQiita APIを使ってユーザーごとの最新記事をとる方法もあります
そっちの方が良さそう