【Nuxt】Puppeteerでスクレイピング〜データ取得から表示まで(serverMiddleware)
今回は、Puppeteer を用いてスクレイピングし、取得したデータを表示するサンプルアプリケーションを構築したので紹介する。
Puppeteerとは
- プログラムからAPIでブラウザ(Chromium(Chrome))を制御できる Node.js のライブラリ
- ヘッドレス(GUIなし)で制御でき、高速
- GoogleのChrome DevTools Teamが開発
- 利用用途:E2Eテスト、スクレイピング
アプリケーション構成
今回は、Dockerに開発環境を構築し、Nuxt.js上でアプリケーションを完結させる構成とした。
Dockerの構成
参考: Running Puppeteer in Docker
なお、実装したサンプルコードは GitHub に公開している。 Dockerから構築したため、pullして、Dokcerを立ち上げるのみで確認ができるようにした。
本記事では、ポイントだけを抜粋して説明するため、実際にコード全体を確認する場合は GitHub を参照ください。
NuxtでのAPI(サーバーサイドの処理)を実装する方法
基本的には、2つの方法があるようだ。
(上記で、APIと述べているのは、フロント側から何らかの形で呼び出して、データを得ることを想定しているためである。)
1. Expressのミドルウェアとしてnuxtを使う
構成図としては、以下のようになる。
nuxtで create-nuxt-app
でサーバーサイドのフレームワークを Express
を選択した場合は、こちらの方式となる。
const express = require('express') const { Nuxt, Builder } = require('nuxt') const app = express() <- 省略 ->
こちらの場合は、APIを組み込む場合は、通常のExpress同様に、 app.use('/api', function (req, res, next) { (api処理) })
を実装することで実現できる。
2. NuxtのサーバーミドルウェアとしてExpressを使う
Nuxt は内部で connect のインスタンスを作ります。 それはミドルウェアをスタックに登録したり、 外部サーバーを必要とせず に API などのルートを増やす事を可能にしてくれます。
Connectとは何かと思い、調べると以下の通りでした。
Connect is an extensible HTTP server framework for node using "plugins" known as middleware.
これは便利かもしれない。APIなどのルートを増やして、データの登録などができる!
json形式で返却もできれば、言うことなし!
と思いきや、 res.end()
しか使えませんでした。
requestに対するライブラリー(body-parserなど)は充実しているので、フロント側からデータを受け渡して、サーバー側でミドルウェア的に処理することはできるが、APIみたいに返却値を返すことはConnectでは厳しいみたいでした。
そこで、 Express
を導入することに変更せざるを得ませんでした。
でも、そもそもサーバーミドルウェアってそもそもなんだ?ってことで、Nuxt.jsのライフサイクルを確認。
このライフサイクルのミドルウェアで実行されるのか思いきや、サーバーミドルウェア vs ミドルウェア!に以下のように記載されていた。
クライアントサイドや SSR の Vue で各ルートの前に呼び出されている ルーティングのミドルウェア と混同しないでください。 serverMiddleware は vue-server-renderer の 前に サーバー側で実行され、API リクエストの処理やアセットの処理などのサーバー固有のタスクとして使用できます。
ここの認識を間違えると大変なので、公式を確認することはやはり大切だと実感。
どちらにおいても、Expressを使用するので、正直大差はない気がするが、APIにどれだけの機能を実装するのか、またどんな機能を実装するかで選択すべきかと思う。 - APIをガッツリ開発する場合は、 「1. Expressのミドルウェアとしてnuxtを使う」 - サーバー側でデータ取得を行い、フロントから実行することがない場合、 「2. NuxtのサーバーミドルウェアとしてExpressを使う」 こんなイメージではないだろうか。 今回は、サーバー側でスクレイピングしたデータをサーバーでレンダリングする際に取得するだけなので、「2. NuxtのサーバーミドルウェアとしてExpressを使う」を使うこととした。
export default { serverMiddleware: [ '~/server', ], <- 省略 -> }
const express = require('express'); const app = express(); const scraping = require('./scraping') app.get('/scraping', async(req, res) => { const data = await scraping.train() res.json(data) }) module.exports = { path: '/api', handler: app }
スクレイピング
基本的には、 APIドキュメント に従って実行することができる。 何でもできるように、様々なメソッドがあるが、スクレイピングのみであれば以下があれば十分かと思われる。
- page.goto(url[, options]):任意のページにアクセス
- page.$eval(selector, pageFunction[, ...args]):1つの要素オブジェクト(Element)を取得する
- page.$$eval(selector, pageFunction[, ...args]):複数の要素オブジェクト(Element)を取得する
- page.close([options]):ページを閉じる
取得した要素に対して、以下のメソッドを用いる
- textContent:テキスト内容を取得する
- querySelector:最初の要素を取得する
- querySelectorAll:全ての要素を取得する
取得したい要素のセレクタを確認する場合は、Chromeのディベロッパーツールで確認する。
今回は試験的に、Yahooの運行情報のページをスクレイピングしてみたので、参考にしてみてください。
画面表示に関して
Nuxtのサーバーミドルウェアとして実行するので、 asyncData
を使い、データを取得する。
async asyncData({ $axios }) { const data = await $axios.$get('/api/scraping') return { data } }, data: () => ({ data: [] })
データの更新ボタンも設けているが、画面をリロードさせているだけである。
以上、今回はPuppeteerでスクレイピングを行い、Nuxt.js上で表示するまでを実装した。 スクレイピングをやる際に、Puppeteerは非常に便利だと感じた。
では、ステキな開発ライフをー