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

# はじめに
あーあ、スクレイピングしたいな。

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

あまり人に迷惑をかけてはいけないと言われて育ったので、スクレイピングは自分のサイトにします。（ログイン不要だよ。ぜひ使ってみてね♪）
[toribure | ひとりでもチームでも使えるシンプルイズベストなブレストツール](https://toribure.herokuapp.com/)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254216/f72c8d79-2328-92bf-08cf-5e71695f1363.png)

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

# 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: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のトラブルシューティング](https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-puppeteer-in-docker)に詳細があります。ブラウザやフォントがコンテナ内に用意されていないとエラーになるってことです。

```yaml: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](https://ja.nuxtjs.org/api/configuration-servermiddleware/)を使っていきます。公式でも紹介されている[express-template](https://github.com/nuxt-community/express-template)を参考にserverMiddlewareをAPIとして通してスクレイピングしていきます。

```js:nuxt.config.js
export default {
  ...
  ,
  serverMiddleware: {
    '/api': '~/api'
  }
}
```
これで`/api`のアクセスを`~/api/index.js`に流します。のでファイル作ります。

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

```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.js`の`get_image()`メソッドを呼び出すようにしてます。

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

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

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

```vue: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>
```

**完成！！**

# 動作確認
![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254216/9321a5e8-cd9f-46a4-eb83-e8e290ff83a2.gif)

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

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

# 参考
- [【Nuxt】Puppeteerでスクレイピング〜データ取得から表示まで（serverMiddleware） - 7839](https://serip39.hatenablog.com/entry/2020/06/13/170000)
- [【手順解説】JavaSciptで簡単スクレイピング！ – Puppeteerでスクリーンショットを撮影する｜ProgLearn - プログラミング総合情報サイトプログラーン](https://blog.proglearn.com/2019/06/20/javascipt%E3%81%A7%E7%B0%A1%E5%8D%98%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%94%E3%83%B3%E3%82%B0%EF%BC%81-puppeteer%E3%81%A7%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7/)
- [Puppeteerでスクレイピング | grgr-dkrkのブログ](https://www.dkrk-blog.net/javascript/puppeteer01)
- [Docker + docker-compose + puppeteer でスクレイピングしてみた - Qiita](https://qiita.com/takayuki-miura0203/items/4fa4cdc9ef0c07a857a9)
- [Nuxt.jsでAxiosによるデータ取得 - Qiita](https://qiita.com/yoshi0518/items/0f3f766c5d17099ff1a9)