CAMPFIREのページをモニタリングしてクラウドファンディングの状況をウォッチする - スクレイピング編


クラウドファンディングプラットフォームの大手CAMPFIREさんのWebサイトをスクレイピングして、ファンディング中のプロジェクトの現在の進捗や、パトロン数などをウォッチしたいと思います。

今回はスクレイピング編です。

## 対象とするSPARKSチャンネル

今回はCAMPFIREの中でも、プロトアウトスタジオ x CAMPFIREで現在開催中の`SPARKS by BOOSTER STUDIO`のチャンネルを対象にスクレイピングしてみます。

> Sparks - https://camp-fire.jp/channels/sparks

> ![スクリーンショット 2019-12-18 1.49.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/c988f1d1-4927-2782-9e49-95b6c5efe78e.png "スクリーンショット 2019-12-18 1.49.15.png")

### 環境など

* Node.js 13.3.0
* axios 0.19.0


`Node.js 13.3.0`で試してみていて、`ES Modules`な記述(import)にしてみています。
もし真似しようとしてエラーが出る人は冒頭のimport文を`const axios = require('axios');`に書き換えて従来の読み込みにしましょう。

```bash
npm init -y
npm i axios
```

こんな感じで事前に準備はしておきます。

### まずは要素の特定

Chromeのディベロッパーツール(右クリック->検証)で各要素の抜き出しをしてみます。

> ![スクリーンショット 2019-12-18 1.48.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/97cab50d-07ab-bbb9-0c1f-e712e0542720.png "スクリーンショット 2019-12-18 1.48.49.png")

何となく、class="box"や`data_project_id`の辺りの記述で引っ張ってこれるかもしれないとアタリを付けてみます。

