組み込みProgrammerのチラシの裏

Chromium

| Comments

今までWebスクレイピングする時には、Go言語 + agouti + selenium + chrome driver + chromeの組み合わせでプログラムしていた。 しかし、この場合はchromeとchrome driverのバージョンを揃えないと動かない。 chromeは自動でバージョンアップしてしまうため、chrome driverとバージョンを揃えるのが面倒になっていた。

そこで、これからはGo言語 x chromedp x chromiumでWebスクレイピングしていく。

chromium

AWS Lambdaは容量制限があるため、直接chromiumを格納することはできない。 そのため、AWS Lambda Layerにchromiumを格納し、WebスクレイピングするGoプログラムをAWS Lambdaに格納する。

  • chromium : AWS Lambda Layer
  • Goプログラム : AWS Lambda

さて、ここでAWS Lambda Layerに格納できるchromiumを用意する。 いくつかchromium起動まで失敗してきたので、その変遷を記載しておく。

  1. GitHub > adieuadieu内のバイナリを使用: chromiumのバージョンが60.0.3095.0のため古くてchromedpと通信できない
  2. GitHub > adieuadieuを手動ビルド: MAC失敗。自宅のLinuxは古くて失敗。
  3. GitHub > shelfio内のバイナリを使用: python用のもので、通常のchromiumが格納されてなかったため失敗。
  4. GitHub > pahudで取得: 成功。バージョンは86.0.4240.111。
1
make build

の結果のlayer.zipを使用する。 pahud/lambda-layer-headless-chromiumのbuild.shを確認したところ、結局adieuadieuのdockerからコピーしていることがわかる。

1
2
3
4
docker run -dt --rm --name headless-chromium adieuadieu/headless-chromium-for-aws-lambda:stable
docker cp headless-chromium:/bin/headless-chromium ./layer/headless-chromium/
docker stop headless-chromium
chmod +x ./layer/headless-chromium/headless-chromium

AWS Lambda Layer

layer.zipをAWS Lambda Layerに登録する。 なお、layer.zipが50MBを超えているため、zipファイルを直接登録することはできない。 必ず、AWS S3経由で登録すること。

layer.zipを解凍すると以下のディレクトリ構成となっている。

1
2
3
4
5
6
headless-chromium
├── .fontconfig
│   └── 16df3a2d55dda9e8d20c253f295c1151-le64.cache-3
├── .fonts
│   └── NotoSansCJKtc-Regular.otf
└── headless-chromium

AWS > Lambdaレイヤーの作成と共有

に書かれている通り、Lambda Layerは /opt に展開される。 しかし、 /opt はPATHに入っていないため、実行ファイルを展開しても検索することはできない。 /opt/bin はPATHに入っているため、予めzipファイル生成時にbinディレクトリ内に実行ファイルを格納すれば良い。 しかし、今回使用するlayer.zipはPATHが通っていないディレクトリにheadless-chromiumがあるため、 Go言語側でheadless-chromiumのパスを指定する。

登録時に以下の設定も行う

  • 「互換性のあるランタイム」 に Go1.xを追加

AWS Lambda

  • AWS Lambda Layerの登録
  • 「設定 > 一般設定 > メモリ」 を 512MBに変更
  • 「設定 > 一般設定 > タイムアウト」 を 1分に変更

AWS Lambda Layer上のchromiumを起動するため、 chromedpのExecAllocatorOptionは以下を設定した。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
opts = append(opts,
  chromedp.DisableGPU,
  chromedp.NoSandbox,
  chromedp.Headless,
  chromedp.ExecPath("/opt/headless-chromium/headless-chromium"),
  chromedp.Flag("no-zygote", true),
  chromedp.Flag("single-process", true),
  chromedp.Flag("homedir", "/tmp"),
  chromedp.Flag("data-path", "/tmp/data-path"),
  chromedp.Flag("disk-cache-dir", "/tmp/cache-dir"),
  // https://github.com/adieuadieu/serverless-chrome/issues/108
  chromedp.Flag("disable-setuid-sandbox", true),
  chromedp.Flag("enable-webgl", true),
  chromedp.Flag("use-gl", "osmesa"),

その他問題

施策

実際動かすことができるようになるまでに2週間ほどかかった。 chromiumバイナリの取得・chromiumの起動オプションの設定にほとんどの時間がかかっている。 問題解決するため、素早くテストするために良かった点をまとめておく。

  • AWS Lambdaをコマンドラインからdeployした ビルド&デプロイ時間を大きく削減できた。多くのchromiumの起動オプションを試していたため有用だった。
1
$ aws lambda update-function-code --no-cli-pager --function-name mindra --zip-file fileb://hoge.zip
  • ローカル環境&AWS Lambda環境の双方をビルドできるようにGo言語のbuild tagを活用 ほぼ同じコードでローカル環境とAWS Lambda環境を比較することで問題の切り分けに貢献した
  • chromiumのログを出力するようにGoプログラムを変更 chromiumがエラーで起動しない時など、chromium側のログがないと問題を切り分けることはできなかった
1
2
3
4
5
6
7
opts := chromedp.DefaultExecAllocatorOptions[:]
opts = append(opts,
  chromedp.CombinedOutput(os.Stdout),
  chromedp.Flag("vervose", true),
  chromedp.Flag("enable-logging", true),
  chromedp.Flag("log-level", "0"),
)

その他問題:No usable sandbox

AWS Lambdaのログ

1
2
[WARNING:resource_bundle.cc(353)] locale_file_path.empty() for locale
[FATAL:zygote_host_impl_linux.cc(107)] No usable sandbox! Update your kernel or see

Qiita > CircleCI で puppeteer のテストを実行する を見ると、chromium起動時に以下の引数を足す必要があるとのこと。

1
--no-sandbox --disable-setuid-sandbox

Go言語のコードに落とすと以下となる。

1
2
3
4
5
6
44         if sandbox == false {
45                 opts = append(opts,
46                         chromedp.Flag("no-sandbox", true),
47                         chromedp.Flag("disable-setuid-sandbox", true),
48                 )
49         }

その他問題:websocket url timeout reached

AWS Lambdaのログ

1
2
3
4
{
  "errorMessage": "scrape: chromedp.Run: websocket url timeout reached",
  "errorType": "errorString"
}

恐らく、DevTool protocolのソケットを開けなかったと思われる。

Comments