Puppeteerを使って、スクレイピング箇所をブラウザUIで指定し、取得・比較するツールをVue.jsで作りました

majirouです。

[前回](https://qiita.com/majirou/items/e4efa1ea9708bf79d98f)、「Puppeteerでページの必要な部分だけをブラウザ上でクリック選択して登録、定期的なスクレイピングするツールを作っていきます（１）」といった記事を書いて、その後順次記事を投稿する予定…がお座なりになったので、再度見直しと作り直しを行いました。

* ソースは github にアップしました。 https://github.com/majirou/trouper
* ツール名は「操り人形を使う劇団員」という感じでつけました

## インストール

```bash
git clone https://github.com/majirou/trouper.git
npm install
npm run build
```

合わせて以下のインストールも必要となります。

* Mongodb
* ImageMagick

## 起動

```bash
npm run serve
```

## 機能と実装

### スクレイピング対象の読込

![qiita01.png](https://qiita-image-store.s3.amazonaws.com/0/136889/9f1059fd-e936-4858-ab5e-c55f6e0ba5e0.png)

1. 一覧画面より「＋」ボタンをクリックすると、上図のURLを入力する画面となります。
2. URLを入力し、「取得」をクリックすると、`puppeteer`が動作し対象ページをスクレイピングします。
3. スクレイピング結果はローカルに保存され、iframe上で表示します。

#### ローカル保存する理由

* iframe内部で読み込むにあたり、クロスドメイン制約にて他サイトを自サービス上に呼び出すことは基本的に不可能なため、「ならば、こちら側に保存してしまえ」という発想の元、ローカル保存をしています。

#### JavaScriptを削除する理由

* 保存の際、scriptタグは削除しています。
* iframe上で呼び出した際、相手サーバーへ不必要なリクエストなどを発生させないようにしています。
* これのトレードオフとして、JavaScriptで描画するデータを取得するサイトは期待通りでない結果になることが多いです。<br>
qiitaトップページを試しに取得しましたがランキングなどは当然空っぽでした。

#### 画像やCSSパスを書き換え、ローカルへ保存する理由

* 上記と同じ理由で、相手サーバーへの負荷への配慮でローカルに保存するため
* 画像は相手サーバーで変更、削除されると再現できないためローカルに保存します。
* その際、`src=〇〇〇` のようなパスにて、絶対パス、相対パスなど様々あり、これを１つの基準で保存します。<br>
`public_html/data/scenario/シナリオID/スクレイピング日時/サイトドメイン/ファイルパス`
 
### スクレイピング箇所の設定

![qiita02.png](https://qiita-image-store.s3.amazonaws.com/0/136889/534d5255-6a2e-14a0-d347-c3ed2f0e3c10.png)

1. iframe上の読み込みたい箇所をクリックすると、そのDOM要素範囲が赤ハイライトされます。
2. 同時に右側の「選択中の要素」に選択しているDOM要素が表示されます。
3. 右側の「ウォッチ」をクリックすると、この要素がスクレイピング時の部分抽出（後述）箇所として、「ウォッッチリスト」に登録されます。
4. 「ウォッチ」の隣の「親選択」をクリックすると、選択中の要素の親が選択されます。

#### ブラウザベースにした理由

* HTMLなどに詳しくない人が直感的に操作できるもの＝ブラウザでポチポチと取り出したい箇所をクリックできると視覚的にわかりやすいかも
* Chromeのdeveloper toolのElementsタブにて、HTMLタグを選択すると、
実際のページの該当箇所に薄青の色味がつくのを真似たい

### シナリオ登録

* 必要なウォッチ対象を登録した後、「シナリオ登録」にてDB登録を行います。
* DBはMongoDBとなります。
* 上記までの「どのURLの、どこをスクレイピングするか」という登録内容に対し、puppeteer（操り人形）をtrouper（劇団員）が操る単位として「シナリオ」と命名しました。

### スクレイピング

* 一覧画面で、シナリオを選択し、![スクリーンショット 2019-04-10 2.51.10.png](https://qiita-image-store.s3.amazonaws.com/0/136889/be38896f-bfd3-6b93-7064-6f56756a8489.png)をクリックすると、即時スクレイピングを行います。<br>
（水泳アイコンなのは、当初「スクレイピング」でなく、「クローリング」と考えていため）
* 予定日に合わせて、サーバー側で実行するには、`npm run crawl`を実行してください。
* 定期的なスクレイピングをする場合は、上記をcron登録します。

### 結果差分比較

* スクレイピング結果が２つ以上ある場合、直近の２つを比較し、差分を表示します。
* 「ページ全体」、「ページ部分」、「画像」の３種が存在します。
* テキスト差分はスクレイピング時に`diff`コマンドにて生成し、フロントでは`diff2html.js`を使っています。
* 画像差分はスクレイピング時にスクリーンショットを撮り、`ImageMagick`にて画像差分を生成しています。

#### ページ全体

* スクレイピングしたHTMLの差分結果を表示します。

![qiita03.png](https://qiita-image-store.s3.amazonaws.com/0/136889/63e8d3bf-3f1d-4699-9832-65140390a911.png)

#### ページ部分

* ウォッチリストとして登録した箇所をのみを抜き出した差分結果を表示します。

![qiita04.png](https://qiita-image-store.s3.amazonaws.com/0/136889/08b6d9ba-763f-41e4-4cde-31296bc4eb83.png)


#### 画像

* スクレイピング時に取得したキャプチャ画像を表示します。
* 差分は、![スクリーンショット 2019-04-10 3.40.58.png](https://qiita-image-store.s3.amazonaws.com/0/136889/d44a6946-0e1b-6f3b-c843-6c5b6acf2389.png)をクリックすると、赤いハイライト差分をレイヤー表示します。

![qiita05.png](https://qiita-image-store.s3.amazonaws.com/0/136889/a5012360-ec55-8b12-d92f-0719bf5686da.png)


### 通知

* サーバーサイドにて、スクレイピング結果を通知するには、`npm run notify`を実行してください。
* 定期的な通知は、上記をcron登録してください。

## 今後の展望

* puppeteerで302リダイレクトとなる画像やCSSが現状取得できていないので、後処理を追加して漏れを無くしたい
* ログインなどを突破含め、ページ移動を可能としたスクレイピングを行えるようにしたい。
* テキスト差分で、ソースコードをクリックした際、iframeに表示している該当箇所をハイライトしたい

## あとがき

JavaScript=jQueryという知識しかないところから始まり、Vue.jsやnode.jsと最近のJS界隈を少しでも学べていい経験となりました。
さらには、この記事を書くにあたり、qiitaへの投稿や、githubを使うといった「それぐらいやってないとね〜」といわれる部分にも触れられ、とてもとても勉強となりました。
ひとりで彷徨いながらの開発だったので、何が正しく何が悪いかわかっていませんので、様々なご指摘をいただけると助かります。