2014/09/25

HTTPロードバランサが公開プレビューに


限定プレビュー版だったGoogle Cloud Platform のロードバランサが誰でも使える公開プレビュー版になったようです。

このロードバランサの凄いところは、Google検索やGmail、Youtube等の主要Googleサービスと同じインフラに乗っかっているので、圧倒的なパフォーマンスを低価格(約1800円/月)で提供できているという事。また、ロードバランサに対しての監視や設定もRESTful APIが用意されているので、アプリケーション側から動的&柔軟な対応もできる。

リリースされれば、今後Google Cloud Platformの目玉のひとつになるかもしれない。
それにしても、インフラのコストはどんどん安くなるなぁ・・・あとはこのロードバランサに不満を持つほどのWebサービスを作るだけだ。

以下、関連記事まとめ。

Googleの中の人による解説: GoogleのHTTPロードバランサーの破壊力があり過ぎる #gcpja
国内メディア記事: Google Cloud PlatformのHTTPロードバランサーがオープンプレビューに。ウォームアップなしで100万リクエスト/秒に対応する性能、1つのグローバルIPで複数リージョンに分配

2014/09/24

Google Custom Search を使って評価の低い(高い)レビューを検索する

前回記事に続き、Google Custom Search API を使って、もっと詳細に条件を指定する方法をメモ。

Google Custom Searchの公式ドキュメントによると、構造化データの情報を指定して、さらにフィルタリングができるみたい。

To filter by attribute, add a more:pagemap:TYPE-NAME:VALUE operator to a search query.

とあるので、検索キーワードに、more:pagemap:属性名:属性値を追加すればOK。

APIの戻り値を見てみると、価格.comのレビューの評価数の属性値は、review-ratingstarsでとれるので、例えば、評価数3のレビューのみ取得したい場合は、検索キーワードmore:pagemap:review-ratingstars:3.0とすることで、フィルタリングすることができる。また、値をカンマで区切る事でOR条件も指定できる。例えば、評価4と5も含めたい場合はmore:pagemap:review-ratingstars:3.0,4.0,5.0と書ける。前回のフォームに、評価数を指定するプルダウンを設置してみた。ソースはこんな感じ。

入力フォーム

<form class="form-horizontal" role="form" action="search" method="post">
    <div class="form-group">
      <label class="col-sm-2 control-label" for="keyword">キーワード</label>
      <div class="col-sm-10">
        <input type="text" id="keyword" name="keyword" class="form-control" >
      </div>
    </div>
    <div class="form-group">
      <label for="stars" class="col-sm-2 control-label">評価</label>
      <div class="col-sm-10">
        <select class="form-control" id="stars" name="stars">
          <option value="">指定しない</option>
          <option value="1.0">★☆☆☆☆</option>
          <option value="2.0">★★☆☆☆</option>
          <option value="3.0">★★★☆☆</option>
          <option value="4.0">★★★★☆</option>
          <option value="5.0">★★★★★</option>
        </select>
      </div>
    </div>
    <button type="submit" class="btn btn-primary">検索</button>
</form>

リクエストハンドラ 

#!-*- coding:utf-8 -*-
#!/usr/bin/env python
import json
import logging
import urllib
from google.appengine.api import urlfetch
from lib.controller import *

class Top(Controller):
    def get(self):
        self.draw_template('front/cse/top.html')

class Search(Controller):
    def post(self):
        # キーワードを得る
        keyword = self.request.get('keyword')
        # 評価が指定されていればキーワードに追加
        stars = self.request.get('stars')
        if stars:
            keyword += ' ' + 'more:pagemap:review-ratingstars:%s' % stars
        # urlエンコード
        keyword = urllib.quote(keyword.encode('utf-8'))
        # リクエストパラメータ組み立て
        url = 'https://www.googleapis.com/customsearch/v1'
        url += '?key=%s' % 'YOUR_API_KEY'
        url += '&cx=%s' % '000122034385005128488:etmrnaufuww'
        url += '&q=%s' % keyword
        # 検索
        result = urlfetch.fetch(url)
        logging.info(url)
        items = list()
        if not 200 <= result.status_code <= 299:
            # エラー
            logging.error('google custom search error: %s' % str(result.status_code))
            logging.error(result.content)
        else:
            # 結果を得る
            content_dict = json.loads(result.content)
            items = content_dict.get('items', list())
        self.set_template_value('items', items)
        self.draw_template('front/cse/search.html')

