「RSSぅ? アレだろ、ブログの新着記事一覧が表示されるツールみたいなやつ」

違った。表示されるツールはRSSリーダであって、RSSはRSSリーダが読む文書のフォーマットのことだ。RSSは各ブログ、サイトが配信しており、たとえばうちのRSSは http://guild-elf.jugem.jp/?mode=rss から見ることができる。し、知らなかった。ともかくこれは特定のフォーマットに従って書かれておるので、それをパース(整理)することで新着記事の情報を取得できるって寸法だ。今回作ったリーダの機能はこんな感じ。

  • 拾ってきたRSSをjson形式でDB保存し、キャッシュとする
  • 前回のキャッシュが6時間ならそのキャッシュを使う
  • 前回のキャッシュが6時間以上前なら新たにRSSを拾ってきて保存する
  • キャッシュからhtmlを作って新着記事のリストをブラウザで表示する

実行すると、バシッとブラウザが開いてこんな風に表示されるよ。今回はとくに咬まれたところはなかったんで、気楽に、スクリプトのスニペットを並べていく。

DB接続の仕方。俺はsqliteが好きなのでそれを使ってるぜ。しかもpythonにはデフォルトでsqlite3に接続するモジュールがあるのだ。使うっきゃないだろ。

import sqlite3
conn   = sqlite3.connect("example.sqlite3")
cursor = conn.cursor()
query  = "SELECT name,sex FROM tableName WHERE id=?;"
cursor.execute(query, (1,))
trash  = cursor.fetchall()
cursor.close()
conn.close()

executeメソッド実行の行で値のバインドを行っているが、バインドする項目が1つでもきちんとタプル化しないといけないのが注意すべき点だ。じつは (1) はタプルにはならないんだよな。(1,) こうしないとタプルにはならない。オライリーを読んでいなければ詰まってたと思う。

このモジュールには気に食わない点があって、SELECTクエリで返ってくるリストにはカラム名の情報が含まれていないのだよ。だからいちいち手動でディクショナリ化しないとカラム名で値を取り出すということができない。そのくらい処理オプションでつけてくれてもんじゃないの、pythonの旦那? つーわけで俺は以下のような関数をよく使ってる。

# cursor.fetchall() で返ってくるリスト
trash = [('なまえ', 'せいべつ')]

def assoc(trash, columns):
    rows = []
    for i in range(len(trash)):
        rows.append({})
        for j in range(len(trash[i])):
            rows[i][columns[j]] = trash[i][j]
    return rows

# 結果(ディクショナリ化したリスト)
print(assoc(trash, ["name", "sex"]))
# [{'name': 'なまえ', 'sex': 'せいべつ'}]

「今の時間が前の時間と何秒差か」求める方法。

import datetime
timeNow    = datetime.datetime.today()
timeBefore = datetime.datetime(2016, 3, 1, 0, 0)    # 2016.3.1. 00:00
delta      = round((timeNow - timeBefore).total_seconds())

年、月、日、時、秒の順番でdatetime.datetime()メソッドに渡すと、その時間のオブジェクトを作ってくれるのだ。

本日の主役feedparserの使い方。

import feedparser
original = feedparser.parse("http://guild-elf.jugem.jp/?mode=rss")
cacheDic = {}
cacheDic["siteTitle"]  = original.feed.title    # サイトのタイトル
cacheDic["siteUrl"]    = original.feed.link     # サイトのurl
cacheDic["articleNum"] = len(original.entries)
for i in range(len(original.entries)):          # エントリー配列が入った配列
    entry = original.entries[i]
    date  = entry.updated_parsed                # 更新日付
    month = "{0:02d}".format(date.tm_mon)       # 更新日付から月を取り出す
    day   = "{0:02d}".format(date.tm_mday)      # 更新日付から日を取り出す
    cacheDic["article" + str(i)] = {
        "title":entry.title,                    # エントリのタイトル
        "url"  :entry.link,                     # エントリのurl
        "date" :"%s/%s/%s" % (date.tm_year, month, day)
        }

いや何がステキって、feedparserって読むRSSがRSS1.0だろーがRSS2.0だろーがAtomだろーが同じ形式の配列にパースしてくれるんだよ。おかげでパース前にそのRSSがどの書式なのか判別する作業をこちらでやらずに済む。

最後に、影の主役webbrowserの使い方。

import webbrowser, os
realPath = os.path.realpath("html/example.html")
webbrowser.open(realPath)

これはローカルのhtmlファイルを開く方法だ。いやローカルのファイルパスなんて手打ちするわいって思うかもしんないが、os.path.realpath()通すと確実ですよ。それと、これはマジで面白いと思ったことなんだけど、webbrowser.open()ってさ、ブラウザを開く関数じゃないんだよ。具体的には、指定したパスのファイルを、個人で設定してる規定のプログラムで開く関数なのだよ。俺の環境だと.txtファイルのパスを与えたらsublime text3が開くし、.sqlite3ファイルのパスを与えたらDB Browser for SQLiteが開くってわけ。何を投げようとブラウザで開く機能なんだと思ってたから、.htmlファイル投げてsublime text3が開いたときはびっくりしたよ(俺はhtmlファイルはsublime text3と紐づけてる)。 これは絶対、ほかのとこでも使えるぜ。…このモジュールにwebbrowserという名前をつけた奴は何を考えてんだ、ほんで。