2013年4月22日月曜日

GAE (Google App Engine) でcsvを一括アップロードする (python)


昔駅データのcsvファイルをGAEで一括アップロードしたことがありましたが、改めて本日csvファイルのアップロードが必要になりましたので、調べながらまたやってみたいと思います。

このデータのアップロードとダウンロードというページに従ってやればいいようです。

よく分からないながら色々やっている状態ですが、気づいた点を記載します。

まず最近のデータストアを使っている場合は、CGIが使えません的なエラーが出ますので、remote_apiの設定については、builtins ディレクティブを利用するパターンになるのではないかと思いました。

あと、IntegerPropertyの場合に、csvデータ側でデータが入っていない場合は、エラーになるので、データを入れるか、StringPropertyにするなどの対応が必要なのではないかと思いました。

あと、blukloader.yamlと、csvファイルの置き場所ですが、GAEのプロジェクトディレクトリが置かれているディレクトリに置く必要があります。プロジェクトディレクトリ内ではありません。

また、どうやら新しいデータストアでのアプッロードやダウンロードはターミナルでのコマンドに変更があるようです。

appcfg.py upload_data --config_file=bulkloader.yaml --url=http://sample.appspot.com/_ah/remote_api --kind=Sampledata --filename=sample.csv
また、db.TextProperty()をデータストアのエンティティで設定している場合、blukloader.yamlの設定で、下記のようにdb.textを設定する必要があります。これがないと、500文字超過の場合にエラーがでます。
 - property: points
      external_name: points
      import_transform: db.Text
その他、本件についての参考になるサイトを列挙します。 googleグループのディスカッション
datastore のデータをダウンロード/アップロードする
googleグループのディスカッション2

2013年3月21日木曜日

mac 10.8.3にcakePHP2.3.1をインストール

毎回別のエラーが出る。
・上の方のエラーは前に書いた、アクセス権限付与でOK。
・あと、.htaccessファイルもcakePHPのディレクトリからコピーする必要もある。
・あと、DebugKit pluginをgithubから落として、app/Plugin/DebugKitに入れる必要があり
・その後、/app/Config/bootstrap.phpのCakePlugin::load('DebugKit'); //Loads a single plugin named DebugKitと書いてある行のコメントを消す必要がある。

2013年3月10日日曜日

codeIQをやってみた

The Essence of Programming 結城 浩さんからのアルゴリズムの問題をやってみました。簡単だと思ったのですが、アルゴリズムとかまだ勉強できてないので合ってるのか分かりません。ミスしてる可能性もあります。一応確認したら出来てる感じでしたが。ただ、やっぱりアルゴリズムの問題はクイズ的だし面白いので、もっと勉強しようと思いました。codeIQにはこういうのが色々あるようなので色々見てみたいと思いました。

合ってなかったら残念です。

# -*- coding: utf-8 -*-

f = open('blendlist.txt', 'r')

blends = [] #[[a,[b,c,d]],[b,[d,e,f]]]
curryA = []
curryB = []

#blendsの作成
for blend in f:
    spices = blend.split() #[a,b]
    flag = False
    
    for spice in spices:
        if len(blends) > 0:
            for b in blends:
                if spice == b[0]:
                    flag = True
                    break
        if not flag: blends.append([spice,[]])
        flag = False

    for idxS in range(len(spices)):
        for idxB in range(len(blends)):
            if spices[idxS] == blends[idxB][0]:
                if idxS == 0: idxAdd = 1
                else: idxAdd = 0 
                if not spices[idxAdd] in blends[idxB][1]:
                    blends[idxB][1].append(spices[idxAdd])

f.close()

#curryA,Bの作成
cntA = 0
cntB = 0

for blend in blends:
    if not curryA and not curryB:
        curryA += [blend[0]] + blend[1]
        for b in blends:
            if not b[0] in curryA: curryB += [b[0]]
    else:        
        for idx in range(len(blend[1])):
            if blend[1][idx] in curryA: cntA += 1
            else: cntB += 1
        if cntA > cntB:
            if not blend[0] in curryA:
                curryB.remove(blend[0])
                curryA.append(blend[0])
        elif cntA < cntB:
            if not blend[0] in curryB:
                curryA.remove(blend[0])
                curryB.append(blend[0])
        cntA = 0
        cntB = 0    

#得点の計算
pntA = 0
pntB = 0

for blend in blends:    
    if blend[0] in curryA:
        for spice in blend[1]:
            if spice in curryA: pntA += 1
            for idxBlends in range(len(blends)):
                if blends[idxBlends][0] == spice:
                    blends[idxBlends][1].remove(blend[0])
    else:
        for spice in blend[1]:
            if spice in curryB: pntB += 1
            for idxBlends in range(len(blends)):
                if blends[idxBlends][0] == spice:
                    blends[idxBlends][1].remove(blend[0])

#Answerの作成
answer = ''

if pntA > pntB:
    curryA.sort()
    for spice in curryA: answer += spice + ' '
else: 
    curryB.sort()
    for spice in curryB: answer += spice + ' '
answer = answer.rstrip()
answer += '\n\n■得点\n'
answer += 'CurryA: ' + str(pntA) + '点\n'
answer += 'CurryB: ' + str(pntB) + '点\n'
answer += '------------------------------\n'
answer += '合計   : ' + str(pntA + pntB) + '点\n\n■ソースコード\n' 

f = open('curry.py','r')
answer += f.read()
f.close()

#answerの書き込み
f = open('answer.txt', 'w')
f.write(answer)
f.close()

2013年3月4日月曜日

せどり向けプレミア本・DVDの検索システムをつくってみた

せどりというのはご存知でしょうか?ブックオフで100円で買った古本は、アマゾンだと意外と高値で売れたりするというやつです。裁定取引ということでいいんでしょうかね。最近ですと電脳せどりといいまして、わざわざリアル店舗のブックオフに足を運ばずにインターネットで仕入れてアマゾンで売るということも行われているようです。実際せどりで仕入れる商品をどうやって探すのかというと、アマゾンで定価よりも高値なのにどんどん売れている商品を探すんです。アマゾンで売るわけだから、アマゾンで探すわけですね。

