2011年2月27日日曜日

GAEの全文検索

http://www.ianlewis.org/jp/gae-hackathon-disc-3-jp
このページにGAEで全文検索する為のコードが紹介されている。全文検索は今僕は不要だが、とりあえずメモっておこう。すごく詳しそうな人のページだ。

DatastoreのTips

http://d.hatena.ne.jp/kazunori_279/20090617/1245212016
このページにDatastoreのTipsが色々書いてある。読んで覚えよう。
ところで、DataStoreで部分一致検索をしたいんだけど、どうすればいいんだろう?今情報探し中。

GAEの検索パターン

http://d.hatena.ne.jp/knj77/20100313/1268447499
このサイトに、GAEの検索方法について、書いてあったのでメモ。GAEは複雑な検索をするにはインデックスを作らないといけないけど、場合によっては全てをインデックス作成するのは非現実なことがあります。その場合の問題回避方法として最も優れた方法だと思われていることが書いてあります。

2011年2月26日土曜日

Djangoでチェックボックスの複数の値を読み取る

セレクトボックスや、チェックボックスで複数チェックされてリクエストされた場合に、チェックされた全ての値を読み取るには、

request.POST.getlist(キー)

とやる。キーに対応する全ての値がリストで貰える。

その他、Djangoのリクエストオブジェクトを参照。

GAEの開発サーバのデータを消去する

dev_appserver.py --clear_datastore {{app名}}
でいいらしい。

2011年2月22日火曜日

GAEの開発サーバのDashboardをみる

今まで知らなかった。超便利だ。
http://localhost:8000/_ah/admin/datastore

GAEのbulk loaderツールを使って駅データのCSVをデータストアにアップロードする

駅データのcsvファイルをGAEのデータストアにアップロードしたい。

csv等をデータストアに一括アップロードする場合、bulk loaderツールを使う。bulk loaderはappcfg.pyコマンドから使用できる。bulk loaderツールは、remote_apiを使用してGAEアプリのデータストアに一括アップロードする。

remote_apiが設定されていない場合は、まず最初に設定する必要がある。 http://bit.ly/9YoNpV に2種類の設定方法が書いてある。

remote_apiの設定が終わったら、次にbulk loaderの設定をする必要がある。bulk loaderの設定をするには、まずbulkloader.yamlを自動生成する。

appcfg.py create_bulkloader_config --filename=bulkloader.yaml {directory}
※{directory}はアップロードしたいアプリのディレクトリ名を入れる。

これによって、bulkloader.yamlが自動生成されるので、生成されたファイルを開き、編集する。
但し編集の前に、データの仕様を決めておく必要がある。僕はDjangoパッチを使っているので、models.pyに下記クラスを記入した。駅データのCSVを開いて、各カラム名とその要素のデータ仕様に合わせたもの。

class Station(db.Model):
    rr_cd = db.IntegerProperty()
    line_cd = db.IntegerProperty()
    station_cd = db.IntegerProperty()
    line_sort = db.IntegerProperty()
    station_sort = db.IntegerProperty()
    station_g_cd = db.IntegerProperty()
    r_type = db.IntegerProperty()
    rr_name = db.StringProperty()
    line_name = db.StringProperty()
    station_name = db.StringProperty()
    pref_cd = db.IntegerProperty()
    lon = db.FloatProperty()
    lat = db.FloatProperty()
    f_flag = db.IntegerProperty()

上記データ仕様に合わせて、bulkloader.yamlを編集する。編集方法は、 http://bit.ly/9YoNpV に詳細が書いてあるが、編集結果は下記のとおり(編集したのは、transformers:以下の行)。

# Autogenerated bulkloader.yaml file.
# You must edit this file before using it. TODO: Remove this line when done.
# At a minimum address the items marked with TODO:
#  * Fill in connector and connector_options
#  * Review the property_map.
#    - Ensure the 'external_name' matches the name of your CSV column,
#      XML tag, etc.
#    - Check that __key__ property is what you want. Its value will become
#      the key name on import, and on export the value will be the Key
#      object.  If you would like automatic key generation on import and
#      omitting the key on export, you can remove the entire __key__
#      property from the property map.

# If you have module(s) with your model classes, add them here. Also
# change the kind properties to model_class.
python_preamble:
- import: base64
- import: re
- import: google.appengine.ext.bulkload.transform
- import: google.appengine.ext.bulkload.bulkloader_wizard
- import: google.appengine.ext.db
- import: google.appengine.api.datastore
- import: google.appengine.api.users

