Python+pandasを使ってRSSフィードを取得→Mattermostに投稿＆DBに保存

## やったこと

Mattermostを導入してなにかやってみたかったので、RSSフィードを投稿するプログラムを作りました。（あとで、[公式のプロジェクト](https://github.com/bitbackofen/Rss-Atom-Feed-Integration-for-Mattermost)があったことに気づいたのは秘密）

いろいろと応用が利くかなと思い、取得したfeedをpandasで処理して
DBに格納することとしました。

※Mattermostってなんぞやって言う人は[こちら](http://qiita.com/terukizm/items/4a4016d8ec5a21856e4f)

## 環境

- CentOS7
- Python2.7
    - pandas
    - feedparser
    - sqlalchemy 
    - requests
- PostgreSQL9.5
- MatterMost

PostgreSQLについては、dockerイメージから導入しました。

```bash
docker pull postgres:9.5
docker run -p 5432:5432 --name postgres-server -v /var/lib/postgresql:/var/lib/postgresql:rw  postgres:9.5
firewall-cmd --permanent --add-port=5432/tcp
firewall-cmd --reload
```

これでとりあえずリモート接続可能なPostgreSQLコンテナが立ちあがるはず。
docker便利だなぁ。。。

そのほかの環境構築は省略、Pythonはpyenv環境で実施しています。

## 1. RSSフィードを取得する。

`feedparser`というPythonライブラリを使います。
ここら辺を参考にして、`pip`でインストールしました。

http://qiita.com/shunsuke227ono/items/da52a290f78924c1f485

```py
import feedparser

RSS_URL = "http://b.hatena.ne.jp/hotentry/it.rss"
print("Start get feed from %s" % (RSS_URL))
feed = feedparser.parse(RSS_URL)
```

これでフィードが取得できます。
（ちなみに↑ははてなのテクノロジーカテゴリのホットエントリを取得しています。）

## 2. 取得したフィードをpandas.DataFrameに展開

今後の処理のしやすさなどを考えて、pandas.DataFrameにマッピングします。

```py
import pandas as pd
entries = pd.DataFrame(feed.entries)
```

...終わり。
pandasさん優秀ですね。

はてなのRSSフィードの場合、以下の12列の要素が取得されていました。

- content
- hatena_bookmarkcount
- id                      
- link                    
- links                   
- summary                 
- summary_detail          
- tags                    
- title                   
- title_detail            
- updated                 
- updated_parsed          

ここまでくれば、あとはpandasの機能で自由にデータをいじれます。

## 3. 新着フィードのチェック

feedparserはとても便利ですが、アクセスした時点のフィードを取得するので過去に取得したフィードと重複がでます。

ここでDataFrameに展開した意味が出てくるのです！
以下はDataFrameの操作で、新着のフィードのみを取り出して表示する例です。

```py
already_print_feeds = pd.Series()

while True:
        time.sleep(300)
        feed = feedparser.parse(RSS_URL)
        entries = pd.DataFrame(feed.entries)
        new_entries = entries[~entries['id'].isin(already_print_feeds)]
        if not new_entries.empty:
            for key, row in new_entries.iterrows():
                feedinfo = "[**%s**](%s)\n\n>%s" % (row['title'],  row['link'], tag_re.sub('', row['summary']))
                print(feedinfo)
        already_print_feeds = already_print_feeds.append(new_entries['id'])
```

### ちょっと解説

```py
new_entries = entries[~entries['id'].isin(already_print_feeds)]
```

これは、取得したRSSフィードから新着のみをぬきだしています。

`already_print_feeds`にはこれまで取得したRSSフィードの`id`が入っている前提です。

そうすると、`entries`に格納されたフィードのうち、
新着の行のみに`True`が設定されたSeriresが返却されるので、
これを`entries`のインデックスに指定すれば新着だけ抜き出せます。

```py
~entries['id'].isin(already_print_feeds)
# =>
0     False
1     True # => ★New!
2     False
3     False
4     False
5     False
6     False
7     False
8     False
9     False
10    False
11    False
12    False
13    False
14    False
15    False
16    False
17    False
18    False
19    True # => ★New!
20    False
21    False
22    False
23    False
24    False
25    False
26    False
27    False
28    False
29    False
Name: id, dtype: bool
```

`already_print_feeds`にはこれまで表示した新着フィードのIDを追記していけばいいです。

```py
already_print_feeds = already_print_feeds.append(new_entries['id'])
```

:warning:  ただし上記のコードだと`already_print_feeds`に無限にデータが貯まっていくので、いつか破綻します（メモリが）
1日1回フラッシュしたり、DBから読み取るようにしたりしましょう

## 4. DB(PostgreSQL)に保存

取得した、RSSフィードをPostgreSQLに保存します。
ただし、列は下記に絞りました。

- id 
- link
- title
- summary
- updated 

まずDBにテーブルを作ります。

```sql
create table feed ( id text primary key , link text, title text, summary text, updated timestamp );
```

一応、idには主キー制約を入れて、かつupdatedはtimestamp型としておきました。
（はてなのフィードのupdatedはそのままtimestamp型としてINSERTできる形式のようです。）


```py
from sqlalchemy import create_engine

DATABASE_CONN = "postgresql://xxxx:xxxx@xxxxx:xxx/xxxx"
DATABASE_TABLE = "feed"
# connect database
engine = create_engine(DATABASE_CONN)

# Store database
stored_entries = new_entries.ix[:, [
                "id", "link", "title", "summary", "updated"]]
stored_entries.to_sql(DATABASE_TABLE, engine, index=False, if_exists='append')
```

DataFrameの`to_sql`メソッドを使います。

- index=False

とすることで、格納時に勝手にインデックス列を付与しなくなり、

- if_exists='append'

で、すでに存在するテーブルにデータを追記する挙動となります。

## 5. MatterMostに投稿する

HTTPリクエストを送る`request`というPythonライブラリで非常に簡単に投稿できます。

```
import requests
import json

mattermosturl = "MatterMostのincomming webhook URL"
username = "好きな名前"
header = {'Content-Type': 'application/json'}
payload = {
        "text": feedinfo,
        "username": username,
        }

resp = requests.post(mattermosturl,
                     headers=header, data=json.dumps(payload))
```




## というわけで

せっかくpandasにマッピングしたので、
機械学習的なこともやっていきたいと思います。