ということで、アマゾンで新品が売ってなくて、中古1点しか売れてない商品を探せるプログラムを作成しました。まあ巷に同様のプログラムは色々あるのですが、なんだかんだお金がかかりますので、自分で作ってみました。これを更に改良してみたいと思います。(ガンガンせどりをするつもりではありませんが)



ちなみに、先程このプログラムでみつけたDVDが楽天で一個だけあったので買ってみました。初せどり挑戦であります。今度売ってみます。

2013年3月2日土曜日

神様を発見しました

今日本当の神様をみつけました。

2013年2月24日日曜日

Pythonで時間関連の関数の勉強(datetime)

地味ですが、Pythonの時間関連の勉強をしたいと思います。
当然いつもやっているのですが、しっかりと頭に叩き込みたいと思います。

下記をやることで頭に叩き込まれると思います。

  • 今日を得る
  • 明日を得る
  • 昨日を得る
  • 1時間前を得る
  • 29時間後を得る
  • 5分前を得る
  • 2011年3月11日からの経過日を得る
  • あと何年で50歳になるかを得る
  • あと年日で50歳になるかを得る
  • あと何時間で50歳になるかを得る
  • 8日後を得る
  • 1ヶ月後を得る
  • 3年2ヶ月3日及び11時間後を得る

さて、まずここを見ながら勉強します。

timedeltaオブジェクト


class datetime.timedelta([days[, seconds[, microseconds[, milliseconds[, minutes[, hours[,weeks]]]]]]])
timedeltaオブジェクトは経過時間、すなわち二つの日付や時刻間の差を表します。


ということなので、時間の計算をしたいときは、このオブジェクトを使うことになるわけでしょう。
試しに、使ってみましょう。

d = datetime.timedelta(days = -1)
こうやると、dは、-1 day, 0:00:00となっています。
d1 = datetime.timedelta(days = 1)
d2 = datetime.timedelta(days = 5)
dd = d2 - d1
こうやると、ddは、4 days, 0:00:00となっています。確かに、計算できていますね。dd.total_seconds()を出してみると、345600.0となります。60秒×60分×24時間×4日ですと、まさしく345600ですね。合っていました。

dateオブジェクト


class datetime.date(year, month, day)
dateオブジェクトは日付 (年、月、および日) を表します。


次はdateオブジェクトで、年月日を表すオブジェクトですね。差分を表すのがtimedeltaですが、こちらはまさに日付を表すものになります。シンプルですね。では、実際使ってみます。

badDay = datetime.date(2011, 3, 11)
とやると、badDayは、2011-03-11となりました。
today = datetime.date.today()
とやると、今日の日付が得られます。2013-02-24となりました。
d = datetime.date.fromordinal(1000000)
こんなのもやってみました。1年1月1日を1とするという変な関数ですね。2といれると、1年1月2日ですし、365と入れると、1年12月31日となります。ちなみに、よく百万年とかいいますが、百万日ということで1000000と入れると、2738-11-28が返ってきます。100万年というのは果てしないことであるのだなと思った次第です。
y = today.year
とやりますと、先程作成したtodayの年のみyに入ります。month、dayも同じ仕組みですね。

ほぼほぼ、整いました。

それでは、宿題を順に解いていきましょう。

■ 今日を得る
これは先程やってしまいました。

■明日を得る
tomorrow = today + datetime.timedelta(days = 1)
とやると、2013-02-25 12:50:48.396328出ました。OKです。

■昨日を得る
yesterday = today - datetime.timedelta(days = 1)
とやると、2013-02-23 12:52:21.310513出ました。OKです。

■1時間前を得る
d = today - datetime.timedelta(hours = 1)
とやると、 2013-02-24 11:55:24.600223でました。タイムゾーンが違うんですけどまあいいでしょう。

■ 29時間後を得る
e = today + datetime.timedelta(hours = 29)
とやると、 2013-02-25 17:57:20.966985出ました。oKそうですね。

■5分前を得る
飽きてきました。
f = today - datetime.timedelta(minutes = 5)
2013-02-24 12:54:06.213152出ました。OKです。

■2011年3月11日からの経過日を得る
今日から引けばいいですね。
g = datetime.datetime(2011,3,11) - today
-717 days, 10:57:36.650128そういえば、today = datetime.datetime.today()としてましたので、全部時間がでてるですね。
ちなみに、date型と、datetime型で計算しようとすると怒られました。

■あと何年で50歳になるかを得る
これは今32歳ですので、今日から18年後を取得するってことでいいですかね。
いやーひととおり、色々やってたんですけど、よく考えたら18年ですね。

■1ヶ月前を得る
もう飽きたので眠いのでこれをやって終わりたいと思います。
i = today - datetime.timedelta(days=30)
2013-01-25 13:15:23.474761

終了です。

体得しました。修行のようでした。

Amazon 本 価格帯別販売点数調査 第二弾


アマゾン価格帯毎販売点数調査第二弾

100円単位バージョン

100円単位で調べたところ、0-99円は圧倒的の80万冊でしたが、これは電子書籍、青空文庫の関係であると思われます。2,000円以上50,000円で見てみると、2,700円〜2,799円台が最も多く、約6万点であることが分かりました。

以下は、2000円以上で、販売点数の多さ上位50位の表です。

MinPriceMaxPriceNumber of Books
2700279961750
2900299959648
3000309956439
2500259948213
2800289945611
2000209944932
3200329940019
2600269936034
3900399932371
3100319931282
2400249926640
3300339924939
5000509924926
3500359924917
3400349922853
2100219922500
2300239919947
4900499919715
2200229919468
3600369919302
3700379918584
4500459918497
4000409918364
3800389918350
4400449916632
4700479916099
4800489915133
4200429913945
5900599913541
9900999913250
8000809912270
100001009912237
5500559911857
4100419910880
6000609910494
4300439910374
4600469910168
790079999675
520052999311
540054999026
980098998554
700070997997
570057997562
950095997522
580058997128
17800178996930
21000210996881
690069996649
850085996245
10500105996221

total: 1,021,882

Amazon 本 価格帯別販売点数調査

