もうやりたくねー!!です!!
解説専用ではありませんが作っていたコードはGitHubにあります!
khsk/togetter-to-text: TogetterをPuppeteerでスクレイピングする
どんなものを作ったか
フロントをNuxt.jsで構築し、APIでPuppeteerを起動しスクレイピング結果を返して表示する。
動作環境
- Docker mhart/alpine-node:base-10
- Nuxt 2.2.0
- Puppeteer 1.9.0
- express 4.16.4
NuxtのserverMiddlewareでPuppeteer動かない問題
詳細はこちら
Nuxt.jsからPuppeteerが実行できない TypeError: Cannot read property 'on' of undefined - Qiita
NuxtにAPIサーバーを追加する場合、本来ならNuxt単体でHTTPリクエストを処理できるのですが、Puppeteerが動作しなかったため、expressにNuxtを追加する形になりました。
しんどい!
nowでPuppeteer動かない問題
Google's Puppeteer doesn't work in now.sh · Issue #861 · zeit/now-cli
こちらは知識としては既知だったのですが、nowでNPM形式ではPuppeteerが動きません。
せっかくPuppeteerがchromiumを同梱してくれるようになったのに…
動かすにはDockerfileをデプロイする必要があります。
nowでPuppeteer入りDockerがデプロイできない問題
Puppeteerを動かすDockerfileについては
zeit/now-examples: Examples of Now deployments you can use
の
now-examples/misc-screenshot at master · zeit/now-examples
が役立ちます。
というか私がDocker及びサーバー構築に無知なのでこれをそのまま動かそうとしました。
が、Puppeteerの他にNuxtなどを含めた結果、ファイルサイズが100MBを超え、サイズ制限に引っかかってデプロイできませんでした。
ローカルのnode_modulesのデプロイに気をつけよう
参照元の構成ではメインのコードが./src
ディレクトリに集約されています。
私のコードはルート直下にすべてあることと、
マルチステージビルドへの理解が薄いため、
FROM mhart/alpine-node:10 as build
WORKDIR /usr/src
#COPY package.json package-lock.json /usr/src/
COPY . /usr/src/
.
をまるごとコピーしていました。
これではせっかくビルド環境で
RUN PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 yarn install --production
しているのに、ローカルで動かすdevDependenciesやchromiumが含まれてしまってファイルサイズが膨張してしまっていました。
(cpにexceptできれば簡単でしたが)
そのため、ローカルファイルを全部コピーした後、
RUN rm -rf ./node_modules
RUN PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 yarn install --production
とローカルのnode_modules
を削除してから改めてビルドしました。
…が、それでも結果は180MB → 110MBとデプロイには至りませんでした…
(つい最近の更新では.nowignore
が追加されたようです)
Dockerデプロイをv1で動かそう
サイズ制限の問題は
Image size limit of 100 Mb · Issue #1523 · zeit/now-cli
などで話されています。
ここでは
now.json
を
"features": {
"cloud": "v1"
},
と、普段v2
で動くモードをv1
に変更すれば動くよと言われています。
v2
とv1
の違いは正直よくわかりません…
- now.sh (zeit) の docker デプロイ - Qiita
- ZEIT – Serverless Docker Beta
- Deploying Docker Apps - ZEIT Documentation
が、確かにDockerのデプロイ自体には成功しました。
デプロイに成功したのにアクセスがタイムアウトする
Docker内でexpressが正常に動いているかがわかりませんでした。
しかしログ的には起動自体は完了していそうです。
しかししかし、動かすJavaScriptを
now-examples/node-express at master · zeit/now-examples
の簡単なexpressにしても同様…
デプロイ先に3000
番ポートでアクセスしてもダメでした。
ここで、個人利用のDockerってたしかポートフォワーディングしないとダメだったような記憶があったことを思い出しました。
また、npmのデプロイでもnowは暗黙的にスクリプトで開けたポートをデプロイ先の80
番ポートとして扱ってくれていることを思い出しました。
そこで、Dockerfileに
EXPOSE 3000
を追加することで80
番ポートでアクセスするとレスポンスが返ってくることが確認できました。
なぜそうなるかと言うと(なぜEXPOSE 3000
がなかったかというと)
Automatic port discovery. We no longer rely on the '
EXPOSE
' instruction. We automatically forward traffic to the port of the process started by 'CMD
'
v2
からは自動的に処理してくれるので、v1
で動かそうとすると手動設定が必要だったわけです。
ドキュメントをちゃんと読むことも大切ですが、過去にDockerを触ってなかったら非常に危なかった…
(でもEXPOSEしても-p
オプションを使わないとうまくいかないのでは?とすごく悩んではいました。今でもちゃんとした動きは把握していないデス)
レスポンス真っ暗問題
ちゃんとポートフォワーディングできるようになりましたが、レスポンスがなくてまっさら。
ただ、スクリプトを
now-examples/node-express at master · zeit/now-examples
に変えるとちゃんと表示されたので、expressの問題だろうと見当はつきました。
結論から言うと、expressでNuxtを動かすためにコピペしたコード、
【Node.js】express + Nuxt.jsでシンプルなテンプレートを作った - Qiita
の
const express = require("express")
const { Nuxt, Builder } = require("nuxt")
const app = express()
let config = require("./nuxt.config.js")
config.dev = !(process.env.NODE_ENV === "production")
const nuxt = new Nuxt(config)
if (config.dev) {
const builder = new Builder(nuxt)
builder.build()
}
が問題でした。
こちらもコピペしたDockerfileでは
FROM mhart/alpine-node:base-10
WORKDIR /usr/src
ENV NODE_ENV="production"
としているので、production環境になりNuxtのためのbuilder
が働いていませんでした。
expressでNuxtを動かす背景を理解していないために起きた問題ですね。
// if (config.dev) {
const builder = new Builder(nuxt)
builder.build()
// }
とすることで解決。
Puppeteer動かない問題
ここからはPuppeteerとの戦いになります…
launchできない
これはローカルのコードでは手元のchromiumを動かそうとしますが、Dockerではちゃんと用意したものを使わないといけないからです。
ここで
config.dev = !(process.env.NODE_ENV === "production")
NODE_ENV === "production"
が役立ち、デプロイ用の記述を使い分けられることに気づいたのが良かったです。
now-examples/setup.js at master · zeit/now-examples
をちゃんと確認して、
if (process.env.NODE_ENV === 'production') {
this.browser = await puppeteer.launch({
executablePath: '/usr/bin/chromium-browser',
args: ['--no-sandbox', '--headless', '--disable-gpu', '-—disable-dev-tools'],
dumpio: true,
devtools: false,
})
} else {
this.browser = await puppeteer.launch({ headless: true, })
}
productionではalpineのchromiumのパスをちゃんと指定しましょう。
.click()
に失敗する
if (await this.page.$(selector)) {
await this.page.click(selector)
}
ちゃんと要素の存在を確認しているのに、デプロイ先では
Node is either not visible or not an
(HTMLElement)
と出てきてクリックに失敗します。
不本意ですが、倣ってevaluate
でブラウザ内でクリックすることになりました。
const moreTweetId = '#more_tweet_btn'
if (await this.page.$(moreTweetId)) {
if (process.env.NODE_ENV === 'production') {
// now環境で.click()にError: Node is either not visible or not an HTMLElementがでるのでeval clickで回避する
await Promise.all([this.page.waitFor((moreTweetId) => !document.querySelector(moreTweetId), moreTweetId), this.page.evaluate((moreTweetId) => { document.querySelector(moreTweetId).click() }, moreTweetId),])
} else {
await Promise.all([this.page.waitFor((moreTweetId) => !document.querySelector(moreTweetId), moreTweetId), this.page.click(moreTweetId),])
}
}
デプロイ制限
nowはデプロイ無制限をうたっていますが、
logs prematurely rate limited · Issue #1169 · zeit/now-cli
何回も(それでも結構な回数)デプロイした結果、制限に引っかかってしまいました。
動かない焦りで雑なデプロイを繰り返していたので、逆に良かったかも…
その他 不定期失敗
yarn install
デプロイ時のyarn installで
error https://registry.yarnpkg.com/@nuxtjs/youch/-/youch-4.2.3.tgz: Extracting tar content of undefined failed, the file appears to be corrupt: "Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?"
が出るときがありました。時間を空けて解消。
CDN
Puppeteer動作時にpage.goto
が
net::ERR_NAME_RESOLUTION_FAILED
になることがありました。
デプロイし直すと直ったり直らなかったりします…