後の人のために残します。
- Python3の資料が少ない
- Bottleの日本語資料も少ない
以上の理由で、無駄なところで苦労しました……。
目次
- この組み合わせの理由
- Custom searchの例
- Custom Searchを使って検索する部分
- Webアプリケーションとしてのレスポンスを返す部分
- 完成
- 感想
- ソースコード
この組み合わせの理由
Python3の理由
- まず研究にPythonを使うことに決めた。Numpy, ScipyのあるPythonは情報検索の研究に向いている。
- Python3はデフォルトのエンコーディングがutf-8なので、Python2より日本語を扱いやすい。
Bottleの理由
- Webの研究である以上、Webアプリケーションとして実装するべきだと考えた。
- DBは使わない。なのでマイクロフレームワークが最適。
- PythonのマイクロフレームワークとしてFlaskとBottleが有名。だがFlaskはPython3に対応していない。(2013年5月23日現在。もうじき対応するかも)追記:2013年11月29日現在、FlaskはPython3に対応しています!! いぇい!
Custom Searchの例
Custom Search APIを使って検索するサンプルコードはこちらの記事を参考。
http://qiita.com/items/92febaf8bbea541b1e36
ただし、例はPython2系列のコードとなっているので、いくつか修正する必要がある。
Custom Searchを使って検索する部分
実際に検索結果のjsonを取ってくる処理をgooglesearch.pyのsimple_search(query)という関数として定義する。
import urllib
import urllib.request
import urllib.parse
import json
def simple_search(query):
QUERY = query
API_KEY = 'AIzaSyBdhBWUc5W3Aco3YGPwOlS_rYM0LENl_jo'
NUM = 1
url = 'https://www.googleapis.com/customsearch/v1?'
params = {
'key': API_KEY,
'q': QUERY,
'cx': '013036536707430787589:_pqjad5hr1a',
'alt': 'json',
'lr': 'lang_ja', }
start = 1
for i in range(0, NUM):
params['start'] = start
request_url = url + urllib.parse.urlencode(params)
try:
response = urllib.request.urlopen(request_url)
json_body = json.loads(response.read().decode('utf-8'))
items = json_body['items']
except:
print('Error')
return items
このファイルをapp.pyと同じディレクトリに入れる。
なお、このコードでは1ページ分、つまり検索結果10件分だけ取っている。もっと取りたい場合はparams['start']の値を変えてforループを回す。
注意点
Python3での注意。いくつかの標準ライブラリがPython2から変わっている。
- urllib.urlopen(url)はurllib.request.urlopen(url)になった。
- urllib.urlencode(params)はurllib.parse.urlencode(params)になった。
また、API_KEYは自分のものを使うこと。
https://code.google.com/apis/console/ で自分のGoogleアカウントでのAPIキーを取得できる。
Webアプリケーションとしてのレスポンスを返す部分
Bottleはapp.pyに、URLとhttpリクエストに対応する処理、つまりコントローラのコードを書く。
今回、view部分に使うテンプレートはMakoを使った。Makoで使うテンプレートはstatic/templatesディレクトリに入れておく。
from bottle import Bottle, route, run, static_file, request
from mako.template import Template
import googlesearch
import pdb
template = Template(filename='static/templates/index.tmpl')
app = Bottle()
@route('/static/:path#.+#', name='static')
def static(path):
return static_file(path, root='static')
@route('/results')
def results_get():
return template.render(items='')
@route('/results', method='POST')
def results():
query = request.forms.decode().get('query')
items = googlesearch.simple_search(query)
return template.render(items=items)
@route('/')
def greet():
return template.render(items='')
run(host='localhost', port=1234, debug=True)
ポイントは、@route('/results', method='POST')の関数で、
query = request.forms.decode().get('query')
とすること。日本語で検索するならdecode()が必要。
このやりかたはStackoverflowで見つけたのだが、日本語の資料が少ないとこういう処理をするときにマニュアルの英語を読んだりStackoverflowを探したりで無駄に手間がかかってしまうのが悲しい……。英語圏のプログラマはマルチバイト文字のことなんて考えないしもうつらい。
@route('/results', method='POST')の関数で検索結果を表示する。
POSTでなくGETメソッドで検索する方法もあると思うが、やりかたがわからないしもうPOSTでいいかなと感じている。
で、view側。
#coding: utf-8
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Subtask Search</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Loading Bootstrap -->
<link href="static/css/bootstrap.css" rel="stylesheet">
<!-- Loading Flat UI -->
<link href="static/css/flat-ui.css" rel="stylesheet">
<link rel="shortcut icon" href="static/images/favicon.ico">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements. All other JS at the end of file. -->
<!--[if lt IE 9]>
<script src="static/js/html5shiv.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<div class="demo-headline">
<a href="/">
<h1 class="demo-logo">
Subtask Search
</h1>
</a>
</div> <!-- /demo-headline -->
<div class="span4 offset4">
<form action="/results" method="post">
<input type="text" name="query" value placeholder="Input your task" class="span4 offset4" />
<input type="submit" value="Search" />
</form>
</div>
<div class="span8 offset2">
<ul class="unstyled">
% for item in items:
<li>
<a href= ${item['link']}>
${item['title']}
</a>
</li>
% endfor
</ul>
</div>
</div> <!-- /container -->
<!-- Load JS here for greater good =============================-->
<script src="static/js/jquery-1.8.2.min.js"></script>
<script src="static/js/jquery-ui-1.10.0.custom.min.js"></script>
<script src="static/js/jquery.dropkick-1.0.0.js"></script>
<script src="static/js/custom_checkbox_and_radio.js"></script>
<script src="static/js/custom_radio.js"></script>
<script src="static/js/jquery.tagsinput.js"></script>
<script src="static/js/bootstrap-tooltip.js"></script>
<script src="static/js/jquery.placeholder.js"></script>
<script src="http://vjs.zencdn.net/c/video.js"></script>
<script src="static/js/application.js"></script>
<!--[if lt IE 8]>
<script src="static/js/icon-font-ie7.js"></script>
<script src="static/js/icon-font-ie7-24.js"></script>
<![endif]-->
</body>
</html>
このindex.tmplにはFlat UIを使ってきれいな見栄えにした。cssとjsはFlat UIからダウンロードしてきてstaticディレクトリに入れている。
Makoが働いているのは
% for item in items:
<li>
<a href= ${item['link']}>
${item['title']}
</a>
</li>
% endfor
の部分のみである。app.pyの
return template.render(items=items)
で、simple_searchの結果を入れたitemsをMakoのitemsに入れて、このviewをレンダリングしている。
完成
感想
マルチバイト文字の処理つらい。Python3になって楽になったはずなのにまだつらい。日本語の資料が少ないの、もっとつらい。みんなもっとノウハウ共有してほしい。
ソースコード
https://github.com/katryo/google_simple_search
にアプリケーションまるごと置いておきました。どうぞ見ていってください。