2013年2月24日現在、アマゾンで和書を検索してみると、全部で5,595,348冊あると表示される。沢山ありますね。
ところで、アマゾンで売られている本はどの価格帯が一番多いのだろうか?まあ感覚的には1000円台かな?と思うんだけど、それを具体的に調べてみたいと思ってやってみた。あとは、数万円以上の高額本って結構あるのかな?とかも合わせて確認したい。

まずは、1000円〜10万円までを1000円単位で区切ってそれぞれの売ってる本の数を取得してテーブルで表示してみたいと思う。

はい、では結果を表示しましょう。

Pythonのオブジェクト指向の勉強(1)

Pythonのオブジェクト指向を学びたいと思います。

まずは、お気楽 Python プログラミング入門 第 5 回 オブジェクト指向の基礎知識をみながら勉強していきたいと思います。

まず読みながら勉強になったことを一覧化していきます。

  • クラス名の横に括弧をつけないと、継承されないだけで構文的には問題ない 
  • クラスを関数と同じ形式で呼び出すと、そのクラスのインスタンスを生成して返す 
  • Python のインスタンス変数は、代入操作が行われるときに生成される 
  • Python のインスタンス変数は、どこからでもアクセスすることができる(すべてがpublic) 
  • メソッドの第 1 引数にはインスタンスが渡される。Python では、この引数名を self と記述する習慣がある 
  • __init__() はインスタンスを生成するときに自動的に呼び出される特殊メソッド 
  • __init__() は return で値を返すとエラーになる

まだ途中だけど、満足した。今度続き読もう。

GAE jinja2 をちょっと効率的にする(くだらないこと)

GAEのjinja2というテンプレートエンジン?を使ってますが、一々出力するときの3行くらい書くのめんどくさいから、こうやって効率的にしたというくだらないことだけど。
def write(self,param,html):
    template = jinja_environment.get_template(html)
    self.response.out.write(template.render(param))
とやっておいて、使う時はこうやる。
write(self,{'aiueo':'aiueo'},'index.html')

google app engine - datastoreのけんきゅう

簡単なけんきゅうです。

class Price(db.Model):
    price = db.IntegerProperty()
このクラスを使います。

now = Price.all()
とやると、 <google.appengine.ext.db.Query object at 0x106845fd0>が返ってきます。これはデータが入っていようが、空だろうがこのような結果が返ってきます。

now = Price.all()
now = now.fetch(1)
とやると、 []が返ってきます。 当然これは、Priceにデータが入ってなかったからです。もしあれば、[<main.Price object at 0x1043a6210>]といったリストが返ってきます。

now = Price.all().fetch(1)
if not now:
    now = Price(price=100)
とやると、<main.Price object at 0x105e88810>が返ってきます。当然これはPriceにデータがなかったので、Priceオブジェクトを作成してnowに代入したからこの結果になってます。もしPriceにデータがあれば、[<main.Price object at 0x103f4aad0>]といったように、リストが返ってきます。

now = Price.all().fetch(1)
if not now: now = Price(price=100)
else: now = now[0]
とやると、Priceにデータがあってもなくても、<main.Price object at 0x106dcff90>といったようなものが返ってきます。

つまり

  • Price.all().fetch(1)とやると、オブジェクトのリストを返します。
  • Price.all().fetch(1)[0]とやれば、オブジェクト自体を取り出せます。(ただし、オブジェクトが無かった場合エラーになります)
  • よってfetch()後のリストが空でないことを確認した上で、オブジェクトを取り出します。

2013年2月23日土曜日

コールセンターの必要席数試算(アーランCと稼働率の関係)

コールセンターの必要席数を試算するときに、アーランを使う場合と使わない場合があると思います。

アーランを使う場合と使わない場合とで必要席数が大きく異なる場合、アーランを使わない場合に利用されている稼働率という指標を適当に設定しているケースがよくあります。そもそも、目標応答率が高いということは、取りこぼしがないように、入電が同時に沢山発生したときを考慮して席数を多めに配置するわけですから、当然ながら稼働率は下がります。なので、目標応答率70%のときも、95%のときも稼働率が80%なんてことは試算上はあってはならないのであります。

ということで、試しに、アーランCの結果を正とした場合に、各目標応答率に対する適正な稼働率を算出してみたいと思います。

各目標応答率に対する適正な稼働率を算出する


ちなみに、アーランを使わない場合の席数試算方法は下記になります。

必要席数 = 1時間当たり入電数 ÷ ( 60 ÷ ( 平均通話時間 + 平均後処理時間 ) × 稼働率 )
※平均通話時間と平均後処理時間の単位が”分”の場合の式です。単位が”秒”なら、60は、3600になります。

アーランを使う場合は、依然このブログで投稿した『JavaScriptでアーランCによりコールセンターの必要席数を計算する』で記載している計算方法を使いましょう。

ソースコードは下記です。下にあるボタンを押すとこれを起動させるようにしました。

1時間当たり入電数(件):
平均通話時間(分):
平均後処理時間(分):


<table>
<tr><td>1時間当たり入電数(件):</td><td><input id="call" type="text" size="50" /></td></tr>
<tr><td>平均通話時間(分):</td><td><input id="acd" type="text" size="50" /></td></tr>
<tr><td>平均後処理時間(分):</td><td><input id="acw" type="text" size="50" /></td></tr>
</table>
<input type="button" id="startBtn" value="稼働率を計算" /><br />
<div id="kadoritsuResult"></div>

<script type="text/javascript">
$(function(){
    $('#startBtn').click(function(){start();});
});

function start(){
 $('#kadoritsuResult').empty();
 var call = $('#call').val();
 var acd = 60*$('#acd').val();
 var acw = 60*$('#acw').val();
 var aht = acd+acw;
 var kadoritsu = 0;
 var sheet = 0;
 var text = '<br /><table class="kadoritsuTable">';
 
 text += '<tr><td>応答率</td><td>稼働率</td><td>必要席数</td></tr>';

 for(var sl=0.5;sl<1;sl+=0.01){
  sheet = erlangc(call,aht,sl);
  kadoritsu = Math.round((getKadoritsu(call,aht,sheet))*100)/100;
  text += '<tr><td>' + parseInt(sl*100) + '%</td><td>' + kadoritsu +'</td><td>' +
  sheet + '席</td></tr>';
 }

 text += '</table>';
 
 $('#kadoritsuResult').append(text);
}