> ![](https://i.gyazo.com/142af9760af10d55fb055088db27296d.gif)

```campfire.js
'use strict';

import axios from 'axios';

const CF_URL = `https://camp-fire.jp/channels/sparks`;

axios.get(CF_URL).then(res => {
    const bodyall = res.data;
    let parts = bodyall.split('data_project_id='); // `data_project_id=`の箇所でスプリット
    parts.shift(); //HTML全体の最初を削除
    console.log(parts[0]);
})
```

この時点でこんなHTMLが取得できます。

```
"210634"><div class="box-in"><div class="box-thumbnail"><a href="/projects/view/210634?list=channel_sparks"><img class="lazyload" data-srcset="https://static.camp-fire.jp/uploads/project_version/image/329602/f8330bbc-b104-437f-a1b0-773052ddd9d6.png?ixlib=rails-2.1.4&amp;w=320&amp;h=213&amp;fit=clip&amp;auto=format 320w, https://static.camp-fire.jp/uploads/project_version/image/329602/f8330bbc-b104-437f-a1b0-773052ddd9d6.png?ixlib=rails-2.1.4&amp;w=414&amp;h=276&amp;fit=clip&amp;auto=format 414w, https://static.camp-fire.jp/uploads/project_version/image/329602/f8330bbc-b104-437f-a1b0-773052ddd9d6.png?ixlib=rails-2.1.4&amp;w=768&amp;h=512&amp;fit=clip&amp;auto=format 768w, https://static.camp-fire.jp/uploads/project_version/image/329602/f8330bbc-b104-437f-a1b0-773052ddd9d6.png?ixlib=rails-2.1.4&amp;w=960&amp;h=639&amp;fit=clip&amp;auto=format 960w, https://static.camp-fire.jp/uploads/project_version/image/329602/f8330bbc-b104-437f-a1b0-773052ddd9d6.png?ixlib=rails-2.1.4&amp;w=1024&amp;h=682&amp;fit=clip&amp;auto=format 1024w" data-sizes="100vw" data-src="https://static.camp-fire.jp/uploads/project_version/image/329602/f8330bbc-b104-437f-a1b0-773052ddd9d6.png?ixlib=rails-2.1.4&amp;w=1120&amp;h=746&amp;fit=clip&amp;auto=format"></a></div><div class="box-title"><a title="EZ-Lapse いつでもどこでも気軽にタイムラプス動画を撮影できるカメラ" href="/projects/view/210634?list=channel_sparks"><h4>EZ-Lapse いつでもどこでも気軽にタイムラプス動画を撮影できるカメラ</h4></a><div class="sub"><p>「いつでもどこでも気軽にタイムラプス動画を。」タイムラプス動画を撮影したことはありますか？確かに素敵な動画が撮れますが、撮影中ずっとスマホが使えず、気軽に撮るこ...</p></div></div><div class="box-date sp-none"><div class="category"><a href="/projects/category/technology"><i class="fa fa-tag"></i> テクノロジー・ガジェット</a></div><div class="ownner"><a href="/profile/takeaship"><i class="fa fa-user"></i> takeaship</a></div></div><div class="meter">
<div class="meter-in"><div class="bar" style="width: 0%;"><span>0%</span></div></div>
<span>0%</span>
</div><div class="overview">
<div class="total" data-js="money-unit">
<small>現在</small>0円</div>
<div class="rest">
<small>パトロン</small>0人</div>
<div class="per">
<small>残り</small>9日</div>
</div></div></div><div class="box  " 
```

## 細々と情報を抜き出し

```
<small>残り</small>9日</div>
```

の`9`の部分だったり

```
<small>パトロン</small>0人</div>
```

の`0`の部分だったりを正規表現で抜き出します。

```campfire.js
'use strict';

import axios from 'axios';

const CF_URL = `https://camp-fire.jp/channels/sparks`;

axios.get(CF_URL).then(res => {
    const bodyall = res.data;
    let parts = bodyall.split('data_project_id=');
    parts.shift();

    const part = parts[0]; //0件目

    const project = {};
    project.percentage = part.match(/<span>(.*?)%<\/span>/)[1]; //達成率
    project.yen = part.match(/<\/small>(.*?)円<\/div>/)[1]; //円
    project.patron = part.match(/パトロン<\/small>(.*?)人<\/div>/)[1]; //パトロン数
    project.remaining_days = part.match(/残り<\/small>(.*?)日<\/div>/)[1]; //残り日数
    project.title = part.match(/<a title="(.*?)" href="/)[1]; //タイトル
    project.description = part.match(/<div class="sub"><p>(.*?)...\/p><\/div><\/div>/)[1]; //概要
    project.link = 'https://camp-fire.jp' + part.match(/div class="box-thumbnail"><a href="(.*?)">/)[1]; //リンク

    console.log(project);
})
```

```bash
node campfire.js
(node:2508) ExperimentalWarning: The ESM module loader is experimental.
{
  percentage: '0',
  yen: '0',
  patron: '0',
  remaining_days: '9',
  title: 'EZ-Lapse いつでもどこでも気軽にタイムラプス動画を撮影できるカメラ',
  description: '「いつでもどこでも気軽にタイムラプス動画を。」タイムラプス動画を撮影したことはありますか？確かに素敵な動画が撮れますが、撮影中ずっとスマホが使えず、気軽に撮るこ.',
  link: 'https://camp-fire.jp/projects/view/210634?list=channel_sparks'
}
```

こんな雰囲気ですね。

## あとは複数プロジェクト分処理を回す

```campfire.js
'use strict';

import axios from 'axios';

const CF_URL = `https://camp-fire.jp/channels/sparks`;

axios.get(CF_URL).then(res => {
    const bodyall = res.data;
    let parts = bodyall.split('data_project_id=');
    parts.shift();

    for (let i = 0, len = parts.length; i < len; i++) {
        const part = parts[i];
        const project = {};
        project.percentage = part.match(/<span>(.*?)%<\/span>/)[1]; //達成率
        project.yen = part.match(/<\/small>(.*?)円<\/div>/)[1]; //円
        project.patron = part.match(/パトロン<\/small>(.*?)人<\/div>/)[1]; //パトロン数
        project.remaining_days = part.match(/残り<\/small>(.*?)日<\/div>/)[1]; //残り日数
        project.title = part.match(/<a title="(.*?)" href="/)[1]; //タイトル
        project.description = (part.match(/class="sub"><p>(.*?)<\/p>/)) ? (part.match(/class="sub"><p>(.*?)<\/p>/)[1]) : ''; //概要
        project.link = 'https://camp-fire.jp' + part.match(/div class="box-thumbnail"><a href="(.*?)">/)[1]; //リンク
        // project.image = part.match(/class=" lazyloaded" data-srcret="(.*?)">/)[1]; //画像
        console.log(project);      
    }

})
```

```bash
$ node campfire.js

・
・
・
{
  percentage: '56',
  yen: '5,700',
  patron: '7',
  remaining_days: '4',
  title: '【おかたづけ】こどもが自分で片付けしたくなるIoTおもちゃ箱',
  description: 'こどもがおもちゃを散らかしっぱなしにして困っているお母さんお父さん大助かり！おもちゃ箱を電子工作して子供が自分で片付けしたくなります！',
  link: 'https://camp-fire.jp/projects/view/211804?list=channel_sparks'
}
{
  percentage: '10',
  yen: '13,000',
  patron: '11',
  remaining_days: '4',
  title: 'オフィスワーカー向け 座り過ぎを解決するクッション CiliCill シリシル',
  description: '世界一の「座りすぎ大国」日本そんな日本人特有の問題を解決したい！座ってる時間がわかるクッション CiliCill -シリシル-を作りました。一日の中で自分がどの...',
  link: 'https://camp-fire.jp/projects/view/207642?list=channel_sparks'
}
```

こんな感じでプロジェクトの情報を抜き出せました。

## 次回

次はこれを定期実行させる&チャット通知させる予定です。