連想検索エンジン reflexa API を Python から使ってみる2
先日の連想検索エンジン reflexa API を Python から使ってみる モジュールを手直し。
使い方
reflexa_search に検索したい文字列を渡します。連想された語を Unicode 文字列で返してくるジェネレータが得られます。 for 文で回したり list で変換したりしてあげてください。
>>> from reflexa import reflexa_search >>> for s in reflexa_search(u'Python'): ... print s Monty Python ニシキヘビ (中略) factorial WideStudio PythonLabs
スクリプトとしても使用可能です。手抜き実装ですが・・・(汗
C:\>python -m reflexa "初音ミク" VOCALOID クリプトン・フューチャー・メディア (後略)
reflexa.py
# coding: utf-8 u"""『連想検索エンジン reflexa』(http://labs.preferred.jp/reflexa/) を使用する fgshun 『銀月の符号』 http://d.hatena.ne.jp/fgshun/ """ import sys import urllib2 from urllib import urlencode from string import Template from xml.parsers.expat import ParserCreate if sys.version_info[:2] >= (2, 6): import json else: import simplejson as json __all__ = ['reflexa_search', 'ReflexaError'] __version__ = u'0.3.2' __date__ = u'2009-09-19' class ReflexaError(StandardError): pass reflexa_api = Template( u'http://labs.preferred.jp/reflexa/api.php?${query}' ) def reflexa_parse_json(response): u"""Reflexa より取得した JSON データの内容を解析し1つずつ返す""" # Reflexa より得られるデータは UTF-8 でエンコードされている。 for s in json.loads(response, encoding='utf-8'): yield unicode(s) def reflexa_parse_xml(response): u"""Reflexa より取得した XML データの内容を解析し1つずつ返す""" words = [] elements = [] def char_data(data): if elements == ['result', 'words', 'word']: words.append(data) def start_element(name, attrs): elements.append(name) def end_element(name): elements.pop() parser = ParserCreate() parser.CharacterDataHandler = char_data parser.StartElementHandler = start_element parser.EndElementHandler = end_element parser.Parse(response, True) for word in words: yield word def _create_query(word, format=u'json'): u"""検索文字列 word からクエリー文字列を作成する""" # TODO: 『reflexa Web APIについて # (http://labs.preferred.jp/reflexa/about_api.html)』 # にて「空白は %20 とする」とある。 # しかし、『連想検索エンジン reflexa について # (http://labs.preferred.jp/reflexa/about.html)』 # では「apple フルーツ」の例にて空白が + になっている。 # (q=apple+%E3%83%95%E3%83%AB%E3%83%BC%E3%83%84) # 空白を %20 にする必要は無いのではないか? # api.php と search.php とは別物なので試してみないと # わからないが。 # 追記: # 「apple フルーツ」で試してみたところ、どちらでも動く。 # 要再調査。 if not format in (u'xml', u'json'): raise ValueError q = dict(q=word.encode('utf-8'), format=format.encode('utf-8')) qs = urlencode(q) #qs = qs.replace('+', '%20') return qs def _create_request(qs): u"""問合せ先 URL を作成する reflexa API テンプレートにクエリー文字列を埋め込むことで行っている。 """ url = reflexa_api.substitute(query=qs) request = urllib2.Request(url) return request def reflexa_search_raw(word, opener=None, format=u'json'): u"""検索文字列 word を 『連想検索エンジン reflexa』 で検索する 戻り値は reflexa より得られた生のデータで、 JSON もしくは XML フォーマットのテキスト。 Reflexa へのアクセス失敗時には ReflexaError が送出される。 word: 検索文字列、もしくは検索文字列を含むシーケンス opener: urllib2.OpenerDirector オブジェクト。 またはその類似オブジェクト。 もしくは None 。 None の場合 urllib2.build_opener() で オブジェクトを作成する。 format: 受信フォーマットの指定 [u'json' or u'xml'] """ if not opener: opener = urllib2.build_opener() if not format in (u'json', u'xml'): raise ValueError qs = _create_query(word, format) request = _create_request(qs) try: reader = opener.open(request) response = reader.read() except urllib2.URLError, e: raise ReflexaError(e) return response def reflexa_search(word, opener=None): u"""検索文字列 word を 『連想検索エンジン reflexa』 で検索する 連想された語をひとつずつ返す。 word が文字列でないならば、文字列を含むシーケンスであることを 期待して u' '.join で連結を試みる。 Reflexa へのアクセス失敗時には ReflexaError が送出される。 word: 検索文字列、もしくは検索文字列を含むシーケンス opener: urllib2.OpenerDirector オブジェクト。 またはその類似オブジェクト。 もしくは None 。 None の場合 urllib2.build_opener() で オブジェクトを作成する。 """ if not opener: opener = urllib2.build_opener() if isinstance(word, basestring): pass else: word = u' '.join(word) response = reflexa_search_raw(word, opener, u'json') for s in reflexa_parse_json(response): yield s def main(): words = sys.argv[1:] coding = sys.getdefaultencoding() opener = urllib2.build_opener() for word in words: word = unicode(word, coding) if len(words) > 1: print (u'--- %s ---' % word).encode(coding, 'backslashreplace') try: for s in reflexa_search(word, opener=opener): # 'strict' はまずい。 # 'u\017d'(Zにハーチェクを付した文字) # とか届いたことが。 print s.encode(coding, 'backslashreplace') except ReflexaError, e: m = u'Reflexa にアクセスできませんでした'.encode(coding) print >> sys.stderr, m if __debug__: print >> sys.stderr, e.message if __name__ == '__main__': main()
変更点
- json を解析するためにつかうモジュールを demjson から simplejson に変更しました。このモジュールは easy_install で取ってこれるのですが、 C のコンパイル環境の無い Windows ではインストールに失敗してしまいます。この場合はソースコードをダウンロードしてきて python setup.py --without-speedups build とします。C モジュール部分を欠くと本来の速度はでませんが動作に問題は無いそうです。また Python 2.6 より json として標準モジュール入りするので将来は準備不要となります。モジュールインポートするときの if sys.version_info[:2] >= (2, 6): のイディオムはuasiの日記風より。ありがとうございます。
- xml の解析には BeautifulSoup を使用していましたが、標準モジュール xml.sax.expat に変更しました。解析対象が簡単な XML なので BeautifulSoup の力を借りなくても無事、読めました。
- reflexa_search, reflexa_search_raw 関数が reflexa へのアクセスを失敗した際に ReflexaError を送出するようにしました。