function getKadoritsu(call,aht,sheet){
 return call/(3600/aht)/sheet
}

function erlangc(call,aht,sl){
    sl = 1 - sl;
    if(sl<=0)sl=0.0009;
    var erlang = aht*call/3600;
    var sheet = parseInt(erlang);
    var p = 1.0;
    var up = 1.0;
    var down = 0.0;
    var temp;

    while(p>=sl){
        sheet++;
        //分子
        for(var x=0; x<sheet; x++){
            up *= erlang/(sheet-x);
        }
        up = up*sheet/(sheet-erlang);
        //分母
        for(var x=0; x<sheet;x++){
            temp = 1.0;
            if(x!=0){
                for(var y=0; y<x;y++){
                    temp *= erlang/(x-y);
                }
            }
            down += temp;
        }
        down += up;
        p = up/down;
        up = 1.0; down = 0.0;
    }
    return sheet;
}
</script>

2013年2月21日木曜日

Product Advertising APIで効率よくItemSearchする(GAE×Python2.7)

こんばんは。タイトルのとおり効率よくItemSearchしたいと思ってます。
効率よくも何もないとお思いになられるかもしれませんが、本、DVD、おもちゃ、ホビー、TVゲームが全て検索できるItemSearch関数を作ろうということです。

私は本しかやったことなかったので、他のものも検索するにあたって、全て別の関数つくってたんじゃたまりません、ということで、全てを効率よく問題なく検索できる関数の姿を明らかにしようと思ってます。

今ここに本を検索する為の関数があります。

def bookSearch(browseNode,page,minPrice):
    pyzon = Pyzon(access_key_id, secret_access_key, associate_tag)
    xml = pyzon.ItemSearch(
                           search_index='Books', BrowseNode=str(browseNode),
                           ItemPage=str(page),Condition='All',MerchantId='All',
                           ResponseGroup='Small,SalesRank,Images,OfferSummary',
                           Sort='salesrank',MinimumPrice=str(minPrice)
                           )
    xml = urllib.unquote(xml)
    soup = BeautifulStoneSoup(xml)
    return soup

ご覧いただくと分かるかもしれませんが、pyzonとbeautifulsoupを使ってます。 ブラウズノードIDと、ページ、最低価格を引数にとり、検索をかけて、その結果をBeaftifulSoupのクラスにして返却しています。 さて、同じ引数でDVD、おもちゃ、ホビー、TVゲームも検索できるようにするには、今設定しているオプションが本以外でも問題なく使用できることを確認する必要があります。また、search_indexは、必ず設定しないといけないみたいですので、search_indexの値も引数にしないといけないのではないかと思ってます。

ItemSearch では、サーチインデックスの他に、1つまたは複数のパラメータ値を指定する必要があります。

さて、本以外でつかえる検索オプションを確認する為には、『SearchIndex-ItemSearch パラメータの組合せ (JP)』をみれば一目瞭然であります。 おおおお、DVDではなんと、最低金額設定ができないようです!

本(Books)DVD(DVD)TVゲーム(VideoGames)おもちゃ(Toys)ホビー(Hobbies)
Author, BrowseNode, Condition, ItemPage, Keywords, MaximumPrice, MerchantId, MinimumPrice, Power, Publisher, Sort, TitleActor, AudienceRating, Availability, BrowseNode, Count, Director, Format, ItemPage, Keywords, Magazines, Performer ,PostalCode, Publisher, Sort, State, TitleBrand, BrowseNode, Condition, ItemPage, Keywords, Manufacturer, MaximumPrice, MerchantId, MinimumPrice, Sort, TitleBrowseNode, Condition, ItemPage, Keywords, Manufacturer, MaximumPrice, MerchantId, MinimumPrice, Sort, TitleBrowseNode, Condition, ItemPage, Keywords, Manufacturer, MaximumPrice, MerchantId, MinimumPrice, Sort, Title

でも、このドキュメント古いから、一応試してみよう。DVDだけできないというのはおかしい。 やってみたら出来た。なんだかな。要するに、上記の本用の関数を引数にsearch_indexを加えるだけで問題ないらしい。まあ後は、レスポンスの取り方としては、大体全部同じなんだけど、itemattributesが、authorじゃなくてactorだったり、actorは複数あったりするのでこの辺りのレスポンスの取り方を注意すれば全然問題ないようだな。

ということで下記のようになりました。

def itemSearch(searchIndex,browseNode,page,minPrice):
    pyzon = Pyzon(access_key_id, secret_access_key, associate_tag)
    xml = pyzon.ItemSearch(
                           search_index=searchIndex, BrowseNode=str(browseNode),
                           ItemPage=str(page),Condition='All',MerchantId='All',
                           ResponseGroup='Small,SalesRank,Images,OfferSummary',
                           Sort='salesrank',MinimumPrice=str(minPrice)
                           )
    xml = urllib.unquote(xml)
    soup = BeautifulStoneSoup(xml)
    return soup
なんかどうってことないことでしたね。

2013年2月19日火曜日

Product Advertising APIのBrowseNodeLookupで本(和書)のBrowseNodeを全部取得する(2)


はい、前回やっていたことはかなり非効率であることが分かりました。再帰するのか否かはおいておきまして、そもそもBrowseNodeLookupの結果のルールに先程気づきまして、非常に複雑でめんどうなことをしていたと気づいた次第です。

さて、何がルールかというと、RootのLookupをした場合は、ancestorが一つだけあります。中間のLookupをした場合は、ancestorが複数あります。最下層のLookupをした場合は、ancestorがございません。このancestorの数を確認するだけで、最下層か否かを確認することが可能であります。どうでしょうか?衝撃的事実であったのではないかと思います。ぜひ、Browsenodeの取得に苦慮されている方がいましたら、この事実を教えてあげるといいと思います。

それでは失礼します。

コードは大体下記のようになるのではないでしょうか?

