Feedy(Python)でRSSフィードをいい感じに処理する
最近、RSSフィードをfetchしてゴニョゴニョ処理したいと思うことが多かったのですが、特に気にいるライブラリが無かった *1 のでFeedyというライブラリを作ってみました。 個人的には結構気に入っていて、便利に使えているので紹介します。
もともと欲しかった機能・特徴としては、
- デコレータベースでシンプルに記述できる
- 当然、前回fetchした時間からの更新分のみの取得も可
- RSSフィードのリンク先のhtmlも自動で取得して、好きなHTMLパーサ(個人的にはBeautifulSoup4)でいい感じに処理したい
具体的には↓のように記述します
from feedy import Feedy feedy = Feedy('./feedy.dat') # 前回フェッチした時間とかを格納(Redisとかに自分で置き換えることも可能) @feedy.add('https://www.djangopackages.com/feeds/packages/latest/rss/') def djangopackages(info, body): # django packagesのRSSに載っている、パッケージ名と記事へのリンクを出力する例 print("- [%s](%s)" % info['article_title'], info['article_url']) if __name__ == '__main__': feedy.run()
他にも、全部は紹介しませんがプラスαの機能として
- デバッグ時とかは柔軟に実行時のオプションが指定できるCommand Line Interfaceも欲しい
- ページごとにFacebookとかはてブ数も気軽に取得できる仕組みがほしい
- ↑のような便利な機能を簡単に追加できる、プラグイン機構
- 上記のことを同期的に処理するとそれなりに時間がかかるので、裏側ではasyncioで高速に処理しておきたい(リクエストが飛び過ぎないようにsemaphoreも指定できる)
- HTMLをパースして文章をjanomeで形態素解析・各単語の出現頻度を数えた結果もほしい
Feedyを使ってみる
README頑張って書いたので、Feedyの基本的な使い方やCLIのオプションなどはGithubを見てください。 ここではとりあえずみなさんにも便利そうな使い方を3つぐらい紹介します。
記事のfacebookのいいね数、pocketの保存数、はてブ数を取得してみる
プラグインは自分で書くことも出来ますが、とりあえず僕の方で作った social_share_plugin
を使ってみます。
SNSでのシェア数等が簡単に習得できます。
from feedy import Feedy from feedy_plugins import social_share_plugin feedy = Feedy('feedy.dat') feedy.install(social_share_plugin) @feedy.add('http://nwpct1.hatenablog.com/rss') def c_bata_web(info, body, social_count): print('=============================') print('Title:', info['title']) print('HatenaBookmark: ', social_count.get('hatebu_count')) print('Pocket:', social_count.get('pocket_count')) print('Facebook:', social_count.get('facebook_count'))
最新の記事3つ分ぐらい表示してみましょう
$ feedy example.py feedy -t c_bata_web -m 3 ============================= Title: Pythonを使ったデータ分析に関する内容をJupyter Notebookにまとめ始めました HatenaBookmark: 67 Pocket: 79 Facebook: 20 ============================= Title: Golangでつくる検索エンジン(Webクローラ、MongoDB、Kagome、gin) HatenaBookmark: 67 Pocket: 94 Facebook: 5 ============================= Title: Python製WebフレームワークのURL DispatcherとType Hintsの活用について HatenaBookmark: 41 Pocket: 66 Facebook: 1
成功 🎉
このブログの画像のURLを集めてみる
試しに↓のようにimgタグを全て表示してみます。
from feedy import Feedy from bs4 import BeautifulSoup feedy = Feedy('feedy.dat') @feedy.add('http://nwpct1.hatenablog.com/rss') def c_bata_web(info, body): soup = BeautifulSoup(body, "html.parser") for x in soup.find_all('img'): print(x)
実行すると↓の通り。
$ feedy example.py feedy -t c_bata_web -m 3 --ignore-fetched <img alt="この記事をはてなブックマークに追加" height="20" src="https://b.st-hatena.com/images/entry-button/button-only.gif" style="border: none;" width="20"/> <img alt="実践 機械学習システム" class="hatena-asin-detail-image" src="http://ecx.images-amazon.com/images/I/51%2BfZJOKEKL._SL160_.jpg" title="実践 機械学習システム"/> : <img alt="f:id:nwpct1:20160409180830p:plain" class="hatena-fotolife" itemprop="image" src="http://cdn-ak.f.st-hatena.com/images/fotolife/n/nwpct1/20160409/20160409180830.png" title="f:id:nwpct1:20160409180830p:plain"/> : :
はてなブックマークボタンなどノイズも混じっていますが、どうやら class="hatena-fotolife"
は私がアップロードした画像のようです。
class="hatena-fotolife"
で絞ってみます。
for x in soup.find_all('img', {'class': 'hatena-fotolife'}): print(x['src'])
実行してみましょう
$ feedy example.py feedy -t c_bata_web --ignore-fetched http://cdn-ak.f.st-hatena.com/images/fotolife/n/nwpct1/20160409/20160409180830.png http://cdn-ak.f.st-hatena.com/images/fotolife/n/nwpct1/20160107/20160107173222.png http://cdn-ak.f.st-hatena.com/images/fotolife/n/nwpct1/20160107/20160107173406.jpg :
成功 🎉
単語の出現頻度をカウントする
はてなブックマークのITカテゴリのホットエントリーやHacker Newsの一覧から自分の興味のある記事だけ抽出したいと考えています。 そのためには、Bag-of-Wordsした結果に対してTF-IDFの計算やクラスタリングやトピックモデルなどの機械学習手法を当てはめるとよさそうです。 全部説明するのは長いので、ここではBag-of-Wordsをするところまで紹介。
from feedy import Feedy from feedy_utils import word_counter feedy = Feedy('feedy.dat') @feedy.add('http://b.hatena.ne.jp/hotentry/it.rss') def hatena_it(info, body): print(word_counter.count_words(body).most_common(20)) # 出現回数の多い単語を20個取得して表示
実行してみます
$ feedy example.py feedy -t hatena_it -m 3 --ignore-fetched [('エンジニア', 583), ('paiza', 203), ('スケジュール', 129), ('人', 124), ('コミュニケーション', 90), ('ユーザー', 77), ('仕様', 75), ('jp', 73), ('tag', 72), ('B', 66), ('learning', 64), ('項目', 64), ('img', 64), ('jmp', 64), 62), ('業務', 61), ('http', 59), ('IT', 58)] [('月', 1059), ('ユーザー', 140), ('document', 138), ('ハリス', 75), ('企業', 69), ('Google', 65), ('IT', 65), ('write', 60), ('amp', 42), ('GIGAZINE', 40), ('Facebook', 37), ('心理', 35), ('手法', 30), ('ムービー', 30), ('if','都合', 27), ('社会', 26), ('自分', 25), ('店', 25)] [('var', 334), ('id', 327), ('ITmedia', 319), ('i', 300), ('name', 253), ('if', 217), ('position', 192), ('adRequest', 192), ('著者', 170), ('document', 167), ('d', 150), ('b', 150), ('getElementById', 149), ('div', 144), ('s',42), ('return', 140), ('ISP', 138), ('span', 136), ('js', 120), ('IT', 120)]
成功 🎉 実際には、printするのではなくscikit-learnのCountVectorizerに入れてしまったり、MongoDB等に保存しておくという使い方になるかと思います。
終わりに
プラグイン等を除いた、メインの実装は200行ぐらいで済みました feedy.py 。 今回はasyncioやaiohttpで高速化を頑張ってみたのですが、それについてはまた後日記事にまとめようとおもいます。
- 作者: Ryan Mitchell,嶋田健志,黒川利明
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/03/18
- メディア: 大型本
- この商品を含むブログを見る
Pythonを使ったデータ分析に関する内容をJupyter Notebookにまとめ始めました
研究をかれこれ2年半ぐらい続けてきたので、研究をする中で必要になった機械学習の手法について調べたりコードを書いたりしてきたのですが、まだまだ触ったことのない機械学習の手法も多く、研究で必要になる手法以外の知識も付けたくなってきたので、勉強し始めました。
Sphinxにまとめるか悩んだのですが、「ひとまず簡単にスライドにできること」・「手元でもすぐにコードを実行できる」という理由でJupyter Notebookを使用しています。 もし誤りやタイポ等があれば、IssueやPRお待ちしております。
今のところ↓の2つについてまとめました。
ノートブックの内容一覧
内容については今後何度も変更をすると思いますが、とりあえず今の予定としては下記の内容について書く予定です。 次は「Seabornの使い方」と「クラスタリング」について書く予定。
データ加工(Data Wrangling)・可視化(Visualization)
- Jupyter Notebook / Numpy / Pandas / matplotlibに関する基礎知識
- 異常値・外れ値・欠損値
- Seabornを使った可視化
統計(Statistics)と機械学習(Machine Learning)
Pythonのデータ分析環境の構築 with Docker
このままだと内容紹介だけになってしまうので、Dockerを使った環境構築についてメモ。
JupyterはDockerイメージを公開してくれているので、基本的にはそちらを使えばいいと思います。この時、notebooks
ディレクトリを作成しておいて、そこを下記のコマンドのように共有しておくと終了した際にもNotebookが手元のマシンに残すことができます。
$ docker pull jupyter/datascience-notebook $ docker run -p 8888:8888 -v $PWD/notebooks:/home/jovyan/work c-bata/datascience
もし自作ライブラリなどJupyterが公開しているDockerイメージの中に含まれてないライブラリ・パッケージを使用する際には下記のように jupyterのdockerfileをベースとしたコンテナを作成しましょう。
# https://github.com/c-bata/datascience-notebook/blob/master/Dockerfile From jupyter/datascience-notebook MAINTAINER Masashi Shibata <contact@c-bata.link> USER root RUN apt-get update && \ apt-get install -y graphviz-dev graphviz && \ rm -rf /var/lib/apt/lists/* USER jovyan RUN pip install pandas-validator outlier-utils
docker buildや実行は下記のようにすればOKです。
$ docker build -t c-bata/datascience . $ docker run -p 8888:8888 -v $PWD/notebooks:/home/jovyan/work c-bata/datascience
Jupyter NotebookはWebブラウザからアクセスするので、Dockerを使う方法は相性が良いなと感じています。
VPS上でNginxも使ってJupyter Notebookを使用する
VPSの環境がある場合、わざわざ手元で実行しなくてもVPS上で実行していれば好きなときにURLにアクセスして確認することが出来ます。
Nginxの設定は↓を参考にしてください。 /api/kernels
のところを下記のように記述しないと「カーネルにつながらない」と言われ動かすことが出来ない点に注意してください。
upstream jupyter { server 127.0.0.1:8001; } server { server_name jupyter.hoge.com; location / { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_set_header Host $host; proxy_pass http://jupyter; } location /api/kernels { proxy_set_header Host $host; proxy_set_header Upgrade "websocket"; proxy_set_header Connection "Upgrade"; proxy_pass http://jupyter; } }
研究データの解析とかもここでやっておくと、教授への共有も簡単ですし、移動中でもiPad等から操作出来て良さそうです。 また運用しながら知見等が貯まれば記事にしますね。
Pythonによるデータ分析入門 第2版 ―NumPy、pandasを使ったデータ処理
- 作者: Wes McKinney,瀬戸山雅人,小林儀匡,滝口開資
- 出版社/メーカー: オライリージャパン
- 発売日: 2018/07/26
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
Golangでつくる検索エンジン(Webクローラ、MongoDB、Kagome、gin)
最近、Golangを書き始めたので勉強として、1年半ほど前にPythonで作っていたWebクローラと検索エンジンをGolangで実装してみた。WebフレームワークはFlaskの代わりにgin、Mecabの代わりにKagomeを使用、Datastoreは前回と同じくMognoDBを使ってます。
- GitHub - c-bata/gosearch: Web crawler and Search engine in Golang.
- 今回のコード
- Pythonでつくる検索エンジン(Webクローラ, Mecab, MongoDB, Flask) - c-bata web
今回はHTML書くの面倒だったので、フロントはginでJSON返すだけにしました。
以下使ったライブラリやGoに関するメモ
可変長引数の挙動
スライスを展開して渡すときは、他の引数もスライスにappendしてから展開して渡さないとToo many argumentsで落ちるみたいです。
gore> hoge := func(args ...string) { ..... for _, a := range args { ..... fmt.Println(a) ..... } ..... } (func(...string))0x2610 gore> hoge("foo", "bar") foo bar gore> hoge([]string{"foo", "bar"}...) foo bar foo bar gore> hoge("foo", []string{"bar"}...) # command-line-arguments /var/folders/nc/jkpxzpsd6459zf5qb2stk1cwnk6_vq/T/364524589/gore_session.go:31: too many arguments in call to hoge error: exit status 2 exit status 2
仕様だとは思いますが、少し面倒な気もします。
gore(GolangのREPL)の挙動
gore> :import fmt gore> fmt.Println("foo") foo (int)4 (interface {})<nil> gore> fmt.Println("bar") foo bar (int)4 (interface {})<nil> gore> fmt.Println("baz") foo bar baz (int)4 (interface {})<nil>
Goの勉強中はgoreというREPLをヨック使ってました。 標準出力に過去のprint内容が混ざっているので、最初バグかと思ったのですが、goreではそれまでに入力されてたものをすべて連結して毎回コンパイルするので実装的に仕方ないみたいです。
スライスがあるアイテムを持つかどうかの判定
pythonで言うところの 'baz' in ['foo', 'bar', 'baz']
のようなチェックをしたい場合、Golangではそういう演算子や構文が特に用意されていないみたい。少し泥臭いですが、以下のページのように対応するよりなさそうです。
analyzerについて
前回Pythonで実装したものもそうなのですが、N-gramは使わずにanalyzerは形態素解析エンジンのみです。 以前はMecabを使用していましたが、今回はPure Goな形態素解析エンジンであるKagomeを使用しました。 こちらも特にトラブルなく使えました。
GitHub - ikawaha/kagome: Self-contained Japanese Morphological Analyzer written in pure golang
Godep
GitHub - tools/godep: dependency tool for go
Goの依存関係を記述するツールとしてGodepを使いました。まだ godep save しただけなので特に問題は起きてないです。
envの扱い
MongoDBのDataBase URIとかは環境によって変更したいので、↓のようなファイルを用意して環境変数に応じて変えました。
デバッガ(delve)
delveをとりあえず入れて何度か動かしてみたんですが、まだ慣れなくてスムーズには扱えてないです。 結構assertionのレポートとかを頼りにデバッグしてます。 アサートが失敗したタイミングでカジュアルにdelveを起動できるといいのかも(ちゃんと調べてないだけです。ありそうな気もします)。
テスト
Goでは1テストメソッド複数アサーションが普通に行われるみたいです。 アサーションライブラリはtestifyを使用しました。GoConveyも良さそうだったのですが、個人的に書き方に慣れているので、今回はこれを使用しました。
GitHub - stretchr/testify: A sacred extension to the standard go testing package
// https://github.com/c-bata/gosearch/blob/master/crawler/crawl_test.go#L68 func TestRemoveTags(t *testing.T) { assert := assert.New(t) input := `<ul><li>item</li></ul>` actual := RemoveTags(input) expected := "item" assert.Equal(expected, actual) }
Webクローラについて
Webクローラ完成時のコードは↓です。これ実行すると結構スピード出るので使う際は気をつけてください。
GitHub - c-bata/gosearch at 57aa4107e247254e9de5e95590aa6b3ecd38d67d
mgo
MongoDBのドライバでは一番人気そうだったので使用。 Sessionの取得とかは この辺 参考にしてください。
GetCollection
c := Session.DB("database_name").C("collction_name")
Insert
type Index struct { ID string `bson:"_id"` Keyword string `bson:"keyword"` Url []string `bson:"url"` } c := Session.DB("test").C("index") index := &Index{"hoge", []string{"http://example.com"}} c.Insert(index)
Update
err = c.Update(bson.M{"keyword": "keyword1"}, bson.M{"$push": bson.M{"url": "http://1.example.com"}}) err = c.UpdateId(result.ID, bson.M{"$push": bson.M{"url": "http://2.example.com"}})
- Update operator(
$set
や$put
、$pop
)は基本的に使えるはず - https://docs.mongodb.org/manual/reference/operator/update-array/
UpdateAll
err = c.Update(bson.M{"keyword": "keyword1"}, bson.M{"$pushAll": bson.M{"url": []string{"http://hoge.example.com"}}}) err = c.Update(bson.M{"_id": result.ID}, bson.M{"$pushAll": bson.M{"url": []string{"http://6.example.com"}}})
Find
result := &Index{} c.Find(bson.M{"keyword": "hoge"}).One(&result)
FindAll
var results []Index c.Find(nil).All(&results) results
Goの所感など
- 並行処理周り以外ではそれなりにすぐに理解できた
- 並行処理まわりは気づいたらブロックして出力が何もないことが多かったです。
- こちら Go の並行処理 - Block Rockin’ Codes を読んで理解していきました。ありがとうございます。
- デバッガはまだ慣れなくて、ついついテストとPrintデバッグに頼っちゃってます
- 個人的に一番期待しているのはクロスコンパイルとシングルバイナリなのですが、その辺の良さが一番出るのはコマンドラインツールな気がするので、次はその辺で何か作ってみます。github.com
- 作者: Mat Ryer,鵜飼文敏,牧野聡
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/01/22
- メディア: 大型本
- この商品を含むブログ (2件) を見る