クラウドで手軽に始めるクローリング

#はじめに
前回の記事「[Pythonで手軽に始めるWebスクレイピング](https://qiita.com/Moh_no/items/a835f77b6b4e3972fbbe)」では、簡単なスクレイピングの方法をご紹介しました。
しかし、せっかくスクレイピングコードを用意しても、ローカルで1個ずつ手作業で実行するのはダルいですよね？
放置するだけでデータを自動更新してくれるクローラーが欲しい。でもレンタルサーバは高いし、メンテナンスもめんどくさい。ではどうするか？
クラウドを使うのです。

本投稿では、スクレイピングコードをクラウド上の仮想マシン（VM）で動かすことで、簡単にクローラーを作る方法をご紹介します。

#クローラーの構築手順
クラウドは **Google Cloud Platform (GCP)** を採用します。
手順は以下の通りです。

1. GCPの登録とプロジェクトの作成
2. [VMインスタンスの作成](#vmインスタンスの作成)
3. [VMインスタンスの環境構築](#vmインスタンスの環境構築)
4. [クローラーのコーディング](#クローラーのコーディング)
5. [Error Reporting（任意）](#error-reporting任意)
6. [cronの設定](#cronの設定)

GCPの初期設定は以下のページが参考になりました。
[これから始めるGCP（GCE）　安全に無料枠を使い倒せ](https://qiita.com/Brutus/items/22dfd31a681b67837a74)
特にGCPの登録とプロジェクトの作成（上記1）は、これ以上書くこともないため割愛します。

#VMインスタンスの作成
GCPのメニューより **Google Cloud Engine (GCE)** を選択して、VMインスタンスを作成します。
###小規模なクローラーの場合
スクレイピング時間が数分で終わるような小規模なクローラーなら、**無料枠でVMを使いましょう**。
私のオススメは以下の構成です。

|項目|オススメ設定|
|:-:|:-:|
|リージョン|us-west1|
|マシンタイプ|f1-micro1[^1]（vCPU x 1、メモリ 0.6 GB）|
[^1]: 無料枠のf1-micro1は1台までです
|OS|Debian GNU/Linux, 10 (buster)[^2]|
[^2]: f1-micro1のスペックで動かすには、Debian以外は重すぎるので注意
|ディスクストレージ|30Gb[^3]|
[^3]: 標準永続ディスクのストレージは 30Gb までが無料枠。デフォルトは10Gb
|ファイアウォール|HTTPSトラフィックを許可する|
|IDとAPIへのアクセス|すべての Cloud API に完全アクセス権を許可[^4]|
[^4]: デフォルトのアクセス権でも良いが、ここでアクセス権を与えないと、後々APIでいろいろ工夫しようとした際につまづく（Error Reportingなど）

この構成では、スペックの問題でwebdriverはまともに使えません。
つまり、**seleniumを使ったスクレイピングはできません**。
seleniumを使ったスクレイピングがしたい場合、以下の有料枠のVMを設定してください。

###大規模なクローラーの場合
実行時間が長いコードならびに、Beautiful Soupだけでなくseleniumやwebdriverを使用するコードは、無料枠で動かすにはスペックが足りません。
[公式ドキュメント](https://cloud.google.com/compute/docs/machine-types/?hl=ja)などを参考に、必要なスペックを見極める必要があります。
以下に構成の例をあげます。

|項目|設定例|
|:-:|:-:|
|リージョン|us-west1|
|マシンタイプ|e2-standard-4（vCPU x 4、メモリ 16 GB）|
|OS|Debian GNU/Linux, 10 (buster)|
|ディスクストレージ|30Gb|
|ファイアウォール|HTTPSトラフィックを許可する|
|IDとAPIへのアクセス|すべての Cloud API に完全アクセス権を許可|

私の場合は、上記構成でだいたい 100〜300円/日 くらいになります。
課金は時間単位です。インスタンスの電源をつけっぱなしだと、何もしていなくても課金されます。
ですので、私はクローリングしたい時間に自動でインスタンスを起動し、処理が終わったら電源を落とすように設定しています（[VMインスタンスの起動設定（有料枠向け）](#vmインスタンスの起動設定-有料枠向け)を参照）。

###VMインスタンスの起動設定 （有料枠向け） 
VMインスタンスを使いたい時だけ起動するようにすれば、無駄な課金を節約することができます。
インスタンス起動時間の設定は、下記の2ステップで簡単に実装できます。

1. VMインスタンス起動用の **Cloud Functions** 関数を設定する（[公式ドキュメント](https://cloud.google.com/scheduler/docs/start-and-stop-compute-engine-instances-on-a-schedule#set_up_the_functions_with)）（ここで同時に、関数のトリガーとなる **Cloud Pub/Sub** の設定も行われる）
2. Cloud Pub/Sub を呼び出す **Cloud Scheduler** ジョブを設定する（[公式ドキュメント](https://cloud.google.com/scheduler/docs/start-and-stop-compute-engine-instances-on-a-schedule#set_up_the_jobs_to_call)）

インスタンスの終了も同様にできますが、今回はインスタンス自身にシャットダウン操作をさせるため、設定しません（[クローラーのコーディング](#クローラーのコーディング)の二つ目のコード参照）。

**注意**：Cloud Functions, Pub/Sub, and Compute Engine API を有効にする必要があるため、VMインスタンスのAPIアクセス権限が必要です。



#VMインスタンスの環境構築
###ssh接続
下記のいずれかの方法で、VMインスタンスにssh接続します。
・Google Cloud Shell で```$ gcloud compute ssh < インスタンス名 >```を入力する（**推奨**）。
・GCPのVMインスタンス一覧より、各SSHボタンを押す（GUI操作からVMインスタンスにssh接続できる）。
###環境構築
下記コマンドでクローラーの環境を整えます。

- 全体の更新

```
$ apt-get update
$ apt-get upgrade
```
- pipのインストール

```
$ apt-get install python3-distutils
$ curl -kL https://bootstrap.pypa.io/get-pip.py | python3
```
- Error Reporting のパッケージインストール（任意）

```
$ pip install google-cloud-error-reporting --upgrade
```
- python3の各ライブラリをpipでインストール

＜Beautiful Soupを使う場合＞

```
$ pip install requests
$ pip install beautifulsoup4
```
＜Seleniumを使う場合＞

```
$ apt-get install wget
$ sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
$ wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
$ apt-get update
$ apt-get install google-chrome-stable
$ pip install chromedriver-binary==<Chromeバージョン番号>
$ pip install selenium
```
Google Chromeのバージョン番号は```apt list --installed google*```で確認できます。

Google Chrome および webdriver のインストールについては前回の記事にまとめましたので、参考にしてください。
[Pythonで手軽に始めるWebスクレイピング](https://qiita.com/Moh_no/items/a835f77b6b4e3972fbbe#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB-webdriver%E3%81%AA%E3%81%A9)


#クローラーのコーディング
まず初めに、VMインスタンス上にあなたが作ったスクレイピングコードを配置してください（例：下記の code1.py〜code4.py）。方法は問いません。
私の場合は、開発したコードをGitHubで管理しているため、そこからインスタンス上にクローンしています（[VMインスタンスにGitHubからコードを共有 （任意）](#vmインスタンスにgithubからコードを共有-任意)）。
###実行用シェルファイル
もしあなたがマシンタイプ f1-micro1（無料枠）でスクレイピングしたいなら、コードは下記で十分です。

```crawler.sh
# code1.py 〜 code4.py のスクレイピングコードを順番に実行するだけ
python3 code1.py
python3 code2.py
python3 code3.py
python3 code4.py
```
しかしマシンタイプが有料枠の場合、実行時間の短縮のために**並列処理**をしましょう。

並列処理は、シェルの`wait`コマンドが一番簡単です。
参考：[【wait】Linuxで指定したプロセス・ジョブの終了を待つコマンド](https://uxmilk.jp/52821)

```crawler.sh
# code1.py 〜 code4.py のスクレイピングコードを並列実行する
python3 code1.py &
python3 code2.py &
python3 code3.py &
python3 code4.py &
wait
echo "クローラー実行完了"
# VMが有料枠の場合、処理の最後にVMをシャットダウンする
sudo shutdown -h now
```

さらにスクレイピングコードが大量にある場合、上記のようなコードではCPUやメモリに遊びが生じます。
より効率的に並列処理を行いたい場合は、`xargs`や`GNU parallels`でコーディングしましょう。
参考：[Linuxにて複数のコマンドを並列実行（同時実行数の制限付き）](https://www.slideshare.net/maraigue/linux-80826380)

###VMインスタンスにGitHubからコードを共有 （任意）
まずはGitHubとのssh通信を確立させます。
参考：[GithubのSSH通信設定](https://qiita.com/yysskk/items/974c9c55d66a26515651)

VMインスタンス上で下記コマンドを実行し、gitをインストールします。
さらに、gitに自分の情報を教えます。

```
$ apt-get install git-all
$ git config --global user.email < GitHubに登録した自分のメールアドレス >
$ git config --global user.name < GitHubに登録した自分の名前 >
```

任意のパスで`$ git clone github:< プロジェクト名 >/< リポジトリ名 >.git`を行い、スクレイピングコード開発用のリポジトリをクローンしましょう。

上記`crawler.sh`の一番最初に`git pull origin master`を書き加えると、常に最新版のコードでクローリングができるためオススメです。

#Error Reporting（任意）
GCPでは、スクレイピングコードがpythonの場合、クローリング中にエラーが生じた場合に教えてくれる便利機能 **Error Reporting** を簡単に使うことができます。
必要な準備は下記の2点だけです。

- Error Reporting API を有効にする（[公式ドキュメント](https://cloud.google.com/error-reporting/docs/setup/python?hl=ja#before_you_begin)）
- [Error Reporting パッケージのインストール](#環境構築)

スクレイピングコードにライブラリをインポートして、クライアントをインスタンス化して使います。

```errorReporting.py
# わざとError Reporting にエラーを渡すコード
try:
    num = "str" / 2  # TypeError
except Exception:
    from google.cloud import error_reporting
    client = error_reporting.Client()
    # Error Reportingにエラーの内容を出力したい場合
    client.report_exception()
    # Error Reportingに手動でエラーを出力したい場合
    client.report("不具合が発生しました")
```
`client.report_exception()`では、Error Reporting に`TypeError: unsupported operand type(s) for /: 'str' and 'int'`が渡されます。
`client.report("不具合が発生しました")`では、Error Reporting に`Exception: 不具合が発生しました`が渡されます。
場面に応じて、好きな方を使い分けましょう。

**注意**：Error Reportingには、VMインスタンスのAPIアクセス権限が必要です。

#cronの設定
最初にタイムゾーンを日本にして、cronを再起動しましょう。

```
$ ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
$ service cron restart
```
次にcrontabを設定しましょう。
crontabは下記コマンドで編集画面が開きます。

```
$ crontab -u < ユーザ名 > -e
```
上記コマンドで開いた任意のテキストエディタで、下記の設定を追記してください。

```
SHELL=/bin/bash
CRON_TZ="Japan"
10 00 01 * * bash /< 任意の絶対パス >/crawler.sh
```
これで、毎月1日の午前0時10分に、crawler.shが実行されます。
クローラーの完成です！

**注意**：[VMインスタンスの起動設定](#vmインスタンスの起動設定-有料枠向け)を設定している場合、crawler.shの実行タイミングはインスタンスの起動後にするようにしてください。同時だとうまくいきません。

#おわりに
本投稿では、クラウド上のVMインスタンスにスクレイピングコードを配置するだけで、簡単にクローラーを作ることができました。
cronの時間や実行用のシェルファイルを工夫して、あなただけのオリジナルクローラーを作ってみましょう。

<font color="Darkgray">余談ですが、本文中で紹介した Cloud Functions 関数のみでのスクレイピングも可能です。
しかし、実行時間あたりの料金が高く、既存のスクレイピングコードがある場合はそれも活かしづらいです。
GCEは無料枠があり、馴染みのあるDebian環境で構築できることから、クローラーの入り口に向いているのではないでしょうか。</font>

# 参考
[Pythonで手軽に始めるWebスクレイピング](https://qiita.com/Moh_no/items/a835f77b6b4e3972fbbe)
[これから始めるGCP（GCE）　安全に無料枠を使い倒せ](https://qiita.com/Brutus/items/22dfd31a681b67837a74)
[【wait】Linuxで指定したプロセス・ジョブの終了を待つコマンド](https://uxmilk.jp/52821)
[Linuxにて複数のコマンドを並列実行（同時実行数の制限付き）](https://www.slideshare.net/maraigue/linux-80826380)
[GithubのSSH通信設定](https://qiita.com/yysskk/items/974c9c55d66a26515651)

**公式ドキュメント**
[マシンタイプ](https://cloud.google.com/compute/docs/machine-types/?hl=ja)
[Cloud Scheduler を使用した Compute インスタンスのスケジュール設定](https://cloud.google.com/scheduler/docs/start-and-stop-compute-engine-instances-on-a-schedule)
[Python 用 Error Reporting の設定](https://cloud.google.com/error-reporting/docs/setup/python?hl=ja)