風柳メモ

ソフトウェア・プログラミング関連の覚書が中心。

「サイトのはてなブックマーク合計を画像/JSONで取得するAPI」のPythonソースコード

はじめてのGoogle App Engineアプリ/Pythonプログラミング

なので、記念に(笑)ソースもさらしておきます。

# -*- coding: utf-8 -*-
"""
getTotalHB.py for Google App Engine
  ■概要
    はてなブックマークの被ブックマーク合計数取得API結果をJSONまたは画像で取得
  ■使い方
    [JSON] http://furyu-tei.appspot.com/getTotalHB?uri=(取得サイトのURI)&callback=(コールバック関数名)
    [画像] http://furyu-tei.appspot.com/hb/total/(取得サイトのURI)
"""
import sys,os,cgi,re
import urllib,xmlrpclib
import wsgiref.handlers
import gae_xmlrpc

from xmlrpclib import ServerProxy
from google.appengine.ext import webapp
from gae_xmlrpc import GAEXMLRPCTransport
from google.appengine.api import urlfetch

ICON_BASE='http://b.hatena.ne.jp/images/users/normal/%05d.png'
XMLRPC_HB='http://b.hatena.ne.jp/xmlrpc'
CONTENT_TYPE_JS='application/x-javascript; charset=utf-8'

class getTotalHB(webapp.RequestHandler):
  def get(self):
    uri=self.request.get('uri')
    if uri=='':
      uri=self.request.get('url')
    if uri:
      try:
        proxy=ServerProxy(XMLRPC_HB,GAEXMLRPCTransport())
        count=proxy.bookmark.getTotalCount(uri)
      except:
        count=0
    else:
#      self.error(404)
#      return
      uri=''
      count=0
    
    icount=count if count<10000 else 9999
    icon=ICON_BASE % (icount)
    
    json='{uri:\"'+uri+'\",count:'+`count`+',icon:\"'+icon+'\"}';
    callback=self.request.get('callback')
    if callback: json=callback+'('+json+');'
    
    self.response.headers['Content-Type']=CONTENT_TYPE_JS
    self.response.out.write(json)

class counterImage(webapp.RequestHandler):
  def get(self):
    uri=urllib.unquote(re.compile('^.*?/total/').sub('',self.request.url))
    uri=re.compile('%23').sub('#',uri)
    if uri:
      try:
        proxy=ServerProxy(XMLRPC_HB,GAEXMLRPCTransport())
        count=proxy.bookmark.getTotalCount(uri)
      except:
#        self.error(404)
#        return
        count=0
    else:
      count=0
    icount=count if count<10000 else 9999
    icon=ICON_BASE % (icount)
    img=urlfetch.fetch(url=icon,method=urlfetch.GET)
    if img.status_code==200:
      self.response.headers['Content-Type']=img.headers['Content-Type']
      self.response.out.write(img.content)
    else:
      self.error(404)

def main():
  application = webapp.WSGIApplication([
    ('/hatena/bookmark/getTotalHB', getTotalHB)
  , ('/getTotalHB', getTotalHB)
  , ('/hatena/bookmark/total/.*', counterImage)
  , ('/hb/total/.*', counterImage)
  ],debug=True)
  wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
  main()

XML-RPC API使用ではまったので、覚書

Google App Engineではxmlrpclib(ServerProxy)がそのままでは使えない

PythonでXML-RPC APIを使う場合、

import xmlrpclib
from xmlrpclib import ServerProxy

としておいて、xmlrpclibを使えるようにし、

Result=ServerProxy(API_uri).methodName(parameters)

のようにしてコールするのが定石(?)みたいなのですが、Google App Engineでこれをやると、

  File "C:\Python25\lib\httplib.py", line 666, in connect
    for res in socket.getaddrinfo(self.host, self.port, 0,
AttributeError: 'module' object has no attribute 'getaddrinfo'

みたいな感じで怒られて、使用できません。


(おそらく)セキュリティ的な制限で、socketを自由に作れなくなっているために発生するエラーだと思われます(通信的な機能は、google.appengine.apiで提供されるurlfetch.fetchを介してのみ使える、ということでしょう、多分)。

Google App Engineでもxmlrpclib(ServerProxy)を使うには

で、きっと解決策があるはず……と思って探してみたら、ありました。

Brizzled: Making XML-RPC calls from a Google App Engine application
We can create our own transport class that uses the google.appengine.api.urlfetch module's fetch() method instead of standard socket access.

xmlrpclibで使用するtrasport classを(socketを使用した)標準のものから、Google App Engineのurlfetch.fetchを使用したものに置き換えることで、使用可能にする、という主旨にとりました。


というわけで、同ページの"The GAEXMLRPCTransport class"のところに書かれているソースをそのまま貼り付けたgae_xmlrpc.pyというファイルを作って、これをGoogle App Engineのアプリケーションディレクトリのトップに置き、

import gae_xmlrpc
from gae_xmlrpc import GAEXMLRPCTransport

という記述を追加してインポートし、XML-RPCコール箇所を

Result=ServerProxy(API_uri,GAEXMLRPCTransport()).methodName(parameters)

のように変更することで、無事動作するようになりました。