browsenodes = result.browsenodelookupresponse.browsenodes
ancestorsList = browsenodes.findAll('ancestors')
ancestorIdList = []

for ancestors in ancestorsList:
    if ancestors.browsenode:
        ancestorIdList.append(int(ancestors.browsenode.browsenodeid.contents[0]))
        ancestors.extract()
        
browseNodeList = browsenodes.findAll('browsenode')

#最下層じゃない
if len(ancestorIdList) > 0:
    #最下層じゃない場合の処理

#最下層
else:
    #最下層の場合の処理

2013年2月18日月曜日

Product Advertising APIのBrowseNodeLookupで本(和書)のBrowseNodeを全部取得する

Product Advertising APIのBrowseNodeLookupで本(和書)のBrowseNodeを全部取得したいと思います。

ここに、BrowseNodeLookupの説明が書いてあるんですがちょっと古くて、2013年2月現在では別の結果を返している気がするんですが。最新版ないんですかね?そもそもgoogle先生に聞くと、2011年頃に大幅な改訂がなされたとかで皆さん大騒ぎをしているんですがそれらの結果を反映した最新版が見つからないんですよね??英語だとあるんですかね。まあ自分で試して結果をみるのが一番確かだということで、試してみてます。

本(和書)のブラウズノードは、465610ですので、ここからどんどん下に掘っていきたいと思います。調べた感じですと、browsenodeを探して、その中にchildrenかancestorsが入ってたら飛ばす、両方とも入ってなかったら、そのbrowsenodeidを基に更に掘るという感じでどうだろうかと思っております。掘っても該当するbrowsenodeがなければそこが最下層であると認識します。僕は階層を明確化したいのではなく、最下層のbrowsenodeidの一覧を取得したいので、最下層であると判断されたら、browsenodelookupresponse > browsenodes > request > browsenodelookuprequest > browsenodeidを取得してリストに追加するというイメージでやってみたいと思います。いやいや今調べてたんだけど、そういうわけでもないんですね。なんなんだこのめちゃくちゃなresultは。まあ利用目的が違うとでもいいたいんだろうか。ドリルダウンで全ての属性を知りたいと思うことはあるだろうし、最下層の属性だけ抽出したいと思うこともあるだろうに。なぜこうもめんどくさい仕様になっているんだろうか。ということで、ancestorsが入ってなくてもancestorsであることもあることが分かりましたので、ancestorsを持っているタグ以降に出てくるものは全部ancestorsであるという条件も前提にいれましょう。

さて、BrowseNodeLookupで45610をみて、該当するidをforでBrowseNodeLookupすることを繰り返してみましょー。

class GetBrowseNode(webapp2.RequestHandler):
    def get(self):         
            browseNodeWasyo = 465610
            resultList = browseNodeCheck(browseNodeLookup(browseNodeWasyo))
            templateValues = {'result':resultList} 
            template = jinja_environment.get_template('index.html')
            self.response.out.write(template.render(templateValues))

def browseNodeLookup(browseNodeId):
    pyzon = Pyzon(access_key_id, secret_access_key, associate_tag)
    xml = pyzon.BrowseNodeLookup(browsenode_id=str(browseNodeId))
    xml = urllib.unquote(xml)
    soup = BeautifulStoneSoup(xml)
    return soup

def browseNodeCheck(result):
    resultList = []
    
    if not result.errors:
        browsenodes = result.browsenodelookupresponse.browsenodes
        ancestorsList = browsenodes.findAll('ancestors')
        for ancestors in ancestorsList:
            if ancestors.browsenode: ancestors.extract()
        browseNodeList = browsenodes.findAll('browsenode')
        childrenCnt = 0
        AncestorsCnt = 0
        
        for node in browseNodeList:
            if not node.children and not node.ancestors:
                if AncestorsCnt > 0:
                    AncestorsCnt += 1
                else:
                    time.sleep(1)
                    resultList += browseNodeCheck(browseNodeLookup(node.browsenodeid.contents[0]))
            else:
                if node.ancestors:
                    AncestorsCnt += 1
                elif node.children:
                    childrenCnt += 1
                    
        if  (childrenCnt + AncestorsCnt) == len(browseNodeList):
            browsenodeid = browsenodes.request.browsenodelookuprequest.browsenodeid.contents[0]
            resultList.append(int(browsenodeid))
            logging.debug(u'browsenodeid追加: ' + str(browsenodeid))
        
    else:
        code = result.errors.error.code.string
        message = result.errors.error.message.string
        logging.error(u'AmazonError! code: ' + code + u' message: ' + message)
    
    return resultList

これは、GAE、Pythonでつくっていまして、ライブラリは、PyzonとBeautifulSoupを使ってます。ブラウズノードを確認して、まだ下が有る場合はどんどん突き進み、もう下がない場合はリストにbrowsenodeidを追加するようになっております。再帰してます。ただ、これだと超時間がかかりまして、GAEのローカル環境ですと途中でエラーになりました。いずれにしてもこれを毎日のように実行すると考えるとかなり非現実的です。再帰が使える状況って限られますね。よって、都度都度検索型に変えようと思います。失礼いたしました。

再帰? (Python)

これ再帰?

def testdef(x):
    testList = []
    testList.append(x*2)
    if x >0: testList += testdef(x-1)
    return testList

testdefに10を入れると、[20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 0]が出てきます。 これ再帰でしょう??初めて再帰をやった!?お恥ずかしいです。

2013年2月12日火曜日

cakePHP2.2 Bakeを研究してみる

私の環境は、mac、Eclipse、XAMPP、PHP5.3、cakePHP2.2.5、MySQLです。

Bakeとはデータベースのテーブルさえ作っておけば、瞬時にcakePHPのモデルとビューとコントローラーを自動で作ってくれる機能のことであります。 便利なんじゃないでしょうか。ベースとなる部分って基本的に固定された内容を毎回作成するわけですから、そのベースを多少修正するのは当たり前としても前段となる作業の大部分を自動化できるっていうのは大変有り難いことです。加えて、cakePHPのcookbookというリファレンスは、Bakeを使う前提で解説されている箇所も結構多そうなので、とりあえずBakeはマスターしておこうと思いました。

