読者です 読者をやめる 読者になる 読者になる

COBOL技術者の憂鬱

COBOLプログラマは不在にしています

開発記録のまとめ


連載プログラミングの「クックパッド人気エントリー」編ですが、最後にまとめ記事を書いて終わりにしようと思っていたところ、すっかり遅くなってしまいました。
開発完了してから三週間ほど経過し、おかげさまでアクセスの方も順調に伸びていっています。
前2作がダメダメな感じだったのですが、ここへきてようやく「使えるサービス」を提供できるようになってきたかなーという感触を得ています。
それでは、開発中に感じたことや考えたこと等について整理していきましょう。



○画像の取得について
レシピを一覧表示させる際に、画像も添えて表示できるようにしたいと思い、当初はクックパッドの画像を直接表示させるようにしていました。ですが、さすがにそれは色々とマズイと思って、初回クロール時にデータと一緒に画像も取得してくるように修正を行いました。
クックパッドから取得してきた画像は大きすぎるので、その時点で縮小しています。
GAEには画像のリサイズ機能があるので、それを利用するだけで簡単にできました。


【get_cookpad_recipe.py】

def get_resizedimage(url,width,height):
    if not url:return

    image_url = 'http://' + url
    get_image = urlfetch.fetch(image_url,deadline=10)

    if get_image.status_code == 200:
        return images.resize(get_image.content,width,height)

で、縮小した画像を直接データストアに保管してしまいます。
私のイメージでは、画像はファイルシステム上のディレクトリに保存するようになっていて、データストアにはそこへのパスを保管するのかなと思っていたのですが、もう直接放り込めてしまうんですね。このあたりはすごくラクチンでした。
ちなみに、画像を取得するのは初回クロール時のみで、以降はURLが変わらない限り、クックパッドのサーバーに取りにいくことはありません。
こんなところにも匠の優しい心遣いが表れています。


【get_cookpad_recipe.py】

def put_entry(row):

#(省略)

    if not entry.photo_url == row['photo_url']:
        entry.photo_url = row['photo_url']
        entry.photo_image = get_resizedimage(row['photo_url'],100,100)

#(省略)

    entry.put()

画像を表示させる時に、ちょっとややこしいことをしないといけないので、このあたりの意味が最初よくわからずに混乱してしまいました。
画像のリクエストURLを受け取って、画像を返す処理を独自に用意しておかないといけないんですね。


【show_entry.py】

class ThumbNail (webapp.RequestHandler):
    def get(self):
        entry = datastores.Entries.get(self.request.get("key"))
        if entry.photo_image:
            self.response.headers['Content-Type'] = "image/png"
            self.response.out.write(entry.photo_image)
        else:
            self.error(404)


○関数の名前について
前2作については一本道のだらだらコードだったのですが、今回はきちんと機能分割して、関数を組み合わせて処理を行うようにしました。
関数の名前をつける際には、動詞と目的語を組み合わせて「何々をどうする」で命名するようにしてみました。
すると、全ての関数の名前が「getなんたら…」「putなんたら…」で統一されてしまいました。
なるほどプログラム全体を、「入力→処理→出力」のイテレーションと考えると、自然とそういう名前の付け方になってしまいますよね。
こうしておくことで、「俺はここまでやったから、後はおまえに任せるぞー」という感じで、全体的に見通しがよくなったような気がします。
最後はメソッドチェーンで決まると非常に気持ちがよいですね。テトリスで4段一気に消しにいく時のような感じです。


【get_cookpad_recipe】

if __name__ == "__main__":
    put_entry(get_recipe(get_entry()))


○テンプレートについて
GAEはデフォルトでDjangoのテンプレートが使えることは知っていたんですが、使いどころがよくわからなくて今まで敬遠していました。
こういうのって、デザイナーさんとプログラマーさんがきっちりと役割分担できている環境で、それぞれ同時並行で作業を進める際に便利なんでしょうが、私の場合はあまり必要ないかなーと思っていたのです。


