URLを指定したらスクリーンショットを返すnode + puppeteer構成
PHPのアプリケーションで、特定URLのスクリーンショットを取得して反映する機能にPHP PhantomJSを使いました。目的は達成できたのですが、phantomJSはすでに開発が終了しているためCSS対応が現在でも厳しめに感じました。
例えばflexは一応対応しているみたいなのですが、cssのprefix次第で反映されないくらいにはシビア。将来を考えるとpuppeteerかseleniumでの対応が必要になるかと思いサンプルを作ってみました。
node.js + puppeteer
chromeのみの対応になりますがパワフルな機能を持つpuppeteerを使用して、URLを渡したらスクリーンショットを返すものを試しに作ってみました。puppeteer公式サンプルが非常に親切なのでほぼそのままでいけます。
FROM node:8.14-alpine
COPY ./app .
RUN apk update && apk upgrade && \
echo @edge http://nl.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \
echo @edge http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories && \
apk add --no-cache \
chromium@edge \
nss@edge
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
RUN yarn add express
RUN yarn add [email protected]
dockefileと同階層にappフォルダを作成、フォルダ内でnpm initしてpackage.jsonを作成。index.jsにexpressの設定を書いています。index.jsの中身は下記のリンク先のものを使い、puppeteer公式の説明通りにexecutablePahtを追加。 puppeteer
const express = require('express');
const puppeteer = require('puppeteer');
const app = express()
app.use(async (req, res) => {
const url = req.query.url;
if (!url) {
return res.send('Please provide URL as GET parameter, for example: <a href="/?url=https://example.com">?url=https://example.com</a>');
}
const browser = await puppeteer.launch({
args: ['--no-sandbox'],
executablePath: '/usr/bin/chromium-browser'
});
const page = await browser.newPage();
await page.goto(url);
const imageBuffer = await page.screenshot();
browser.close();
res.set('Content-Type', 'image/png');
res.send(imageBuffer);
});
const server = app.listen(process.env.PORT || 8080, err => {
if (err) return console.error(err);
const port = server.address().port;
console.info(`App listening on port ${port}`);
});
作成完了したらビルドして起動してみます。
docker build -t express-test .
docker container run -it -p 3600:8080 --rm --name text-app express-test /bin/ash
コンテナ内で
node index.js
テストURL http://localhost:3600?url=https://www.google.co.jp
テストURLにブラウザアクセスしてスクリーンショットが帰ってきたら成功です。
puppeteerはE2Eテストやスクレイピングだけでなく、ダイナミック レンダリングでも注目度が上がっていくでしょうから積極的に使って慣れ親しんで行きたいです