さて、Bakeの解説はここに載っていますので、こちらを見ながら試していきたいと思います。

下準備(プロジェクト作成〜データベース設定)

すんなりいくのか不安ですが、まずはEcliplse上でcakePHPのプロジェクトを作成すると共に、MySQLのデータベースを作成し、テーブルも作成します。 そして、cakePHPとデータベースを連携させる為の設定を実施します。ここまでのやり方詳細は、こちらに記載しましたので分からない場合はご確認ください。

さて、今回はbaketestという名前でプロジェクトを作成しました。フォルダは /Applications/XAMPP/xamppfiles/htdocs/baketest にあります。 データベースは、baketestという名前でこれまた作成しました。テーブルは下記2点を作成しました。

cakePHP2.2.5 ー ダウンロードから開発開始までの準備作業詳細を徹底解説!(Mac × Eclipse × XAMPP)


cakePHPを使うの結構久しぶりで、cakePHPの開発環境を削除してしまっていたことから、一からcakePHPをダウンロードするところから始めます。 ちなみに、私の開発環境は、Mac、Eclipse、PHP5.3です。ローカルのウェブサーバーはXAMPPを使っています。

cakePHPのダウンロード

ダウンロードはここからできます。今はver2.2.5が安定板の最新になっていますね。

cakePHPのインストール

インストールはこのページをみたら出来ますね。とりあえずウェブサーバにダウンロードしてきたcakePHPのフォルダをぶち込むだけで、インストール完了という超お手軽なようです。そういえばそうでした。でも、他にも2パターンインストールの方法があるようです。”運用”と”応用”は後でやってみます。
  • 開発(Development): 簡単にはじめることができますが、アプリケーションのURLには、CakePHPをインストールしたディレクトリ名が入ります。 他の設定と比べるとセキュリティ面はやや弱くなります。
  • 運用(Production): Webサーバのドキュメントルートを設定できる必要がありますが、URLをクリーンにでき、セキュリティを固くできます。
  • 応用(Advanced): 幾つかの設定により、CakePHPの重要な各ディレクトリをファイルシステムの異なる場所に配置することができるので、 多くのCakePHPアプリケーションがひとつのCakePHPコアライブラリのフォルダを共有することなどが可能です。

2013年2月9日土曜日

ありえないマーケティング

ありえないマーケティングを読んだ。カフェで2時間位で読んだ。読み返してないけど、やっぱり当たり前のことを当たり前にしっかり全部やることが重要なんだなあと思った。情報過多の時代なので情報提供により顧客をつかむ方法は価値が低下してきているので、あえて情報提供しませんというスタンスをうたいつつ、とはいえやはり情報提供を定期的に実施していくことで顧客リストをつくっていくのだよと書いてあった気がする。

インターネットビジネスは検索エンジン対応が重要で、検索エンジンは課題をもった人々がキーワードを入れて検索するわけですから、そのキーワードから想定される課題の本質に対してしっかりと応えないといけないと書いてあった気がする。キーワードは、ビッグワードはビッグな会社がうごめいているから、スモールニッチワードでビジネス展開をすることで、小資本の貧乏起業家も勝てる可能性が出てくるんだよと書いてあった。

2013年2月7日木曜日

ソーシャルもうええねん

『ソーシャルもうええねん』をこの前読みました。ここ最近の本で一番面白かった。
僕もソーシャルはもうええねんと思いました。
それにしても、この人はやっぱりネットで少し知ってたんだけど超すごくて、三浦大地以降、久しぶりに尊敬しました。

ブルーオーシャン戦略ってめずらしく、ものすごく分かり易いカタカナだと思っていまして、なぜ分厚い本がこのキーワードがお題で出来上がるのか分からないのですが、やっぱりブルーオーシャン戦略一本で人生生きていかないと死んでしまうのだと思いました。
そして全ては現在のご時世に合わせた世渡りと、ホンモノの武器を持たなくてはいけないんだろうなと思いました。

まあホンモノっていっても大体みんなたかが知れていたりもするでしょうし、そういう相対的な競合の中でのホンモノではあると思いますが、そんなせこいことを言ってると、ホンモノの武器は持てないんだろうとも思いました。

2013年2月4日月曜日

GAEでメールを送信する (Python)

GAEでメール送信するのは強烈に簡単です。
久しぶりで忘れてたのでメモっておきます。メモる必要すらないくらい簡単です。

ここを見ればすぐ分かります。

使い方イメージはこんな感じです。

from google.appengine.api import mail

class Mail(webapp2.RequestHandler):
    def post(self):
        company = cgi.escape(self.request.get('company'), True)
        name = cgi.escape(self.request.get('name'), True)
        mailAd = cgi.escape(self.request.get('mail'), True)
        
        if company and name and mailAd:
            sender_address = "Hoge Hogeo <hogehogeo@hogehoge.com>"
            subject = u"ほげほげ"
            body = u'ほげほげほげ\n\n'
            body += u'会社名: ' + company + u'\n'
            body += u'氏名: ' + name

            mail.send_mail(sender_address, mailAd, subject, body)
            
            self.redirect('/mail_success') 

2013年2月3日日曜日

『100円のコーラを1000円で売る方法』を読んでみた

『100円のコーラを1000円で売る方法』を読んでみました。

 

感想

マーケティング理論素人の俺的には結構おもしろかった。
自分の会社の商品はレッドオーシャンでぐっちゃぐちゃの市場が多いけど、面白い商品もある。売り方がキャズムを意識してないから、セールスが分散されて非効率なんだと思った。

ストーリー形式で、破天荒な女子が主人公になってます。まあ世の社会人にとっては常識はずれということで好きになれない人もいるかもしれませんが、俺的には好きですね。究極的にマーケティング理論を知らない破天荒女子ですので、まさに初心者用のマーケティング本ですね。学べる理論はストーリー仕立てなので多くはない(広く浅い)とも言えると思いますが、初心者には分かり易いし面白いしお勧めです。そもそも分厚いマーケティング理論の本も場合によっては冗長的だったりするでしょうし。

こういう本で手っ取り早く気づきを得るというのも中々いい方法だと思いました。