けれども、今回初めてテンプレートを使ってみて、これはやっぱり便利だなと、認識を改めさせられてしまいました。
画面描画に必要な素材を全部掻き集めてきて、「俺はここまでやったから、後はおまえに任せるぞー」と、テンプレートエンジンに残りの処理を託す。このあたりの男の友情というかなんというか、傍らで見ていて胸が熱くなってきますよね。
PYTHONのコードの中にHTMLが混ざらなくなるので、見た目がスッキリするという効果もあるし、これからも積極的に使っていこうと思います。


【show_entry.py】

class MainPage(webapp.RequestHandler):
    def get(self):
        inputparm = get_inputparm(self)
        showparm = get_showparm()
        checkedparm = check_parm(inputparm,showparm)

        gqlsentence = make_gqlsentence(checkedparm)
        query = datastores.Entries.gql(gqlsentence)

        limit = 25
        offset = int(checkedparm['offset'])
        fetched_entries = query.fetch(limit,offset)

        fetched_count = len(fetched_entries)
        pager = make_pager(limit,offset,fetched_count)

        template_values = { 'entries' : fetched_entries,
                                        'showparm' : showparm,
                                        'inputparm' : checkedparm,
                                        'pager' : pager}

        path = os.path.join(os.path.dirname(__file__), 'index.html')
        self.response.out.write(template.render(path, template_values))


○検索メニューの表示について
検索メニュー内のセレクトボックスですが、そこのカテゴリーを開くと、当然ですがずらりとカテゴリが一覧表示されます。
実はこれ、こっそりと裏側で、選択されたカテゴリの回数をカウントしていて、よく利用されているカテゴリについては上位に表示されるようになっています。こんなところにも匠の(略)


【show_entry.py】

def check_parm(inputparm,showparm):

#(省略)

    for k, v in inputparm.iteritems():
        if k == 'category':
#categoryは半角数字のみ
            if is_num(v):
#categoryの存在チェック、区分と名称を取得、ヒット数をカウントアップ
                query = datastores.Categories.gql("WHERE category_id = '" + v + "'")
                category = query.get()
                if category:
                    key = 'category' + category.category_kubun + '_id'
                    checkedparm[key] = v
                    checkedparm['category_name'] = category.category_name
                    category.hit_count += 1
                    category.put()

#(省略)

カテゴリのメニュー表示については、数が多いのでどのように表示したものか、当初ものすごく迷っていたんですが、結局、よくユーザーから選択されているものを上位に表示させればいいやということになって、そこで落ち着いています。
こういう内部の処理は結構簡単にできるんですが、いざ表示させる段階になってあれこれ失敗してしまいました。
Jqueryとやらを使ってスマートにフォーム表示させようとしたんですが、長いカテゴリ名称を選択すると表示が崩れてしまったりと、このあたりで結構苦戦して、結局Jqueryは諦めてしまいました。JSとか何も知らないのに、ちょっと無茶でしたね。
このあたりのフロント部分を作り込んでいく際のスキル不足が、私にとっての課題ですね。まぁ、課題認識しながら早くも五年くらい経ってしまっているわけですが…どっかで、JSとか時間取って勉強してみたいなぁ。




○クックパッド側のHTMLが変更になったことについて
リリースして間もなく、クックパッド側のレシピ画面のHTMLが変更になり、こちらからの取得処理がうまくできなくなってしまいました。
正規表現を修正するだけで対応できたのですが、WebAPIが提供されているわけではないので、今後も同じことが起こることが予想されます。
そこで、あまり度々変更が起こるようであれば、取得処理がおかしくなった時点で、私宛にメールを飛ばすという処理を追加しようと思っています。




とまぁ、ざっくばらんに整理するとこんな感じです。
以上で、連載プログラミング「クックパッド人気エントリー」編を終了したいと思います。


次回作では、久しぶりに動画を扱ってみたいと思っています。
動画を使ったWebサービスといえば、私にとってはRetroTubeという忘れられない思い出深いサービスがあるのですが、あれを作っていた頃から既に4年以上経過しています。
現在の自分のスキルと、Web上で利用できるテクノロジーを使って動画サイトを作ると、一体どんなものができあがるのでしょうか。
そのあたりのことも含めた実験という意味で、再度挑戦してみたいと考えています。