青空文庫API(アルファ版)を改良してみた
昨日公開した青空文庫API(アルファ版)ですが、id:hyukix あたりに「作品データがまったく取れないじゃないか!」と怒られそうな気がするので、作品データや青空文庫作品ページ等々の情報を吐き出すように修正してみました。
- API的なアレはこちらです
http://plasticscafe.sakura.ne.jp/aozora/
青空文庫データまるごと公開
ちなみにパースした青空文庫情報(SQLite)は下記URLからダウンロード出来るようにしてみました。そこら辺の情報を使って何かやりたいんだけど、パースすんのはめんどいデス( ー`дー´)キリッ、という一ヶ月前の俺みたいな人はご自由にどうぞー
実装小話
前回のエントリーで載せたインチキ実装コードでは、ランキングの処理結果のみデータ更新時にキャッシュファイルを生成していますデス。ランキングはカウントやらJOINやらで重たいSQLになる反面、検索結果が変わらないという性質なのでキャッシュしてみることにしてみました。
当初は毎回クエリー発行してたのですが、実際のところ1クエリー1分程度という泣きたくなるような速度だったのでしょうがなく…でもサービス提供系の実装を考えれば結果の変わらない処理は値を保持しておいたほうが効率的だよな…と思うようになったのでいいきっかけでしたとさ…まあそもそものSQLの作り方がアホという可能性は大ですが(´・ω・`)
他の検索なんかもある程度値をキャッシュするように考えていきたいところですが…まあ、時間があったらというところで。
公開周りの処理
前回はパーサー系の処理を載せたので、今回は公開系の処理をばデロデロと載せてみます。実際にはApache CGI de Python(造語)から下記クラスをインポートして利用している感じです…Python的にアウトな部分や、そもそもこの書き方おかしい的箇所があればお知らせいただければ…多そう(´;ω;`)なのでちょっとgkbrですが
#!/usr/bin/python # -*- coding: utf-8 -*- import sqlite3 import json import re import os from xml.dom import minidom class AozoraAPI: def __init__(self, work_dir=None): if work_dir == None: work_dir = os.path.dirname(os.path.abspath(__file__)) + '/' self.work_dir = work_dir self.con = sqlite3.connect(work_dir + 'db/aozora.db') self.file_url = 'http://www.aozora.gr.jp/cards/' # execute ranking sql def ranking(self, category=None): cache_dir = self.work_dir + 'cache/' if category == 'html': f = open(cache_dir + 'ranking_html.json') elif category == 'text': f = open(cache_dir + 'ranking_text.json') else: f = open(cache_dir + 'ranking.json') return f.read() # create artists search sql def artists(self, keyword=None): sql =""" SELECT id, name, url FROM artists WHERE status != 2 """ if keyword == None: return self.con.execute(sql).fetchall() else: search_sql = """ AND (name LIKE '%s' OR name LIKE '%s' OR kana LIKE '%s' OR kana LIKE '%s' ) """ sql = sql + search_sql keyword_katakana = '%' + self.convertKana(keyword) + '%' keyword = '%' + keyword + '%' rows = self.con.execute(sql % (keyword, keyword_katakana, keyword, keyword_katakana)).fetchall() # format records results = [] for row in rows: results.append({ 'artist_id':row[0], 'artist_name':row[1], 'url':row[2] }) return results # execute artist get sql def artist(self, id=None): if id != None: sql =""" SELECT id, name, url FROM artists WHERE status != 2 AND id = '%s' """ rows = self.con.execute(sql % (id)).fetchall() # format records results = [] for row in rows: results.append({ 'artist_id':row[0], 'artist_name':row[1], 'url':row[2] }) return results return None # execute articles search sql def articles(self, keyword=None): sql =""" SELECT ac.id, ac.name, ats.id, ats.name, ac.url, ac.zip_url, ac.html_url FROM articles AS ac LEFT JOIN artists AS ats ON ac.artist_id = ats.id WHERE ac.status != 2 """ if keyword == None: return self.con.execute(sql).fetchall() else: search_sql = """ AND (ac.name LIKE '%s' OR ac.name LIKE '%s' OR ac.kana LIKE '%s' OR ac.kana LIKE '%s') """ sql = sql + search_sql keyword_katakana = '%' + self.convertKana(keyword) + '%' keyword = '%' + keyword + '%' rows = self.con.execute(sql % (keyword, keyword_katakana, keyword, keyword_katakana)).fetchall() # format records results = [] for row in rows: results.append({ 'article_id':row[0], 'article_name':row[1], 'artist_id':row[2], 'artist_name':row[3], 'url':row[4], 'zip':self.file_url + row[2] + '/' + row[5], 'html':self.file_url + row[2] + '/' + row[6] }) return results # execute article get sql def article(self, id=None): if id == None: sql =""" SELECT ac.id, ac.name, ats.id, ats.name, ac.url, ac.zip_url, ac.html_url FROM articles AS ac LEFT JOIN artists AS ats ON ac.artist_id = ats.id WHERE ac.status != 2 AND ac.id = '%s' """ rows = self.con.execute(sql % (id)).fetchall() # format records results = [] for row in rows: results.append({ 'article_id':row[0], 'article_name':row[1], 'artist_id':row[2], 'artist_name':row[3], 'url':row[4], 'zip':self.file_url + row[2] + '/' + row[5], 'html':self.file_url + row[2] + '/' + row[6] }) return results return None # execute article get sql def articlesFromArtist(self, id=None): if id == None: sql =""" SELECT ac.id, ac.name, ats.id, ats.name, ac.url, ac.zip_url, ac.html_url FROM articles AS ac LEFT JOIN artists AS ats ON ac.artist_id = ats.id WHERE ac.status != 2 AND ats.id = '%s' """ rows = self.con.execute(sql % (id)).fetchall() # format records results = [] for row in rows: results.append({ 'article_id':row[0], 'article_name':row[1], 'artist_id':row[2], 'artist_name':row[3], 'url':row[4], 'zip':self.file_url + row[2] + '/' + row[5], 'html':self.file_url + row[2] + '/' + row[6] }) return results return None # execute all search sql def search(self, keyword=None): sql =""" SELECT ac.id, ac.name, ats.id, ats.name, ac.url, ac.zip_url, ac.html_url FROM articles AS ac LEFT JOIN artists AS ats ON ac.artist_id = ats.id WHERE ac.status != 2 """ if keyword == None: return self.con.execute(sql).fetchall() else: search_sql = """ AND (ac.name LIKE '%s' OR ac.name LIKE '%s' OR ac.kana LIKE '%s' OR ac.kana LIKE '%s' OR ats.name LIKE '%s' OR ats.name LIKE '%s' OR ats.kana LIKE '%s' OR ats.kana LIKE '%s') """ sql = sql + search_sql keyword_katakana = '%' + self.convertKana(keyword) + '%' keyword = '%' + keyword + '%' rows = self.con.execute(sql % (keyword, keyword_katakana, keyword, keyword_katakana, keyword, keyword_katakana, keyword, keyword_katakana)).fetchall() # format records results = [] for row in rows: results.append({ 'article_id':row[0], 'article_name':row[1], 'artist_id':row[2], 'artist_name':row[3], 'url':row[4], 'zip':self.file_url + row[2] + '/' + row[5], 'html':self.file_url + row[2] + '/' + row[6] }) return results # execute newly articles def newly(self, limit=None): if limit == None: limit = '100' sql =""" SELECT ac.id, ac.name, ats.id, ats.name, ac.public_date, ac.url, ac.zip_url, ac.html_url FROM articles AS ac LEFT JOIN artists AS ats ON ac.artist_id = ats.id WHERE ac.status != 2 ORDER BY ac.public_date DESC LIMIT '%s' """ rows = self.con.execute(sql % (limit)).fetchall() # format records results = [] for row in rows: results.append({ 'article_id':row[0], 'article_name':row[1], 'artist_id':row[2], 'artist_name':row[3], 'public_time':row[4], 'url':row[5], 'zip':self.file_url + row[2] + '/' + row[6], 'html':self.file_url + row[2] + '/' + row[7] }) return results # execute api command def api(self, command=None, param=None, display='plain'): if command == None: print('Content-type: text/plain; charset=UTF-8') print('') return False elif command == 'ranking': data = self.ranking(param) if display == 'json': print('Content-type: text/javascript; charset=UTF-8') print('') #print(json.dumps(data)) print(data) return True elif display == 'plain': print('Content-type: text/plain; charset=UTF-8') print('') print(json.loads(data)) return True elif command == 'artists': data = self.artists(param) elif command == 'artist': data = self.artist(param) elif command == 'articles': data = self.articles(param) elif command == 'article': data = self.article(param) elif command == 'articlesFromArtist': data = self.articlesFromArtist(param) elif command == 'search': data = self.search(param) elif command == 'newly': data = self.newly(param) else: data = False if display == 'plain': self.plain(data) elif display == 'json': self.json(data) #elif display == 'xml': # self.xml(data) return True # display json def json(self, data): print('Content-type: text/javascript; charset=UTF-8') print('') print(json.dumps(data)) # display plain text def plain(self, data): print(data) ################################################## # Utility # Convert String def convertKana(self, text): # dictionary of kana kana = { 'ア':'あ', 'イ':'い', 'ウ':'う', 'エ':'え', 'オ':'お', 'カ':'か', 'キ':'き', 'ク':'く', 'ケ':'け', 'コ':'こ', 'サ':'さ', 'シ':'し', 'ス':'す', 'セ':'せ', 'ソ':'そ', 'タ':'た', 'チ':'ち', 'ツ':'つ', 'テ':'て', 'ト':'と', 'ナ':'な', 'ニ':'に', 'ヌ':'ぬ', 'ネ':'ね', 'ノ':'の', 'ハ':'は', 'ヒ':'ひ', 'フ':'ふ', 'ヘ':'へ', 'ホ':'ほ', 'マ':'ま', 'ミ':'み', 'ム':'む', 'メ':'め', 'モ':'も', 'ヤ':'や', 'ユ':'ゆ', 'ヨ':'よ', 'ラ':'ら', 'リ':'り', 'ル':'る', 'レ':'れ', 'ロ':'ろ', 'ワ':'わ', 'ヲ':'を', 'ン':'ん', 'ガ':'が', 'ギ':'ぎ', 'グ':'ぐ', 'ゲ':'げ', 'ゴ':'ご', 'ザ':'ざ', 'ジ':'じ', 'ズ':'ず', 'ゼ':'ぜ', 'ゾ':'ぞ', 'ダ':'だ', 'ヂ':'ぢ', 'ヅ':'づ', 'デ':'で', 'ド':'ど', 'バ':'ば', 'ビ':'び', 'ブ':'ぶ', 'ベ':'べ', 'ボ':'ぼ', 'パ':'ぱ', 'ピ':'ぴ', 'プ':'ぷ', 'ペ':'ぺ', 'ポ':'ぽ', 'ァ':'ぁ', 'ィ':'ぃ', 'ゥ':'ぅ', 'ェ':'ぇ', 'ォ':'ぉ', 'ャ':'ゃ', 'ュ':'ゅ', 'ョ':'ょ', 'ヴ':'ゔ', 'ッ':'っ', 'ヰ':'ゐ', 'ヱ':'ゑ', } re_convert = re.compile("|".join(map(re.escape, kana))) return re_convert.sub(lambda x:kana[x.group(0)], text) #################################################### # define main function def main(): ### テストコード import sys import codecs sys.stdout = codecs.getwriter('utf_8')(sys.stdout) aozora = AozoraAPI() aozora.api(None, None, 'json') # when exexute this script if __name__ == "__main__": main()