url_map = [
    ('/cse/top', Top),
    ('/cse/search', Search),
]
application = webapp.WSGIApplication(url_map, debug=True)

ちなみにAPI越しじゃなくても、Custom Search の追加キーワードでも指定できるみたい。ただ、この属性名がreview-ratingstarsとなっているのが、Google独自の命名なのか、構造化ルールでの命名なのかが不明。。。このあたりはもう少し調べてみます。

Google App Engine SDK Version 1.9.12 リリース

Google App Engine SDK のバージョン 1.9.12 がダウンロード可能になっています。

が、、、リリースノートは更新されていない様子。更新されたら追記します。

【追記】
リリースノート更新されてました。pythonは以下の2点が更新されたようです。

  • libxsltライブラリのバージョンがv1.1.22からv1.1.28に。
  • NDBでクエリを実行した時に「AugmentedQuery’ object has no attribute ‘filter_predicate’」というエラーが発生する問題を対応。

Python版については、ここしばらくマイナーアップデートが続いていますね。そろそろアジアリージョン来て欲しい!

2014/09/16

Google App Engine Version 1.9.11 リリース(Python)

4日前ですが、Google App Engine SDK Version 1.9.11 がリリースされています。今後はリリース情報も掲載していく予定です。とりあえずPythonのみ。

Python版では、Search API の日付順の結果が正しく返却されるようになったようです。

Google App Engine Python and PHP SDK Release Notes

Google Compute Engine入門を読んで


Google Cloud Platform(GCP)関連の情報をWeb検索すると、ほとんどと言っていいほどヒットする会社のサイトがある。吉積情報株式会社さんの関連サイトだ。Google関連の事業に力を入れられているようで、このたび、代表の方がGoogle Compute Engineの入門書「Google Compute Engine入門」を出版されたとの事で早速読んでみた。
内容はざっくりとこんな感じ。
  • Google Compute Engineの概要と導入手順
  • コマンドリファレンス
  • AWSとの比較
  • その他
まず、Web公式ドキュメントは全て英語なので、日本語での解説本というのはありがたい。書いてあるとおりにやれば、簡単にGCPのインスタンスにログインするところまではできる。

コマンドリファレンスまでが本書のページ数の8割強を占める。GCPはガッツリ使っていないので、リファレンス本としてはまだ出番は無さそうかな。

個人的には、機能、価格、性能(ベンチ)までとったAWSとの比較はとても参考になった。仕事でも「AWSでいいじゃん」とよく言われる事があるので、このAWSとの比較記事だけでも本書の値段分の価値はあると思っている。

先日Googleが、日本でも本格的にGCPを展開すると明らかにしていたけれど、まだまだWebや書籍の情報量が少ない。特に日本語の。だ。国内で本格的に展開するには、日本語のドキュメントが充実しているかどうかにかかっていると個人的には考えている。あとは国内の導入事例やサポート体制の情報が欲しい。

それにしてもGoogle関連の技術書の陳腐化は早い(iOSもだけど)。しょうがないことだけど、この本も来年には「こないだまではそうだったね」という部分がでてくるだろう。その時はぜひ第2版を!

2014/09/11

無料でGoogle検索を自動化する

プログラムから自動でGoogle検索して、結果を収集したい時がたまにある。

今回は、Google App Engine と、Google Custom Search APIを使って、Google検索を自動化する方法をメモ。サンプルでは、価格.comのレビューのみを取得するフォームを作ってみた。

