ツイートを長期間収集する実験(インフラ準備編(2))

# 前回まで。
- [x] 要求仕様は定まった。
- [x] 最優先課題から優先して実装するよ
	- [x] Public Streamからのツイート受信！ 問題なし！
	- [x] 自動再接続機能！　過程以外はだいたい問題なし！
	- [x] MongoDBへのデータ格納！　想像以上に早かった！
- [ ] 最優先機能だけ組み込んだ、初号リリース版作るよ！　<- New!
	- [ ] 開発環境で動くかチェック
	- [ ] そろそろ実機環境を用意する？

注：だいたいリアルタイムに書いてたりするので、リアルの事情で投稿間隔は前後します。

# とりあえずハードルは低くなった(ハズ
　MongoDBへのデータの格納が、想像の100倍以上早かったので、何回か検算したり環境変えて試したりしましたが、だいたい速度面で問題になるほどの問題はない感じでした。
　Docker上に移したら速度半分くらいになったけど、それでも十分すぎるくらいの速度です。脚まわりの速度で問題になることは(たぶん)無いでしょう。
(最終的には、実機環境で速度テスト行いますが)。

　これで、最優先で実装すべき３点は、すべてそろったことになります。想像以上に出来上がりペースが早い。ほんとなら週末位までかかるかな、とか思ってたんですが(ほぼリアルタイムで書いてます)。

　そうなると、とりあえずこの３つ分(実質２つ分)を合体させて、実際流れるツイートを取得するプログラムの作成、になります。

　とりあえず、ちゃちゃっとそれっぽくまとめて書いてみたら、こんな感じに。

```Python3:TweetCrawlerV1.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import tweepy

from pymongo import MongoClient
import json

# Twitter アクセス関係の変数
CK = ''   # Consumer Key
CS = ''   # Consumer Secret
AT = ''   # Access Token
AS = ''   # Accesss Token Secert

TRACKWORD = '#xxxxxxx'   # Public Stream Filter動作時のキーワード

# MongoDB 接続関係の変数
HOST = 'localhost'      # ホスト
PORT = 27017            # ポート(デフォルト:27017)
DB_NAME = 'TwitterDB'   # DB名
COL_NAME= 'TweetCol'    # コレクション名

INSERT_LIMIT = 20      # 格納するツイート数のリミット

class Listener(tweepy.StreamListener):
    def __init__(self, api=None):   # コンストラクタ

        tweepy.StreamListener.__init__(self)    # 親クラスのコンストラクタ
        print('コンストラクタ')

        self.StopFlg = False          # ストップフラグ。
        self.TweetCnt = 0
        self.mongo_init()

        return

    def mongo_init(self):           # MongoDBの初期化
        Client = MongoClient(HOST, PORT)
        db = Client[DB_NAME]
        self.Collection = db[COL_NAME]
        print('DB初期準備完了')

        return

    def on_status(self, status):
        print('ツイート')
        self.Collection.insert(status._json)        # ここで格納

        # 一定数件格納したら自動終了
        self.TweetCnt+=1
        if(self.TweetCnt > INSERT_LIMIT):
            print(str(INSERT_LIMIT) + '件入れたので終了。')
            self.StopFlg = True
            return False

        return True

    def on_error(self, status_code):
        print('エラー発生: ' + str(status_code))
        return True

    def on_connect(self):
        print('接続しました')

        return

    def on_disconnect(self, notice):
        print('切断されました:' + str(notice.code))
        return

    def on_limit(self, track):
        print('受信リミットが発生しました:' + str(track))
        return

    def on_timeout(self):
        print('タイムアウト')
        return True

    def on_warning(self, notice):
        print('警告メッセージ:' + str(notice.message))
        return

    def on_exception(self, exception):
        print('例外エラー:' + str(exception))
        return


# ここからメイン処理
auth = tweepy.OAuthHandler(CK, CS)
auth.set_access_token(AT, AS)

while (True):     # 無限ループ
    try:
        listener = Listener()
        stream = tweepy.Stream(auth, listener)

        #どれか選択の上コメントアウト外す。
        #stream.filter(track=[TRACKWORD])
        stream.sample()
        #stream.userstream()

        #ストップフラグによるストップ判定
        if(listener.StopFlg == True):
            break;

    except KeyboardInterrupt:
        # CTRL+C
        print('CTRL + C で終了。')
        break
    except:
        pass    # 例外全部無視してループさせる
```

 * CTRL + C で強制終了させる手段がわかったので組み入れ。
 * MongoDB周りの変数になる個所(DB名、コレクション名)を外に出す。
 * 一定件数格納したらで自動終了するように。