メモ

  • ほとんどの企業は時間とコストをかけて他社と同じことを一生懸命自社でもやろうとしています
  • バリュープロポジションとは顧客が望んでいて、他社では提供できなくて、自社では提供できる価値
  • 業界が成熟していることは差別化できない理由にはなりません。そう思うのなら頭を使っていない証拠です。
  • あなたのようにお客さんのいいなりになることを、最近マーケティングの世界では”カスタマー・マイオピア”と呼んでいます。
  • 世の中のほとんどの企業は本来価格勝負をしてはいけないのです
  • ちゃんと売れるためには、コミュニケーションが、戦略的に一貫性があることが大切です。

2013年1月20日日曜日

なんと福井舞と福原美穂は別人だった!

僕は今福井舞と福原美穂は別人だったことに驚いてます。これは福井舞本人なんでしょうか??

これは福井舞


2013年1月19日土曜日

twitterでアカウントが凍結される理由

ツイッターアカウント凍結解除情報 TwitterTwitterヘルプセンターを確認しました。
結構細かく決まってますね。それにしてもLivertyメンバーがフォロワーを大量に買っていたというのは笑えました。昔あったご飯たべたいよー的なやつはスパムにはならないのでしょうか?登録アカウントはかなり大量にあったのではないかと思いますが、それぞれのアカウントで誰さんのフォロワーと誰さんがご飯食べたい同士ですっていうつぶやきを1日1回くらい?してたわけですから、かなり類似するつぶやきが至る所で展開されたわけだと思うのですが。まあ全く同じでなければいいんでしょうね。あまり厳密に類似つぶやきを禁止するとキャンペーンとかでも支障をきたすでしょうからね。

それにボットが生き延びているということは、同じ内容の投稿を確実に実施しているはずなので、一定間隔が空いていれば大丈夫なんでしょう。

つぶやきがリンクばっかりでもいけないんですね。あとブロックされたりスパム報告されたりする数が多いと当然ダメですね。広告目的で大量のリンクをつぶやきまくったりするのはやっぱりダメなんですね。つくるなら程よいものじゃないとダメですね。

三浦 大地すごすぎる2

三浦大地歌うまい。ボアも歌うまい。

2013年1月13日日曜日

GAE×Python - Task Queueの研究

Task Queueを研究いたします。

GAEにはcronというものがあります。これは、cron.yamlにタスクと実行頻度を登録しておくことで、バックグラウンドで自動的に登録されたタスクを登録された頻度で実行するものです。

しかしcronでは不十分な状況が想定されます。例えば、WEBサービスの利用者は随時増加していくものですが、その利用者全員分のタスクを一定頻度で実行したい場合などは、cronでは不十分になります。

例を示します。twitterのbotを作成できるwebサービスとしましょう。2分に1回全ユーザがつぶやく必要があります。ユーザが1人であれば、cronで十分対応できます。下記のようにcronに記載すればいいだけです。

cron:
- description: auto tweet
  url: /autotweet
  schedule: every 2 minutes

しかし、ユーザ数が分からないが100人あるいは10000人いるような場合において、全ユーザの自動つぶやきをどのように実行したらよいかを考えた場合、cronでは不十分です。cronは1回の処理が30秒という制限があります。仮にこの制限がなければ、確かにcronでも対応が可能かもしれません。例えばユーザ分つぶやき処理を繰り返せばいいわけです。しかし確実に30秒を超過する(ユーザが多い場合)わけですから、現実的ではありません。cron以外でバックグラウンド処理ができる機能はないのでしょうか?

そこで登場するが、Task Queueというものです。

これはcronとは異なり、プログラム上でタスクを追加できるものです。一度に複数のタスクを追加してよいようです。これであれば、cronで2分に1回auto tweetというページを読み込んで、実行されたauto tweetが全てのユーザ分のつぶやき処理をタスクに追加するということが可能になると思います。

Task Queueの実験として下記のプロジェクトを作成しました。

# -*- coding: utf-8 -*-
import os
import webapp2
import jinja2
from google.appengine.ext import db
import logging
from google.appengine.api.labs import taskqueue

jinja_environment = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))


class TestData(db.Model):
    name = db.StringProperty()
    age = db.IntegerProperty()
    count = db.IntegerProperty()

'''
100ユーザを自動でつくる
key_nameは1〜100、nameは1〜100、ageは全部20、countは全部0
'''
class Init(webapp2.RequestHandler):
    def get(self):        
        test = TestData.all().fetch(1)
        if not test:
            for idx in range(100):
                testData = TestData(key_name = str(idx+1))
                testData.name = str(idx+1)
                testData.age = 20
                testData.count = 0
                testData.put() 

'''
全てのTestDataのカウントをtask queを使ってアップする
'''  
class Test(webapp2.RequestHandler):
    def get(self):
        tests = TestData.all()
        
        for test in tests:
            taskqueue.add(url='/countup', params={'keyName': test.key().name()})

'''
TestDataの1つを与えられたキーをもとに抽出し、カウントアップする
'''            
class CountUp(webapp2.RequestHandler):
    def post(self):
        keyName = self.request.get('keyName')
        testData = TestData.get_by_key_name(keyName)
        testData.count += 1
        testData.put()    

'''
現状の全てのTestDataのカウントを表示する
'''   
class Main(webapp2.RequestHandler):
    def get(self):
        tests = TestData.all()
        
        templateValues = {'tests':tests}
        
        template = jinja_environment.get_template('layout.html')
        self.response.out.write(template.render(templateValues))


logging.getLogger().setLevel(logging.DEBUG)
app = webapp2.WSGIApplication([
    ('/init',Init),
    ('/test',Test),
    ('/countup',CountUp),
    ('/',Main),
], debug=True)

きちんと作動しました。ただ、大量のタスクをキューに一度に追加した場合、これらは並列的に処理されるもんでしょうか?全然分かりません。これが一個一個順番に処理されていくのであれば、一つの処理に2秒かかっていたとすると、10000ユーザー分の処理を実行し終わるのに、20000秒かかります。すなわち、5時間半です。。。それじゃあ、全くもって意味ないですね。使えないプログラムです。2分に1回全ユーザ分の処理を一挙にやりたい。理想的には10000ユーザ分の処理が2秒で終わる状態です。どうもテストしてみた感じだと、順番に実施してる感があるような。もうちょい調べます。