transformers:

- kind: Station
  connector: csv
  connector_options:
    encoding: euc-jp
  property_map:
    - property: rr_cd
      external_name: rr_cd
      import_transform: int

    - property: line_cd
      external_name: line_cd
      import_transform: int

    - property: station_cd
      external_name: station_cd
      import_transform: int

    - property: line_sort
      external_name: line_sort
      import_transform: int

    - property: station_sort
      external_name: station_sort
      import_transform: int

    - property: station_g_cd
      external_name: station_g_cd
      import_transform: int

    - property: r_type
      external_name: r_type
      import_transform: int

    - property: rr_name
      external_name: rr_name

    - property: line_name
      external_name: line_name

    - property: station_name
      external_name: station_name

    - property: pref_cd
      external_name: pref_cd
      import_transform: int

    - property: lon
      external_name: lon
      import_transform: float

    - property: lat
      external_name: lat
      import_transform: float
    
    - property: f_flag
      external_name: f_flag
      import_transform: int

編集したら、下記コマンドを打って、アップロードする。

appcfg.py upload_data --config_file=bulkloader.yaml --filename=m_station.csv --kind=base_station {directory}

追記2011/02/24:--kind=Stationをbase_staitionに修正した。Djangoの場合特有かもしれないが、baseという名のapp内のmodels.pyでStationクラスを作った場合、kindは、base_stationになる。これ知らなかった。


※{directory}はアップロードしたいアプリのディレクトリ名を入れる。
※m_station.csvは、csvファイルの名前にする。これは駅データからダウンロードしたcsvのファイル名。
これで、アップロード完了。下記のように、10745データを9分位かけてアップロードした。

[INFO    ] 10745 entities (10684126 bytes) transferred in 535.9 seconds
[INFO    ] All entities successfully transferred

ちなみに、ローカルの開発サーバにアップロードするコマンドは下記。

appcfg.py upload_data --config_file=bulkloader.yaml --filename=m_station.csv --kind=base_station --url=http://localhost:8000/remote_api {directory}

追記2011/02/24:--kind=Stationをbase_staitionに修正した。Djangoの場合特有かもしれないが、baseという名のapp内のmodels.pyでStationクラスを作った場合、kindは、base_stationになる。これ知らなかった。



開発サーバへのアップロードの場合は、10745データを3.5分位でアップロードした。

[INFO    ] 10745 entities (4096563 bytes) transferred in 211.9 seconds

2011年2月21日月曜日

JQueryで駅データ取得

HeartRails ExpressのAPIを使って、JQueryで駅データ取得するスクリプト。しかし、GAEで駅データベースをしっかり作ろうとした場合、APIに逐一アクセスするのはかなり遅くなりそうでいやだし、駅名とか県名とか路線名そのものでアクセスや検索をするのは、かなり非効率そうだから、HeartRails Expressよりも駅データjpのがよいと思った。

<h3>駅データの取得</h3>

<script type="text/javascript">
$(function(){
    $(window).load(function () {get_ken();});
    $('#ken').change(function(){get_line();});
    $('#line').change(function(){get_station();});
});

function get_ken() {
    $.ajax({
        dataType: "jsonp",
        data: {"method":'getPrefectures'},
        url: "http://express.heartrails.com/api/json",
        success: function (data) {
            $.each(data.response.prefecture, function(idx,ken_name){
                $("<option/>")
                   .attr("value", ken_name)
                   .append(ken_name)
                   .appendTo("#ken"); 
            });
        }
    });
}
function get_line(){
    var ken = $("#ken option:selected").text();
    $.ajax({
        dataType: 'jsonp',
        data:{'prefecture':ken},
        url:'http://express.heartrails.com/api/json?method=getLines',
        success: function(data){
            $('#line option').remove();
            $.each(data.response.line, function(idx,line_name){
                $("<option/>")
                    .attr("value", line_name)
                    .append(line_name)
                    .appendTo("#line");
            });
        }
    }); 
}
function get_station(){
    var line = $("#line option:selected").text();
    $.ajax({
        dataType: 'jsonp',
        data:{'line':line},
        url:'http://express.heartrails.com/api/json?method=getStations',
        success: function(data){
            $('#station option').remove();
            $.each(data.response.station, function(idx,station_info){
                $("<option/>")
                    .attr("value", station_info.name)
                    .append(station_info.name)
                    .appendTo("#station");
            });
        }
    }); 
}
</script>