Google Custom Search API は、Google Custom Search サービスを外部から操作できるAPI。100リクエスト/日まで無料だ。それ以上リクエストしたい場合は、1000リクエスト毎に5ドル支払えば検索可能となる。1日100件ぐらいであれば、Google Appe Engine側も無料範囲で十分まかなえる量だ。

Custom Search APIを有効にする

まずは、Custom Search APIが使えるように、有効化とAPIキーを取得する。

Google Developers Console にログインして、該当のプロジェクトを選択し、「APIと認証」から「API」メニューを選択する。
APIメニューを選択

APIの一覧からCustom Search APIを探し、右のボタンを押す。
APIの有効化

ちなみに、100リクエスト/日以上検索したい場合は、API名を選択後に表示される画面で割り当てを選択し・・・
API割り当ての選択

ここのSet billable limitsから、1日のリクエスト可能数を引き上げる事ができる。(※プロジェクトの課金を有効にしておく必要がある。)
enter image description here

続いてAPIにアクセスするためのキーを取得する。「APIと認証」から「認証情報」を選択する。
認証情報

次に表示される画面で、「新しいキーを作成」から「サーバーキー」を選択する。OAuthは、例えばユーザー情報を取得するようなAPIを使用する時に選択する。今回は検索のみを使用するので、サーバーキーでOKだ。
新しいサーバーキーの作成

許可IPアドレスの設定は今回空欄とした。特定のIPアドレスのサーバーからのみキーを有効としたい場合は設定する。
許可IPアドレスの設定

これでキーが作成できた。このキーはプログラムからAPIへ接続する際に使用する。
APIキーの完成

Google Custom Searchを作成する

APIキーを取得したら次は Google Custom Search で検索エンジンを作成する。Custom Search APIはここで作成した検索エンジンを操作するAPIだ。

Google Custom Search へアクセスし、新しい検索エンジンを作る。
新しい検索エンジンの作成

検索対象のサイトドメインを登録する。かならず1つのドメインを指定する必要がある。今回はとりあえず kakaku.com(あとで変更する) を入力し、検索エンジンの名前を指定。
ドメインの登録

これで検索エンジンが完成。コントロールパネルへアクセスする。
検索エンジンの完成

ここから検索エンジンのIDを取得しておく、IDもAPIキー同様にプログラムからAPIへアクセスする場合に必要。
enter image description here

今回は、価格.comのレビューのみを取得する事が目的なので、その他のページがヒットしないよう、レビューページのURLのみをターゲットにする。対象サイトを選択して・・・
enter image description here

レビューページのURLを入力する。商品IDなど動的に変わる部分は*でワイルドカード指定ができる。
enter image description here

これで、価格.comのレビューページだけがヒットする検索エンジンが完成した。あとはこいつをプログラムから呼び出す処理を実装する。

検索処理を実装する

まずはキーワードを入力するフォーム。

<form role="form" method="post" action="search">
  <div class="form-group">
    <label for="keyword">検索キーワード</label>
      <input type="text" id="keyword" name="keyword">
  </div>
    <button type="submit" class="btn btn-default">検索</button>
</form>

次にリクエストハンドラ。キーワードを取得して、しかるべきパラメータでAPIのURLへ送信。戻り値はJSON。※APIの詳細は、Google公式ドキュメントを参照。

import json
import logging
import urllib
from google.appengine.api import urlfetch
from lib.controller import *


class Top(Controller):
    def get(self):
        self.draw_template('front/cse/top.html')


class Search(Controller):
    def post(self):

        # キーワードを得る(URLエンコードしておく)
        keyword = self.request.get('keyword')
        keyword = urllib.quote(keyword.encode('utf-8'))

        # リクエストパラメータ組み立て
        url = 'https://www.googleapis.com/customsearch/v1'
        url += '?key=%s' % '生成したAPIキー'
        url += '&cx=%s' % '作成した検索エンジンID'
        url += '&q=%s' % keyword

        # 検索
        result = urlfetch.fetch(url)

        logging.info(url)

        items = list()
        if not 200 <= result.status_code <= 299:
            # エラー
            logging.error('google custom search error: $s' % str(result.status_code))
            logging.error(result.content)
        else:
            # 結果を得る
            content_dict = json.loads(result.content)
            items = content_dict.get('items', list())

        self.set_template_value('items', items)
        self.draw_template('front/cse/search.html')