　付け足し付け足しな作り方してるから、見た目あまりよろしくない感じだし、設定項目の名称とかどうかしたいところではあるのですが、まずは動くこと優先ということで。
　とりあえず20ツイートざくっと取得する設定(INSERT_LIMIT=20)で実行。

![実行結果](https://qiita-image-store.s3.amazonaws.com/0/139416/8df848ef-b9e9-30ea-8add-8259503c069a.png)

　……コンストラクタ、DB準備完、Twitter接続の後、ツイートが複数表示されて終了。

![実行結果その２](https://qiita-image-store.s3.amazonaws.com/0/139416/58e94035-ec55-faa4-9c8e-74d859d6bdc5.png)

　MongoDBにも入ってます。ヤッター！
　……「(self.TweetCnt > INSERT_LIMIT)」でチェックしてたら、ゼロオリジンなら21コ入りますね(アホ

# 次は実行環境で。
　ひとまずバージョン１、ということで、いったん置いておくとして、次はこれを実際の実行環境で動かすようにしてみます。
## QNAP Container Station とは。＆この先の作業。
　<a href="http://qiita.com/yuhkan/items/671abe3753a99cef978f">初回でも触れましたが</a>、今回の実行環境はQNAP TS-451という高機能NAS上で、Dockerを動かします。QNAPのNASにはContainer Stationという、要はDocker(とLXC)のフロントエンドがあり、それを通じてセットアップ、設定などを行う事になります。
　QNAPのContainer StationでDocker、という、どっちかというと~~物好きな~~マイナーなことをやろうと思うと、どうしても英語文献に当たらざるを得ないのですが、幸いなことに<a href="http://www.moyashi-koubou.com/blog/nas_docker/">先行していろいろ記録を残してくださっている方</a>がいたため、これ幸いと参考にさせていただくことに。

　さて、Dockerのコンテナに対してファイルを送受する場合、、ホストとのファイル共有というのが一般的(のハズ)です。今回、すべてDocker上に展開して作業することになります。が、Dockerコンテナ上のデータは、再起動かかったりすると消えちゃいます。それを防ぐにはデータをホストとの共有フォルダに置いて、永続化させる必要があります……という認識です。

![システム実行イメージ](https://qiita-image-store.s3.amazonaws.com/0/139416/f110aab8-4b92-8a68-9390-e6665e9ee45a.png)

　だいたいこんな感じのイメージ。
　とりあえず出来なきゃいけないのは、

* MongoDBのコンテナDL→イメージ作成
	* ファイル共有でNAS上のフォルダを見れるようにし、データはNAS上に置く
* PythonのコンテナDL→イメージ作成
	* ファイル共有でNAS上のフォルダを見れるようにし、スクリプトはNAS上に置く
* MongoDBとPythonをリンクし、DB接続出来るようにする
* 起動後に自動で(バックグラウンドで？)プログラムを実行開始するように設定。つまり、ターミナル等での接続状態維持を不要とする。

　になります。
　物の本によれば、データ専用イメージを作ってウンタラとかあるのですが、そこまで込み入ったシステムでは無いし、最終的な保存先は変わらずNAS上で、そこから外に出る事も無いという理由でその辺は省略。

　やらなきゃいけないことも、だいたいググれば見つかるレベルであるし、それほど時間もかからずに何とかなるかもねー、と思っていたのですが。

　**甘かった。**

## Container Stationでハマる
　dockerにしてもなんにしても、基本的に本家本元の機能やその設定方法、トラブルシューティングに関してはたくさん蓄積されるし、日本語テキストも多いのですが、特定機種の専用機能の一部、となると途端に情報量は減り、日本語文献はないという状態に陥ります。

　つまり

![不具合。](https://qiita-image-store.s3.amazonaws.com/0/139416/54e6907d-c645-f906-9ada-eeb7ebb35b16.png)

　(共有先フォルダ指定のドロップダウンリストにフォルダが表示されない)<br>*こんなときどうしたらいいのかわからない。*

### トラブル：ファイル共有の設定ができない
　これまでは検索一発、ボタン数回でイケる範囲で快適だなーと無邪気に思っていたのですが、いざファイル共有のためのフォルダ指定をしようとしても設定ができないという現象が発生。
　本来であれば(少なくとも英文マニュアルに従えば)赤丸で囲んだドロップダウンのところに、NASのフォルダ一覧が表示されるはずなのですが、なぜか表示されない。
　英語しかないサポートフォーラムを検索しても確たる原因も対処も分からず、**ここだけで4日悩むことに**。

* とりあえず、テストで動かしてるコンテナとDL済みイメージファイルを全消しし、最初から試す<br>	→ダメでした
* NAS自体の再起動。<br>	→ダメでした
* Container Station 自体を停止→起動、ついでアンインストール→再インストール<br>	→ダメでした
* NASのファームウェアバージョンが前のものならできたという情報をもとに、バージョンダウンを敢行<br>	→ダメでした
* sshでNASに接続して、共有フォルダにしたいフォルダをchownすればOKという情報<br>	→ダメでした

　とにかく手当たり次第に対処法(じゃないかなーと思うあれこれ)をためし、最終的に、
1. AdminでNASにログインし、共有フォルダの権限設定をやり直して再適用させ、
2. NAS管理画面のContainer Stationでなく、独立の管理画面で、<br>(Container Stationは管理画面のGUIのほか、単独のwebページからのアクセスもできる。ただし明示的に設定しないと表示されない)
3. FirefoxでもChromeでもEdgeでもなく、InternetExplorerでアクセスする
　ことで、共有フォルダが表示されるようになりました。

　なんだこの罠……さすがに泣きが入るぞ……

　ともあれ、なんとか設定ができるようになりました。これで不意にリセットされても、データが飛んだりすることがなくなる……ハズです。たぶん。


# コンテナのセットアップ
　気を取り直して、セットアップを再開しましょう。

## MongoDB のセットアップ
　とりあえずMongoDBの側からセットアップしてみます。

　Container Stationの使い方はDockerのGUIツールである<a href="https://kitematic.com/">Kitematic</a>に似ています。
1. 「コンテナの作成」を選択し、検索するなどしてイメージを指定。インストールのバージョンは「インストール」ボタン押下後に決定
2. コンテナの作成画面で詳細を設定。
	* ネットワークの設定で、デフォルトポート27017をホスト側、コンテナ側ともに入力。<br>管理画面からだと、デフォルトの設定値が表示されて、ホスト側には"auto"と表示されているのを変更。
	* 共有フォルダの設定で、「/data/configdb」「/data/db」に、ホストの共有先を指定する。
　なお、この辺の設定は**後から設定変更できない**ので、慎重に設定する必要がある。
3. 設定が終わるとバックグラウンドでコンテナの作成が行われるので、「概要」画面に移り、起動していることを確認する。

　さて、これが動いてることを確認するにはどうするか、と考えて、先のPythonのスクリプトで、MongoDBの接続先をNASにしてみることに。

```
#HOST = 'localhost'      # ホスト
HOST = 'xxx.xxx.xxx.xxx'      # ホスト
PORT = 27017            # ポート(デフォルト:27017)
```

　実行自体はまだWinのVisualStudioから。ぽちっとな、でとくにエラーもはかず終了。

　動作してるかの確認も、とりあえずWinからRobomongoで。
　ちゃんと格納されてます。成功です。

## Python のセットアップ
　次はPythonのセットアップです。
1. コンテナの検索からインストールボタンまではMongoの時と手順同じ。
2. 詳細設定画面で「リンク」の設定。先に作成したMongoDBのコンテナを指定し、エイリアスで名前を決める。
3. 同じく詳細設定で共有フォルダの設定を実行。
　これで目的とするホストが出来上がりました。

### ライブラリのインストール
　次はこれに、tweepyとpymongoを入れる必要があるのですが……**これ、リモートからのアクセス、どうすんだ？**
　一応機能として、Container Stationから端末を呼び出す機能があり、ブラウザ上でshなりbashなりを立ち上げることもできます。
　が、これ、root扱いなんですよね……大丈夫といえば大丈夫のはずなんですけど、やっぱもにょる……

　ひとまずおいておいて、同機能でPythonが動いてるコンテナにログイン。

```bash
xxxx@xxx99999999:/# python --version
Python 3.5.2
xxxx@xxx99999999:/# pip install -U pip
Requirement already up-to-date: pip in /usr/local/lib/python3.5/site-packages
xxxx@xxx99999999:/# pip install -U tweepy
Collecting tweepy
  Downloading tweepy-3.5.0-py2.py3-none-any.whl
Collecting requests-oauthlib>=0.4.1 (from tweepy)
  Downloading requests_oauthlib-0.7.0-py2.py3-none-any.whl
Collecting requests>=2.4.3 (from tweepy)
  Downloading requests-2.11.1-py2.py3-none-any.whl (514kB)
    100% |████████████████████████████████| 522kB 996kB/s
Collecting six>=1.7.3 (from tweepy)
  Downloading six-1.10.0-py2.py3-none-any.whl
Collecting oauthlib>=0.6.2 (from requests-oauthlib>=0.4.1->tweepy)
  Downloading oauthlib-2.0.0.tar.gz (122kB)
    100% |████████████████████████████████| 122kB 2.3MB/s
Installing collected packages: requests, oauthlib, requests-oauthlib, six, tweepy
  Running setup.py install for oauthlib ... done
Successfully installed oauthlib-2.0.0 requests-2.11.1 requests-oauthlib-0.7.0 six-1.10.0 tweepy-3.5.0
xxxx@xxx99999999:/# pip install -U pymongo
Collecting pymongo
  Downloading pymongo-3.3.0-cp35-cp35m-manylinux1_x86_64.whl (337kB)
    100% |████████████████████████████████| 337kB 1.4MB/s
Installing collected packages: pymongo
Successfully installed pymongo-3.3.0
xxxx@xxx99999999:/#
```

　問題なくインストールができてます。

### 実機テスト
　では、この環境で、作ったスクリプトが動くか確認しましょう。

　あらかじめWin上でスクリプトのHOST部を修正。リンク時に指定したMongoのエイリアス名(ここではmongo)に変更。ポートはデフォルトのままなのでそのまま利用。

```
#HOST = 'localhost'      # ホスト
HOST = 'mongo'      # ホスト
PORT = 27017            # ポート(デフォルト:27017)
```

　これをNAS上の共有フォルダにコピーして、実行。

	xxxx@xxx99999999:/share# python TweetCrawlerV1.py
	コンストラクタ
	DB初期準備完了
	接続しました
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	ツイート
	20件入れたので終了。
	xxxx@xxx99999999:/share#

　動きました！　やったー

# この次の作業は？

　とりあえずこれで、最低限の機能は備わりました。最悪、手動で実行、手動で終了とすれば、ツイートを取り巻くるという所期の目標は達成できます。
　次回以降は、時間の許す範囲で追加機能の実装、あと、ちょうど良いので直近のイベント<sub>(隠語)</sub>ハッシュタグで取得を行う実証実験を行いたいと思います。

# ゆるぼ
* tweepyを使うスクリプトで、Twitter Streaming APIにおける**異常系**動作の確認手段。
* ツイート受信のたびに日時チェックして終了させるのスジが悪いので、なんか良い手段(手っ取り早いこと)

(つづく。)