Posted at

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

More than 3 years have passed since last update.


やったこと

Mattermostを導入してなにかやってみたかったので、RSSフィードを投稿するプログラムを作りました。(あとで、公式のプロジェクトがあったことに気づいたのは秘密)

いろいろと応用が利くかなと思い、取得したfeedをpandasで処理して

DBに格納することとしました。

※Mattermostってなんぞやって言う人はこちら


環境


  • CentOS7

  • Python2.7


    • pandas

    • feedparser

    • sqlalchemy

    • requests



  • PostgreSQL9.5

  • MatterMost

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

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

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にマッピングします。

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の操作で、新着のフィードのみを取り出して表示する例です。

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'])


ちょっと解説

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

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

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

そうすると、entriesに格納されたフィードのうち、

新着の行のみにTrueが設定されたSeriresが返却されるので、

これをentriesのインデックスに指定すれば新着だけ抜き出せます。

~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を追記していけばいいです。

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にテーブルを作ります。

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

一応、idには主キー制約を入れて、かつupdatedはtimestamp型としておきました。

(はてなのフィードのupdatedはそのままtimestamp型としてINSERTできる形式のようです。)

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にマッピングしたので、

機械学習的なこともやっていきたいと思います。