url_map = [
    ('/cse/top', Top),
    ('/cse/search', Search),
]

application = webapp.WSGIApplication(url_map, debug=True)

itemsに検索結果のリストが格納される。

Googleは、商品情報や、レビュー、レシピなど、特定の情報については、ルールに従ってHTML(構造化データ)を組んでおけば、意味のある情報としてインデックスしてくれる。たとえば、レビューのタイトルや評価の星の数などだ。

価格.comやヨドバシなどそれなりのショッピングサイトは、この構造化データに準じてサイト構築してくれており、これらのサイトに対して検索した Custom Search API の戻り値も構造化されていて参照しやすい。

つまり、戻されたJSONデータの中には、レビューのタイトル、本文(一部)、評価数(星の数)が、(ほぼ)おなじキー名で格納されているので取り出しやすいのだ。

ちょっと話は脱線したけど、ここまでできれば後はcronで自動化したりと自由自在だ。

実はこの構造化データの値も条件としてフィルタリングできるので、「価格.comの星1つのレビューのみ抽出」といった事も可能となる。この方法はまた次の機会に。。。

2014/09/10

Google App Engineでファイルを保存する方法3つ


Google App Engineでは、通常のWebアプリケーションのように、サーバー上のファイルシステムへアクセスすることができない。別の方法でファイルの読み書きをする事になるけど、方法はいくつかあるので、それぞれの方法と長所短所をまとめてみた。サンプルと全てのソースコードはこちら。(サンプル/ソースコード)

方法1:BlobPropertyを使う