<select id="ken"></select>
<select id="line"></select>
<select id="station"></select>

駅データの取得


2011年2月20日日曜日

JQueryで県データ取得

HeartRails Expressで駅データをAjaxで取得・表示したい。まずは、県データを取得・表示するコード。

<h3>駅データの取得</h3>
<script type="text/javascript">
$(function(){
    $(window).load(function () {get_ken();});
});

function get_ken() {
    $.ajax({
        dataType: "jsonp",
        data: {"method":'getPrefectures'},
        url: "http://express.heartrails.com/api/json",
        success: function (data) {
            $.each(data.response.prefecture, function(idx,ken_name){
             $("<option/>")
                .append(ken_name)
                .appendTo("#station"); 
            });
        }
    });
}
</script>

<select id="station"></select>

駅データの取得


2011年2月15日火曜日

Gaeデータストア エンティティの削除

データ削除の方法は下記の2通り。

q = db.GqlQuery("SELECT * FROM Message WHERE create_date < :1", earliest_date)
results = q.fetch(10)
for result in results:
  result.delete()

# or...

q = db.GqlQuery("SELECT * FROM Message WHERE create_date < :1", earliest_date)
results = q.fetch(10)
db.delete(results)

2011年2月14日月曜日

GAE データストアのStringPropertyとTextProperty

久しぶりだったので勘違いしてた。

class StringProperty(verbose_name=None, multiline=False, ...) 
StringPropertyは、短い文字列。500 バイト以下の Python str または unicode(basestring)値を使用します。StringProperty プロパティ値はインデックス化され、フィルタや並び替え順序に使用することができます。

multiline が False の場合、値に改行文字を含めることはできません。djangoforms ライブラリはこれを使って、データ モデル内のテキスト フィールドとテキストエリア フィールドの違いを強化します。他のライブラリもこれを同じ目的のために使用することができます。

class TextProperty()
一方、TextPropertyは、長い文字列。StringProperty と異なり、TextProperty 値は 500 バイトより長くすることができます。ただし、TextProperty 値はインデックス化されず、フィルタや並び替え順序に使用することはできません。

TextProperty 値は、テキスト エンコードを使ってテキストを保存します。バイナリ データの場合は、BlobProperty を使用します。

ということで、TextPropertyはgqlで検索とか出来ないのだった。

2011年2月13日日曜日

GAEのデータストアでKeyからインスタンスを取り出す

Keyからインスタンスを取り出すときは、単純だ。

entity.put()
key = entity.key()

# ...

entity = db.get(key)

Keyは強いんだな。

GAEのデータストアにはユニコード文字しか入らない

よって、u'*****'になっていないときは、asciなんとかなどというエラーが出るので、unicode変換してからデータストアに入れる。

例)
author = '******' #ユニコード文字じゃない
unicode(author,"utf-8")

ちなみに、unicode(author,"utf-8")とやったときに、authorに文字列が入っていない場合(Noneなど)は、変換出来ないのでエラーになる。

BeautifulSoupの挙動が謎(GAE上でPythonでAmazonAPIを使う)

GAEでPythonとDjangoでAmazonAPI使ったWEBアプリを作成中。
AmazonAPIはPyzonを使っていて、返ってきたxmlはBeautifulSoupを使って読んでいる。
が、BeautifulSoupがよく分からず、どんなコードならいいのか全然わからない。
あれはうまくいったのに、これはうまくいかないなんて全然分からなかったので、あれとこれをメモっておく。


うまくいったコード
from pyzon import Pyzon
from BeautifulSoup import BeautifulStoneSoup
import re

access_key_id = ****
secret_access_key = ****
associate_tag = ****

