AWS Lambdaでpuppeteerを使う

TL;DR: chrome-aws-lambda 使えばok。

次男の通っている保育園が共有サイトでちょくちょく写真を共有してくれてありがたいのだが、いかんせんそのサイトが使いにくすぎるので、スクレイピングして写真をとってきてGoogle PhotosなりFlickrなりに自動でアップしてくれるソリューションがほしくなった。

こういうのは僕たちPlagger世代にはお手のものだぜ、ってんで軽く考えて作業を開始したが、このサイトがjsベースのサイトで昔ながらの方法でスクレイピングするとなるとAPIの解析が必要になりとてもめんどくさいことがわかった…。[1] PhantomJS のようなheadlessブラウザでやるのが楽そうだが、これは開発が停止しているし、いまだとなにでやるのがいいのかな〜とtweetしたら、headless chrome とか puppeteer をおすすめされたので、とりあえずそれをみてみる。

PuppeteerはChrome/ChromiumのDevTools Protocolを使ってそれらをコントロールできるNode.jsのモジュールで、Google謹製だしなかなか良さそう。

問題のサイトがポップアップメニューからメニューをたどる必要があったりとか、ダウンロードリンクの飛び先がそのままだとpuppeteerから扱えなかったりとか、いろいろ問題はあったが、なんとか目的のスクリプトは作成できた。

あとはこのスクリプトを定期実行すればよいが、それによさそうなサーバをいまは契約していないのでlambdaとかでうごかせたりしないのかな? と思ったらchrome-aws-lambdaというまさにそのためのプロジェクトがあって、これをつかえばlambdaでうごかすためのchromiumバイナリをかんたんにバンドルできてかなり素晴しい。

ただこのchrome-aws-lambda、TypeScriptの型情報がないのだけが惜しい。まぁ、ほぼpuppeteerのラッパーなので、使う部分だけ型情報を書くのはサクッとできる。以下のようなのを書いた:

追記@2019-11-22

いま見たら普通に型情報ついてました…。↓のショボいのはまったく必要なし! すばらしい。

declare module "chrome-aws-lambda" {
  import { LaunchOptions, Browser } from "puppeteer";

  interface ChromeAwsLambda {
    puppeteer: {
      launch: (options?: LaunchOptions) => Promise<Browser>;
    };

    args: string[];
    defaultViewport: any;
    executablePath: Promise<string>;
    headless: boolean;
  }

  const chromium: ChromeAwsLambda;
  export default chromium;
}

あとはもうREADME通りにやるだけで完璧に動作した。

さいごにPlaggerでこれをやるならどうしたらいいのだろう?と思ったが、PerlにもChrome::DevToolsProtocolはあってさらにそれをつかうWWW::Mechanize::Chromeもあってすでに簡単にできそうな感じだった。さすが!