ndb(db)のBlobProperty(https://developers.google.com/appengine/docs/python/ndb/properties)ではバイナリデータをそのまま格納できる。

まずは保存するModelの定義を実装する。
from google.appengine.ext import ndb
class UserFileModel(ndb.Model):
    """
    ファイル格納モデル
    """
    file_data = ndb.BlobProperty()                  # ファイルデータ

続いて、アップロードフォーム。
<form>
<div class="form-group">
<input id="exampleInputFile" name="file_data" type="file" />
    </div>
<button class="btn btn-default" type="submit">保存</button>
</form>

アップロードハンドラを実装。リクエストされたデータをそのまま突っ込むだけ。
def post(self):
        file_data = self.request.get('file_data')
        user_file = UserFileModel()
        user_file.file_data = file_data
        user_file.put()

一番お手軽な方法だけど、1エンティティの上限が1MBまでという制限があるので注意が必要。それより大きいファイルを保存しようとするとこんなエラーが出る。

RequestTooLargeError: The request to API call datastore_v3.Put() was too large.

BlobPropertyではzip圧縮オプション(compressed=True)が使えるので多少容量を抑えることができる。ただ、圧縮しない時よりデータ読み書き時にCPUを消費するのでそこはトレードオフだ。

方法2:Blobstoreを使う

Blobstoreとは、Googleが提供するキー・バリュー型のデータストアサービス。上のBlobPropertyと名前が似てるけど、厳密にはGoogle App Engineとは別領域のサービスだ。

まずはアップロード先URLの生成する。Blobstoreでは、最初にアップロード先のURLを生成しておく必要がある。生成にはgoogle.appengine.ext.blobstoreライブラリの create_upload_url を使う。

upload_url = blobstore.create_upload_url('/file_save/upload2')

引数の /file_save/upload2 は、アップロード後にリダイレクトされるパスを指定する。

続いて、アップロードフォームを実装する。上で生成したアップロードURLがPOST先となるようにする。
<form action="{{ upload_url }}" enctype="multipart/form-data" method="post" role="form">
<div class="form-group">
<input id="exampleInputFile" name="file_data" type="file" />
    </div>
<button class="btn btn-default" type="submit">保存</button>
  </form>

生成したアップロードURLはこんな感じになる。
http://gcp-memo.appspot.com/_ah/upload/AMmfu6aKr-VcNuFlEljprrh7rkbKnBA7WndhyciPTGolHiRkxVq670JENWihVEWViTxRue0K5Y10fOFpmnFzF4hn7qBMIZSn8vhmDT55aO5jNMPqEeQL46R6iWQvEps64_i8WQJsXAHl/ALBNUaYAAAAAVA_q4rzSwstBg1S6i5l3FpfHv4N0EdAR/

/_ah/upload はGoogle App Engineで決まっているシステム用のパスだ。ここでアップロードが行われた後、create_upload_urlで指定したパスに302リダイレクトされる。

アップロード後にリダイレクトされる処理を実装する。ここでBlobstoreに保存したファイルの情報が取れるので、キーを格納したりできる。このハンドラは、google.appengine.ext.webapp.blobstore_handlers.BlobstoreUploadHandlerクラス を継承しておく必要がある。
class Upload2(blobstore_handlers.BlobstoreUploadHandler):
    def post(self):
        upload_files = self.get_uploads('file_data')
        blob_info = upload_files[0]
        self.redirect('/file_save/upload2_done?key=%s' % blob_info.key())

先にアップロードするURLを生成する必要があったり、アップロード後にリダイレクトされたりとちょっと扱いにくい印象。保存したファイルを参照する方法はまたの機会に。

方法3:Google Cloud Storageを使う

Google Cloud Storageは、Google Cloud Platformで提供されているストレージサービスだ。Google App Engineからも簡単にアクセスすることができる。

アップロードフォーム。
<form action="upload3" enctype="multipart/form-data" method="post" role="form">
<div class="form-group">
<input id="exampleInputFile" name="file_data" type="file" />
  </div>
<button class="btn btn-default" type="submit">保存</button>
  </form>

アプロードハンドラを実装。通常のPythonアプリケーションの様に、ストレージ上のファイルの読み書きができる。ファイル操作には、Googleから提供されている Google Cloud Storage Client Libraryを使用する。(ダウンロードしてGAEアプリケーションに組み込んでおく必要がある)。

また、blobstore.create_gs_key 関数を使用すると、blobstoreライブラリで利用できるキーを生成することができる。blobstoreライブラリを使うと大きめのファイルのダウンロード処理が簡単にできるようになるので覚えておきたい。

関連ライブラリがごちゃごちゃしている感があるけど、通常のファイル操作と同じように扱えるため利用しやすい。
import lib.cloudstorage as gcs
# (中略)
class Upload3(Controller):
    def post(self):
        # ファイル名とファイルデータを得る
        file_name = self.request.POST['file_data'].filename
        file_data = self.request.get('file_data')
        # このアプリケーションのデフォルトGCSバケット名を得る
        bucket_name = app_identity.get_default_gcs_bucket_name()
        # 保存パスを作成
        filepath = '/' + bucket_name + '/file_save/' + file_name
        # ファイル作成
        gcs_file = gcs.open(filepath, 'w')
        gcs_file.write(file_data)
        gcs_file.close()
        gcs_key = blobstore.create_gs_key('/gs' + filepath)
        self.set_template_value('message', gcs_key)
        self.draw_template('front/file_save/done.html')

まとめ

総合的に見るとGoogle Cloud Storageが一番良い。1MB未満のデータで無料枠で収まるようなアプリならBlobPropertyがお手軽でいいかも。Blobstoreを使うメリットは無さそうかな。
方法 扱いやすさ 容量制限 ストレージ価格(GB/月)
BlobProperty 1MB $0.18
Blobstore × 2GB $0.026
Google Cloud Storage ほぼ無制限 $0.026