SimpleXMLRPCServer

SimpleXMLRPCServerを使ってみます。

  • 引数で (アドレス, ポート) というタプルを与えてSimpleXMLRPCServerのインスタンスを作る
  • register_instance、register_functionで公開するAPIインスタンスに登録
  • serve_foreverでlisten

基本的にはこれだけです。簡単ですね。

# server.py
class Foo:
  def add(self, a, b):
    return a+b

  def get_list(self, *a):
    return [str(i)+'!' for i in a]

  def get_dict(self):
    return {'a': 'apple', 'b': 'banana', 'o': 'orange', }

  def hello(self):
    print 'hello'
    return True

def sub(a, b):
  return a-b

import SimpleXMLRPCServer
SimpleXMLRPCServer.SimpleXMLRPCServer.allow_reuse_address=True
s = SimpleXMLRPCServer.SimpleXMLRPCServer(('localhost', 12345))
s.register_instance(Foo())
s.register_function(sub)
s.register_function(lambda x, y: x*y, 'mul')
s.serve_forever()
# client.py
import xmlrpclib

s = xmlrpclib.ServerProxy('http://localhost:12345')
print s.add(1, 2)
print s.sub(10, 9)
print s.mul(11, 11)
print s.get_list(1, 2, 3, 4, 5, 6, 'foo', 'bar')
print s.get_dict()
s.hello()

実行結果は以下のようになります

> python server.py
> python client.py
3
1
121
['1!', '2!', '3!', '4!', '5!', '6!', 'foo!', 'bar!']
{'a': 'apple', 'b': 'banana', 'o': 'orange'}
> python server.py
localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 -
localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 -
localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 -
localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 -
localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 -
hello
localhost - - [22/Oct/2006 23:22:33] "POST /RPC2 HTTP/1.0" 200 -

localhost - -... というのはアクセスログです。鬱陶しい場合はサーバインスタンス生成時に

s = SimpleXMLRPCServer.SimpleXMLRPCServer(('localhost', 12345), logRequests=False)

とすることでログを表示しないようになります。helloと表示されているのはクライアントがメソッド hello() を呼び出した結果です。
また、

SimpleXMLRPCServer.SimpleXMLRPCServer.allow_reuse_address=True

とすることで サーバ起動 -> 停止 -> サーバ起動 を短い時間で行ったときに出がちなエラー、Address already in use を殺しています。


気がついたことなど

  • サーバに登録するインスタンスメソッド/関数はNone以外の値を返す必要がある
    • hello()で返り値を書かなかったら呼び出し時に例外が起きた
  • 文字列や数値だけではなく、リストや辞書なども結果として返すことができる

xmlrpclib

xmlrpclibを使ってみます。

基本的にはこれだけです。簡単ですね。以下ははてなの公開APIを使った例です。


はてなダイアリーキーワード連想語API

import xmlrpclib

s = xmlrpclib.ServerProxy('http://d.hatena.ne.jp/xmlrpc')
r = s.hatena.getSimilarWord({'wordlist': ['Hatena', 'Python']})
print ', '.join(w['word'] for w in r['wordlist'])

実行結果

ReportLab, はてな伝説, ISBN:, 言語, アプリ, :detail, モジュール, VM, ビルド, Nokia, 付属, シンプル, Guido van Rossum, ...

再帰的にゲット(数が多くなるので最大でも10個ずつに限定)

# getword.py
import sys
import xmlrpclib

def get(s, w, n, p=''):
  for i in s.hatena.getSimilarWord({'wordlist': w})['wordlist'][:10]:
    print p+i['word'].encode('euc-jp')
    if n:
      get(s, i['word'], n-1, p+'-')

s = xmlrpclib.ServerProxy('http://d.hatena.ne.jp/xmlrpc')
get(s, sys.argv[2:], int(sys.argv[1]))

実行例

> python getword.py 2 hatena apple
GUI
-MacOS
--はてなダイアリーFAQ「その他のFAQ」
--2000年
--マルチタスク
--NeXT
--業界
--パーソナルコンピューター
--Mac
--リリース
--クリエイター
--Apple社
-1984年
--柴田あゆみ
--Apple
--Macintosh
--6月13日
--金田美香
--10月7日
--ひまわり
--水泳
--1949年
--3月29日
...

はてなダイアリーキーワード自動リンクAPI

# -*- coding: euc-jp -*-
import xmlrpclib

s = xmlrpclib.ServerProxy('http://d.hatena.ne.jp/xmlrpc')
r = s.hatena.setKeywordLink({'body': 'はてなダイアリーのキーワードをリンクして!',
                             'score': 20,
                             'cname': ['book', 'movie'],
                             'a_target': '_blank',
                             'a_class': 'keyword'})
print r.encode('euc-jp')

実行結果

はてなダイアリーの<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%ad%a1%bc%a5%ef%a1%bc%a5%c9">キーワード</a>をリンクして!

はてなダイアリーキーワード連想語APIで取得したキーワードをはてなダイアリーキーワード自動リンクAPIでリンクする(全カテゴリのキーワードをリンクするためにcnameを省略)

# getword_setlink.py
import sys
import xmlrpclib

s = xmlrpclib.ServerProxy('http://d.hatena.ne.jp/xmlrpc')
for w in s.hatena.getSimilarWord({'wordlist': sys.argv[1:]})['wordlist']:
  print s.hatena.setKeywordLink({'body': w['word'].encode('euc-jp'),
                                 'a_target': '_blank',
                                 'a_class': 'keyword'}).encode('euc-jp')

実行例

> python getword_setlink.py りんご
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%c5%a3%b5%dc%cd%fd%b7%c3">釘宮理恵</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%ea%a5%f3%a5%b4">リンゴ</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/TOWER%20RECORDS">TOWER RECORDS</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/HIPHOP">HIPHOP</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%ec%a5%d3%a5%e5%a1%bc">レビュー</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/2004%c7%af">2004年</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%a2%a5%eb%a5%d0%a5%e0">アルバム</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/ken">ken</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%c8%af%c9%bd">発表</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/bounce">bounce</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%c0%a5%e1%a5%ec%a5%b3">ダメレコ</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/MC">MC</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/Da%2eMe%2eRecords">Da.Me.Records</a>
<a class="keyword" target="_blank" href="http://d.hatena.ne.jp/keyword/%a5%ec%a1%bc%a5%d9%a5%eb">レーベル</a>

気がついたことなど

  • APIに食わせた結果として返ってくる文字列はunicode
    • printするだけなら勝手にコンソールの文字コードエンコードされる
    • リダイレクト(python getword_setlink.py りんご > r とか)したいときなどは明示的にencodeする必要がある
  • APIに日本語を食わせるときはunicodeのままではなく、何らかの文字コードにencodeする必要がある(だけど何故かutf8だと化けることがある)
    • ファイルの先頭で -*- coding: euc-jp -*- と書いて、同じ文字コードで保存すると、ファイル中の日本語は指定した文字コード(この場合はeuc-jp)であると認識される
    • sys.argvで日本語を受け取った場合、コンソールの文字コードになる