Nuxt on Dockerでpuppeteer使ってスクレイピングする。


はじめに

あーあ、スクレイピングしたいな。

ということで Nuxt on Docker でスクレイピングします。
node系だとpuppeteerというライブラリがスクレイピングするのにおすすめっぽかったので、NuxtのserverMiddlewareからサクッとスクレイピングします。

あまり人に迷惑をかけてはいけないと言われて育ったので、スクレイピングは自分のサイトにします。(ログイン不要だよ。ぜひ使ってみてね♪)
toribure | ひとりでもチームでも使えるシンプルイズベストなブレストツール

やや宣伝ですね。トップページにかわいい鳥(いらすとや)の画像があります。今回はこれをスクレイピングで取ってきて表示させようと思います。

Nuxt on Dockerの準備

このあたりは他にも記事がいっぱいあると思うのでさらーっと。
ちなみに環境は

$ docker -v
  Docker version 19.03.13-beta2, build ff3fbc9d55
$ docker-compose -v
  docker-compose version 1.26.2, build eefe0d31

でした。

Nuxtアプリを作る

$ docker run --rm -it -w /app -v `pwd`:/app node yarn create nuxt-app scraping
? Project name: scraping
? Programming language: JavaScript
? Package manager: Yarn
? UI framework: None
? Nuxt.js modules: Axios
? Linting tools: 
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Server (Node.js hosting)
? Development tools: 

axiosだけ後で使うので意識的に入れておきます。

ちなみに記事作成時点でnode:latestイメージのバージョンは14.9.0、create-nuxt-appのバージョンはv3.2.0で、nuxtは2.14.0が入りました。

Dockerfile, docker-compose.ymlを準備する

ここからは今できたばかりのscraping/ディレクトリが作業ディレクトリです。

$ cd scraping

Dockerfile
FROM node

ENV HOME=/app     \
    LANG=C.UTF-8  \
    TZ=Asia/Tokyo \
    HOST=0.0.0.0

WORKDIR ${HOME}

RUN apt-get update \
    && apt-get install -y wget gnupg \
    && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
      --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

COPY package.json ${HOME}
COPY yarn.lock ${HOME}
RUN yarn install

COPY . ${HOME}
EXPOSE 3000
CMD ["yarn", "run", "dev"]

RUN apt-get ...あたりはpuppeteerのトラブルシューティングに詳細があります。ブラウザやフォントがコンテナ内に用意されていないとエラーになるってことです。

docker-compose.yml
version: "3"

services:
  nuxt:
    build: .
    volumes:
      - .:/app
    ports:
      - 3000:3000

ここまで終わったら一度コンテナをビルドしておきます。

$ docker-compose build

スクレイピングアプリを作るぞ

puppeteer導入

yarnでいれちゃいます。

$ docker-compose run --rm nuxt yarn add puppeteer

APIを作る

serverMiddlewareを使っていきます。公式でも紹介されているexpress-templateを参考にserverMiddlewareをAPIとして通してスクレイピングしていきます。

nuxt.config.js
export default {
  ...
  ,
  serverMiddleware: {
    '/api': '~/api'
  }
}

これで/apiのアクセスを~/api/index.jsに流します。のでファイル作ります。

$ mkdir api
$ touch api/index.js api/scraping.js

ファイルを2つ作りましたがindex.jsが受け口になっていて実処理はscraping.jsにやらせようと思います。

api/index.js
const app = require('express')()
const scraping = require('./scraping')

app.get('/get_image', async(req, res) => {
  const image = await scraping.getImage()
  res.send(image)
})

module.exports = {
  path: '/api',
  handler: app
}

こちらは/api/get_imageにアクセスがあったらscraping.jsget_image()メソッドを呼び出すようにしてます。

api/scraping.js
const puppeteer = require('puppeteer')

async function getImage() {
  const browser = await puppeteer.launch({
    args: [
      '--no-sandbox',
      '--disable-dev-shm-usage'
    ]
  })
  const page = await browser.newPage()
  await page.goto("https://toribure.herokuapp.com/")
  const image = await page.evaluate(() => {
    return document.getElementsByTagName("main")[0].getElementsByTagName("img")[0].src
  })
  return image
}

module.exports = {
  getImage
}

ほぼpuppeteer公式のREADMEに則ってます。
page.evaluateを使うことで要素を取得したり操作したりできるんですね。
今回のスクレイピング先(https://toribure.herokuapp.com/)のHTML構造をDeveloper toolなどで見ると分かる通り、全体で1つしかないmain要素配下に1つしかないimg要素がターゲットとしてる鳥さんの画像です。(汚い構造してますがご愛嬌です)
そこまでわかれば通常のjsと同じように要素を取得するだけです。

ここまででAPIサイドはコーディング終了です。

フロントはさっくりと

もう疲れてきたのでフロントはボタン押したら画像表示されるくらいで。

pages/index.vue
<template>
  <div>
    <button @click="showBird">Scraping!!</button>
    <br>
    <img v-if="src" :src="src">
  </div>
</template>

<script>
export default {
  data() {
    return {
      src: ""
    }
  },
  methods: {
    async showBird() {
      this.src = await this.$axios.$get("/api/get_image")
    }
  }
}
</script>

完成!!

動作確認

かわいい鳥が出てきました♪

さいごに

ここまでできれば、あとはDOM操作の世界なので、対象のページの構造を理解してjsを書けば何でもスクレイピングできますね。
スクレイピングは禁止しているサイトもあるのでその点は注意しながらいろんなアイデアを実現できればいいですね!

参考