とりあえず、1 つのタスク実行の有効期間が 30 秒に制限されているようです。まあこれは問題ないですね。cronでやろうとしても1処理は30秒に制限されています。あとは、キューについて色々設定できるようです。設定はqueue.yamlというファイルを作成します。rateというので一定時間にどの程度のタスクを処理するかを設定できるようですが、1分間に1万回とか設定しても大丈夫なんでしょうかね?

まあcronよりいいのは確かだし、かなり色々柔軟に設定できるみたいだし、基本的に順番に処理されるようなものの、同時に処理する場合もある的なことも書いてあるから割と期待できそうなんだけど、結論的にはよく分からない。

分からないので、2番目の実験をしてみようと思う。
実験内容としては、上記コードのCountupクラスを修正して、Countupの処理が終わってから2秒間停止させてみようと思う。これが全部順番に実施されるのであれば、キューは3.3分のちょっとした旅を始めるだろう。10000件処理しようとしたら5.5時間の長旅になってしまう。

Countupクラスの修正コードは下記になります。

class CountUp(webapp2.RequestHandler):
    def post(self):
        keyName = self.request.get('keyName')
        testData = TestData.get_by_key_name(keyName)
        testData.count += 1
        testData.put()
        time.sleep(2)

さて、どうなるか?

見事に3分程かかっている。まだ終わらない。やっぱり基本的に順番に実施されるので、一処理に二秒かかるなら、10000件処理には5.5時間かかると思っておいた方がよいのだろうか?それにしてもそれではいかんのだ。もしかしたら設定ファイルにレートを設定したら早くなるかもしれん。やってみよう。

下記のようなqueue.yamを作成してみたが、やっぱり遅い。全く変わらない。

queue:
- name: default
  rate: 10000/s 

ちなみに、1 つのキューについて 1 秒あたり 50 回のタスク呼び出しと、書いてあった。こういう制限があるのか。念のため、rateを50/sに変えてみたが変わらず遅い。しかしこれじゃあ使えないじゃないか。どうしたらいいのだ。

むむ!?アップしたら、まずrateは最大で1秒500回だぞというエラーが出たので500/sに変えて再度アップしたら、すげー速かった。速かったぞ。念のためもう一度試したけど速かった!Google様ありがとう。

2013年1月12日土曜日

三浦 大知すごすぎる

この動画みたら超かっこよくてびびった!!
三浦大地すげえ。尊敬した。

 

2013年1月2日水曜日

twitterスーパー相互フォローツール『Super Neet Follows』をつくりました!

前回自分用のtwitterフォロー自動増加ツールをつくりましたが、今回はWEBサービスとして、twitterスーパー相互フォローツール『Super Neet Follows』をつくりました。


Super Neet Followsの仕組み

Super Neet Followsはログインするとメンバー登録されます。 Super Neet Followsが都度メンバーをランダムでピックアップし、今のフォロー対象者を決定します。 あなたがフォロー対象に選ばれると、あなたは他メンバーから約1分に3回(*)フォローされつづけます! 他メンバー全員があなたにフォローし終わるか、全部で1000フォローされるまでフォローは続きます。 フォローが完了したら、フォロー対象者をSuper Neet Followsがランダムで選び直します。 つまり、メンバー数が多くないときは、完全な相互フォローを速やかに実現すると共に、 メンバー数が1000人を超えてくると、今のフォロー対象者として選ばれたメンバーは、1日で一気にフォロワーが増加する 祭り状態になり得ます!! *メンバー数が一定数を超過するまではフォロー頻度は1分に1回に設定しております。 *一方で中々選ばれないメンバーはフォローだけが増加する可能性もありますので、今後祭り状態を緩和させる可能性があります。

Super Neet Followsが安全・安心な理由

witterに無駄なアクセスをしませんので、twitter APIの呼び出し制限に引っかかることは基本的にありません。 よって内部でAPI制限に伴うエラーが生じてフォローが全く進まないといった相互フォローシステムとして致命的な状況も基本的にあり得ません。 また、フォロー・フォロー解除を1日に多数実施した場合、twitterアカウントを凍結される可能性がありますが、 Super Neet Followsでは、1アカウント当たり最大40フォローまでと、現状においては凍結される想定基準を下回った設計に なっております。さらに、フォロー解除は一切実施しない仕組みになっておりますので、 まさに安全・安心な設計になっています。あと、もちろんメンバー離脱したいときはいつでもアカウント削除できます。 *但し、将来的なtwitterのアカウント凍結基準の変更などに伴う、本サービス使用による凍結の責任は負いませんのでご注意ください。 ちなみに、最初のアカウント凍結はすぐに復活が可能ですし、現時点で私は別サービスで1日50フォロー、50フォロー解除を実施していますが、 全く凍結されません。1日に300フォローしたときは速やかに凍結されましたが。。

tweepyでtwitter apiのエラーメッセージとエラーコードを取得する

tweepyはpython用のtwitter apiを簡単に操作できるモジュールです。ただtweepyはtwitter api 1.1に対応していない為、有志の方が作成されたtweepy2をつかってます。

tweepyのエラーに関して細かい話ですがメモしておきます。

tweepyを経由してtwitter api上でエラーが発生した場合、tweepyからTweepErrorという例外が発動されます。twitterのエラーメッセージをそのまま返す場合もあれば、tweepy独自のエラーメッセージを返される場合もあります。

twitterでエラーが発生すると、理由を説明したテキストと、エラーコードが返ってきます。そのコードを確認すれば、apiの呼び出し制限をオーバーしたのか、アクセストークンが無効なのか、などが分かりますので、エラーの場合は、twitterのエラーコードを取得したいものです。

しかしながら、tweepyのTweepErrorはtwitterのエラーメッセージを全部テキスト化してしまっているようです。 例えば、下記のように適当な日本語文字列を引数に入れて、create_friendshipを呼び出してみますと、フォローできませんのでエラーになります。