#本を検索する
def book_search(bookname):
    pyzon = Pyzon(access_key_id, secret_access_key, associate_tag)
    xml = pyzon.ItemSearch(search_index='Books', Keywords=bookname,ResponseGroup='Small,Images')
    soup = BeautifulStoneSoup(xml)
    #asin,smallimage,title,author,manufacturerのリスト作る
    bookinfo_list = [[item.asin.contents[0],item.smallimage,item.title.contents[0],item.author,item.manufacturer.contents[0]] for item in soup.findAll('item')]
    #smallimage,authorはNONEの可能性あり、チェックしてNONEの対応して、余計なものを省く
    idx = 0
    for bookinfo in bookinfo_list:
        if not bookinfo[1] == 'NONE':
            p = re.compile('^(?P.+?)')
            m = p.match(str(bookinfo[1]))
            if m: bookinfo_list[idx][1] = m.group('url')
        if not bookinfo[3] == 'NONE':
            p = re.compile('^(?P.+?)')
            m = p.match(str(bookinfo[3]))
            if m: bookinfo_list[idx][3] = m.group('author')
        idx += 1
    return bookinfo_list


うまくいかなかったコード
from pyzon import Pyzon
from BeautifulSoup import BeautifulStoneSoup

access_key_id = ****
secret_access_key = ****
associate_tag = ****

#1冊の本の情報を取り出す(IdTypeはASIN)※amazonAPIのitemLookup
def book_lookup(asin):
    pyzon = Pyzon(access_key_id, secret_access_key, associate_tag)
    xml = pyzon.ItemLookup(item_id=str(asin),ResponseGroup='Small,Images')
    soup = BeautifulStoneSoup(xml)
    #asin,smallimage,mediumimage,largeimage,title,author,manufacturerのリスト作る
    asin = soup.asin.contents[0]
    title = soup.title.contents[0]
    manufacturer = soup.manufacturer.contents[0]
    #imageとauthorはNONEの可能性があるので、チェックした後、値を格納。
    smallimage_url = ''
    mediumimage_url = ''
    largeimage_url = ''
    author = ''
    if not soup.smallimage == 'NONE': smallimage_url = soup.smallimage.url.contents[0]
    if not soup.mediumimage == 'NONE': mediumimage_url = soup.mediumimage.url.contents[0]
    if not soup.largeimage == 'NONE': largeimage_url = soup.largeimage.url.contents[0]
    if not soup.author == 'NONE': author = soup.author.contents[0]
    return [int(asin),str(smallimage_url),str(mediumimage_url),str(largeimage_url),title,author,manufacturer]

うまくいかなかった方は、soup.smallimageとか、soup.authorとかの値が無いときに、最後の数行のif分のところで、無い値の部分がエラーになる。

AmazonAPIがJSONで返してくれたらどんだけ楽なことか。PythonでXMLをJSONに直すのも簡単ではなさそうなので、XMLをBeautifulSoup読む方法で粘ろう。

ちなみに、うまくいかなかったコードの修正版がこれ。
from pyzon import Pyzon
from BeautifulSoup import BeautifulStoneSoup
import re

access_key_id = ****
secret_access_key = ****
associate_tag = ****

#1冊の本の情報を取り出す(IdTypeはASIN)※amazonAPIのitemLookup
def book_lookup(asin):
    pyzon = Pyzon(access_key_id, secret_access_key, associate_tag)
    xml = pyzon.ItemLookup(item_id=str(asin),ResponseGroup='Small,Images')
    soup = BeautifulStoneSoup(xml)
    #asin,smallimage,mediumimage,largeimage,title,author,manufacturerのリスト作る
    bookinfo_list = [[item.asin.contents[0],item.smallimage,item.mediumimage,item.largeimage,item.title.contents[0],item.author,item.manufacturer.contents[0]] for item in soup.findAll('item')]
    #imageとauthorはNONEの可能性があるので、チェックした後、値を格納。
    idx = 0
    for bookinfo in bookinfo_list:
        if not bookinfo[1] == 'NONE':
            p = re.compile('^(?P.+?)')
            m = p.match(str(bookinfo[1]))
            if m: bookinfo_list[idx][1] = m.group('url')
        if not bookinfo[2] == 'NONE':
            p = re.compile('^(?P.+?)')
            m = p.match(str(bookinfo[2]))
            if m: bookinfo_list[idx][2] = m.group('url')
        if not bookinfo[3] == 'NONE':
            p = re.compile('^(?P.+?)')
            m = p.match(str(bookinfo[3]))
            if m: bookinfo_list[idx][3] = m.group('url')
        if not bookinfo[5] == 'NONE':
            p = re.compile('^(?P.+?)')
            m = p.match(str(bookinfo[5]))
            if m: bookinfo_list[idx][5] = unicode(m.group('author'),'utf-8')
        idx += 1
    return bookinfo_list[0]