2008年8月30日土曜日

Python: はてなブックマークのコメント一覧非表示機能について

このブログ記事をはてなブックマークに追加

はてなブックマークのコメント一覧を非表示にする機能が追加された。しかし、新着ブックマークからはコメントが読めてしまう。

例えば、はてなブックマークのコメント一覧非表示機能テストのページはコメントを非表示に設定しているが、その新着ブックマークのページを開くとコメントが表示されていることが分かる。さらに、下記のPythonスクリプトを

hatena_get_comments.py http://d.hatena.ne.jp/fk_2000/20080829/p1

のように実行させると、すべてのコメントとそのユーザ名を表示することができる。

これをGoogle App EngineでWebサービスとすることは簡単だろう。Webサービスの公開はモラルに欠けるようにも思うのでやめておくが、本当にコメントを読めなくするつもりなら今回の非表示機能は中途半端と云わざるを得ない。

因みに、個人的にはコメントを非表示にすることについては賛成とも反対とも考えておらず、あまり関心がない。しかし、はてなブックマークの存在意義にも関わってくるような機能についてはもう少し議論・熟考したほうが良いようにも思える。

hatena_get_comments.py

#!/usr/bin/env python # -*- coding: utf-8 -*- import sys, os, urllib, urllib2, xmlrpclib from HTMLParser import HTMLParser class HatenaHTMLParser(HTMLParser): is_user = False is_comment = False comments = [] current_user = "" current_comment = [] def handle_starttag(self, tag, attrs): if tag == "dd" and ("class", "bookmarker") in attrs: self.is_user = True elif tag == "span" and ("class", "comment") in attrs: self.is_comment = True def handle_endtag(self, tag): if self.is_user and tag == "dd": self.is_user = False if self.is_comment and tag == "span": self.is_comment = False self.comments.append((self.current_user, "".join(self.current_comment))) self.current_comment = [] def handle_data(self, data): if self.is_user: self.current_user = unicode(data) if self.is_comment: self.current_comment.append(unicode(data)) def get_comments(self): return self.comments def main(args): if len(args) < 2: print >>sys.stderr, u"Usage: %s ブックマークされているURL" % os.path.basename(args[0]) sys.exit(1) cnt = xmlrpclib.ServerProxy("http://b.hatena.ne.jp/xmlrpc").bookmark.getCount(args[1])[args[1]] url = "http://b.hatena.ne.jp/bookmarklist?url=%s" % urllib.quote_plus(args[1]) parser = HatenaHTMLParser() for i in range(0, cnt, 25): data = urllib2.urlopen(url + "&of=%d" % i).read() parser.feed(data) for user, comment in parser.get_comments(): print user, comment.encode("mbcs") if __name__ == "__main__": main(sys.argv)

続きを読む...

2008年8月29日金曜日

Python: はてなブックマークのAPIを使ってみた

このブログ記事をはてなブックマークに追加

最近、はてなブックマークを使い始めたこともあり、はてなブックマークのAPIについて調べてみた。ざっと確認をした後、Pythonではてなブックマーク件数取得APIはてなブックマークエントリー情報取得APIを使ってみた。

はてなブックマーク件数取得APIはブックマークされた件数とAmazonの商品がコレクションされた件数をXML-RPCで取得することができる。一方、ブックマークエントリー情報取得APIでは、はてなブックマークのエントリーの情報をJSON形式で取得することができる。サンプルコードを以下に示す。

#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib2 import xmlrpclib import simplejson url = "http://www.hatena.ne.jp/" asin = "4774124966" # はてなブックマークエントリー情報取得API. data = urllib2.urlopen("http://b.hatena.ne.jp/entry/json/%s" % url).read() info = simplejson.loads(data.strip("(").rstrip(")")) if info: title = info["title"] cnt = int(info["count"]) bookmarks = info["bookmarks"] cnt_user = len(bookmarks) cnt_comment = 0 for b in bookmarks: if b["comment"]: cnt_comment += 1 print b["comment"].strip() if cnt_comment > 0: print print u"タイトル: %s" % title print u"URL: %s" % url print u"ブックマークしているユーザ数: %d +%d" % (cnt_user, cnt - cnt_user) print u"コメント数: %d" % cnt_comment rate_comment = float(cnt_comment) / cnt_user * 100 if cnt_user > 0 else 0.0 print u"コメント率: %.2f%%" % rate_comment print # はてなブックマーク件数取得API. s = xmlrpclib.ServerProxy("http://b.hatena.ne.jp/xmlrpc") print u"%sの被ブックマーク数: %d" % (url, s.bookmark.getCount(url)[url]) print u"サイトの被ブックマーク数: %d" % s.bookmark.getTotalCount(url) print u"Amazon商品(%s)の被コレクション数: %d" % (asin, s.bookmark.getAsinCount(asin)[asin])

因みに、JSONを利用するにあたってはsimplejsonモジュールを用いた。

プログラムを実行すると以下のような出力を得る。

お世話になっております。 ぶくま 初ブクマ はてなTOP hoge *default hatena はてなトップ (中略) タイトル: はてな URL: http://www.hatena.ne.jp/ ブックマークしているユーザ数: 1187 +547 コメント数: 218 コメント率: 18.37% http://www.hatena.ne.jp/の被ブックマーク数: 1734 サイトの被ブックマーク数: 54550 Amazon商品(4774124966)の被コレクション数: 127

まず、はてなブックマークエントリー情報取得APIによりすべてのコメントを表示させ、続けて、タイトル、URL、ユーザ数、コメント数、そして、ユーザ数に対するコメント数の割合を示すコメント率を出力。次いで、はてなブックマーク件数取得APIを用いて、指定URLのブックマーク数、サイト全体のブックマーク数、コレクション数の表示を行っている。

続きを読む...

2008年8月24日日曜日

Google: 333333333333335-333333333333334=0?

このブログ記事をはてなブックマークに追加

巷ではGoogleで399999999999999-399999999999998を計算させると0を返すと話題になっているが、ちょうど333333333333335-333333333333334の計算から0を返すようだ。ちなみにこの倍となる数値で計算させると666666666666669-666666666666667=0のように差が2で0を返すようになる。たぶん近いうちに修正されるんじゃないかな。

続きを読む...

2008年8月23日土曜日

Google App Engine: Gearsで位置情報を取得してストリートビューで表示させる

このブログ記事をはてなブックマークに追加

Gearsジオロケーション専用のAPI(Geolocation API)がリリースされたのでGoogle App Engineで実装してみた。まず、Geolocation APIで位置情報を取得し、Google MapsおよびGoogle Street Viewにより表示するWebアプリだ。因みに、アプリを実行させるには予めGearsをインストールしておく必要がある。

Geo View

コードは基本的にはStreet Strollを使っており、初期位置情報をGeolocation APIのgetCurrentPositionメソッドにより取得している。下記に示すコードが主な変更部分になる。

Gearsのコアであるgears_init.jsの読み込み。

<script type="text/javascript" src="http://code.google.com/apis/gears/gears_init.js"></script>

初期化関数initialize()の前にinitialize_geo()を噛ませ、変数lat, lngにそれぞれ緯度・経度が入るようにしている。

function updatePosition(position) { lat = position.latitude; lng = position.longitude; initialize(); } function initialize_geo() { if (window.google && google.gears) { var geo = google.gears.factory.create("beta.geolocation"); geo.getCurrentPosition(updatePosition, function(positionError) { alert(positionError.message); }); } else { if (confirm("位置情報を取得するためにはGearsが必要です。インストールしますか?")) { window.location = "http://gears.google.com/"; } else { // 秋葉原. lat = 35.698584; lng = 139.774216; initialize(); } } }

PCからだとIPアドレスから位置情報を取得するとのことで精度についてはあまり期待できないかもしれないが、携帯電話からだとそれなりに使えるのだろうか。しかし、IE Moblie+Gearsが必要なのがネックかなぁ。

続きを読む...

2008年8月21日木曜日

Google App Engine: mixi OpenIDで認証する

このブログ記事をはてなブックマークに追加

mixiがOpenIDに対応したということで早速Google App Engineで実装してみた。以前のエントリで紹介したがGoogle App Engineのサンプル集OpenID ConsumerというWebアプリがあるので、そのソースコードを利用させてもらった。

しかし、実際に使ってみればわかるのだがこのコードにはいくつかのバグ(とGoogle App Engineの制限)があってちゃんと動かない。下記のリンク先は正常に動くように修正したものだ。

mixi OpenID テスト

以下にバグの箇所とその修正コードを示す。

consumer.py

344-345行目の以下のコードを

self.response.set_status(302) self.response.headers['Location'] = redirect_url

以下のように修正する。

self.response.out.write("<html><head><meta http-equiv=\"refresh\" content=\"0;url=%s\"></head><body></body></html>" % (redirect_url,))

これはGoogle App Engineの制限を回避するためで、一度HTMLを表示し同時にリダイレクトを行っている。

fetcher.py

以下の57行目からのコードで、青で示したコードの後ろにある赤で示したコードを青のコードの前に移動させる。

if body: method = urlfetch.POST if 'Content-Type' not in headers: headers['Content-Type'] = 'application/x-www-form-urlencoded' else: method = urlfetch.GET if not headers: headers = {}

また、外部モジュールのPython OpenID Library (openid)ElementTree (elementtree)は最新のものに変更しても問題なく動く。

ローカルでテストする際はPython OpenID Libraryに付属するexamples/server.pyを利用するのが便利だ。dev_appserver.pyを起動させた後、以下のようにOpenIDサーバを起動させる。これによりテスト用のOpenIDをローカルで作成できる。

./examples/server.py -s localhost

http://localhost:8000/ をブラウザで開くとログイン画面が出るので適当な名前を入れる(たとえばtest)。これでhttp://localhost:8000/id/test がOpenIDのURLとなる。その後、http://localhost:8080/ を開き、OpenIDの入力フィールドでこのIDを使うことができる。もちろんローカルのOpenIDではなくmixi OpenIDや他のOpenIDを使うこともできる。

もしWindowsを使っていてElementTree関連のエラーが出た場合、正しくpyexpatモジュールが読み込まれていない可能性がある。その場合、PythonディレクトリにあるDLLsディレクトリからpyexpat.pydを持ってくると直るかもしれない。

その他については簡単な日本語訳やmixi OpenIDのログインボタンの表示などでそれほど難しくはない。マイミクシィ認証やコミュニティ認証もClaimed Identifierを変更するだけなのでそんなに手間ではないはず。しかし今回、Google App Engineの制限やコードのバグで思ったよりも手間取ってしまった。それでも、ちゃんと動いてしまいさえすればいろいろと応用できそうだ。

追記:

ログイン履歴についてはセキュリティを考慮して最初の1文字以外は伏字とし、リンクしないように変更した。

追記2:

ログイン後にコメントを残せるように変更した。

続きを読む...

2008年8月18日月曜日

Googleの暗号化ツールKeyczarをPythonで使う

このブログ記事をはてなブックマークに追加

Keyczarから持ってきたKeyczar Python (0.5b)を適当なディレクトリに展開し、srcファイル内のkeyczarをPythonディレクトリのsite-packatesにコピーする。次に、PyCryptosimplejsonASN.1 tools for Python から必要なモジュールをダウンロードしインストールする。

KeyczarToolのPythonの実装として、keyczart.pyが提供されているのでこれを利用する。ただし、これにはバグがあり、そのままだとAddKeyを実行できない。詳細は以下に報告されている。

Issue 21: Incorrect AddKey() Call from keyczart.py main()

対処方法としては、svnで最新版を持ってくるか、以下のようにソースを修正する。

keyczart.pyの113行目の

def AddKey(loc, status, crypter=None, size=None):



def AddKey(loc, status, size=None, crypter=None):

に変更する。

実際に平文を暗号文にし、さらにそれを平文に戻してみる。その前に鍵を作る必要があるので、keyczart.pyで作成する。--locationには鍵を作成したいディレクトリを指定すること。

keyczart.py create --location=C:\users\keyczar\keyset --purpose=crypt
keyczart.py addkey --location=C:\users\keyczar\keyset --status=PRIMARY

ここで気を付けなくてはならないのは --status=PRIMARY で指定する文字を大文字にする必要があることだ。マニュアルやコマンドの使い方では小文字になっているが、小文字で指定すると鍵の情報が正しく書き込まれない。たぶんバグだろう。

で、ここまで準備ができれば後は以下のように使用できる。

#!/usr/bin/env python from keyczar import keyczar crypter = keyczar.Crypter.Read(r"C:\users\keyczar\keyset") ciphertext = crypter.Encrypt("Test message!") print ciphertext plaintext = crypter.Decrypt(ciphertext) print plaintext

出力:

AeEDTVSdw1Q-im3koR1BbfgTVVbAWUhX-_4hY23S0cTtKOtdFsTmKi-U9eM6-5Z9MlkOGUhwX54T Test message!

続きを読む...

2008年8月13日水曜日

ActionScript 3.0で使う文字だけを埋め込む

このブログ記事をはてなブックマークに追加

てっく煮ブログAS3で埋め込みフォントを使うテクニックで使う文字だけを埋め込む便利なスクリプトがPerlのワンライナーで書かれていたので、連続する文字に対応させてPythonで実装。

#!/usr/bin/env python # -*- coding: utf-8 -*- import sys def main(args): # 引数がなければ標準入力、あればそのファイル名から読み込み. f = file(args[1]) if len(args) > 1 else sys.stdin # 文字を読み込み、重複しないようにソートして配列に格納. letters = sorted(set([ord(s) for l in f for s in unicode(l.rstrip("\n"))])) # 小さい順に標準出力に表示. 文字コードが連続している場合は - で繋げて出力. sys.stdout.write("U+%04X" % letters[0]) pre_s = letters[0] is_seq = False for s in letters[1:]: if s - pre_s == 1: is_seq = True elif is_seq: sys.stdout.write("-U+%04X,U+%04X" % (pre_s, s)) is_seq = False else: sys.stdout.write(",U+%04X" % s) pre_s = s if is_seq: sys.stdout.write("-U+%04X" % pre_s) if __name__ == "__main__": main(sys.argv)

例えば、以下の文字を使いたい場合、

ぁぃぅぇぉ あいうえお かきくけこ abcdefg xyz

これを上記のスクリプトに通すと、

U+0061-U+0067,U+0078-U+007A,U+3041-U+304B,U+304D,U+304F,U+3051,U+3053

のように出力されるので、以下のように埋め込めばいい。

[Embed(systemFont='MS 明朝', fontName='myFont', mimeType='application/x-font', unicodeRange='U+0061-U+0067,U+0078-U+007A,U+3041-U+304B,U+304D,U+304F,U+3051,U+3053' )] private static const myFont:Class;

続きを読む...

2008年8月7日木曜日

Google App Engine: Googleマップのストリートビューで散歩するWebアプリ

このブログ記事をはてなブックマークに追加


前回のエントリでGoogle Maps APIを試しに動かしたところ問題なく動いたので、ざっとGoogle Maps APIのリファレンスを読んでみた(因みに日本語版だとストリートビュー関連のAPIがまだ載っていない)。なんとなく全体の構成が分かったところで、デモギャラリーを参考にしながら、ストリートビューを使って自由に散歩するWebアプリ、Street Strollを作ってみた。Googleマップ上のマーカーと連動して動くようにしている。また、URLのオプションでマップの大きさを自由に変えられるので環境に合わせて利用して欲しい。要望があれば、写真撮影とその保存・閲覧、位置情報保存・再開、高速移動あたりをデータストアなどを使って実装するかもしれない。

Street Stroll 400×400 (標準)
Street Stroll 300×300
Street Stroll 500×500
Street Stroll 500×250

今回もURLのオプションぐらいしかGoogle App Engineの機能を使っていないのでコードは割愛。ブラウザからソースを開けばJavaScript部分は読むことができる。

追記:

Google StrollからStreet StrollにWebアプリ名を変更した。

続きを読む...

2008年8月5日火曜日

Google App Engine: Googleマップのストリートビューが凄い

このブログ記事をはてなブックマークに追加

日本でもGoogleマップでストリートビューのサービスが開始された。既にいろいろなところで話題になっているが、実際に使ってみると確かに凄い。自宅の前の道は車がぎりぎり通れるかどうかと云う細い道なのだが、それでもちゃんと自宅が写っていた。とっても面白かったので、Google Maps APIでストリートビューをいじってみたくなり、何かできないか考えてみた。

そこで、Google Maps API Demo Gallery - Lazy Street Viewを参考に、と云うかほとんどそのままのコードをGoogle App Engineで利用してみた。スクリプトをそのままHTMLとして書き出しているだけなので、Google App Engineである必要はなく、利用したというのもおこがましいのだが、Google Maps APIを一度も使ったことがなく、とりあえずどんなものか試したかっただけなのでご容赦を。でも、Google App EngineとGoogle Maps APIを組み合わせて面白そうなことができそうだなぁ。実際の町を舞台とした3Dロールプレイングゲームとか?(笑)


デモではJR秋葉原駅とヨドバシカメラを結ぶ横断歩道を中心にカメラがぐるりと回っている。あのいつも混んでいる横断歩道だ。因みに位置を変更するには、変更したい場所をGoogleマップで検索しストリートビューを表示させ、そのリンク内の位置情報(赤字で示す)をオリジナルのスクリプトの位置情報と置き換えればよい。

http://maps.google.com/maps?f=q&hl=ja&geocode=&q=%E6%97%A5%E6%9C%AC+%E7%A7%8B%E8%91%89%E5%8E%9F&ie=UTF8&ll=35.700549,139.774622&spn=0.003929,0.006866&z=17&layer=c&cbll=35.698584,139.774216&panoid=HNw-5xoiN5NJCVEWfeSdlA&cbp=1,65.31797388218855,,0,2.5976720953303447

最後に今回使ったソースコードを示す。HTMLを書き出しているだけなので参考にならないかもしれないけど。

# Google Map Street View API Demo class GoogleMapStreetViewDemo(webapp.RequestHandler): def get(self): self.response.out.write(u""" <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <title>Google Maps Street View API Demo</title> <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=Google Maps APIのKeyを指定" type="text/javascript"></script> <script type="text/javascript"> //<![CDATA[ var panorama; var currentYaw = 180; var currentPitch = 0; var timer; var currentZoom = 0; var zoomingIn = true; function load() { panorama = new GStreetviewPanorama(document.getElementById("pano")); panorama.setLocationAndPOV(new GLatLng(35.698584, 139.774216), {yaw: currentYaw, pitch: currentPitch, zoom: currentZoom}); timer = window.setInterval(spiral, 200); } function spiral() { currentYaw += 2; panorama.panTo({yaw:currentYaw, pitch:currentPitch}); } function stopAndZoom() { clearInterval(timer); zoomingIn = true; timer = window.setInterval(zoom, 500); } function zoom() { if (zoomingIn) { currentZoom++; } else { currentZoom--; } panorama.panTo({yaw:currentYaw, pitch:currentPitch, zoom:currentZoom}); if (currentZoom == 2) { zoomingIn = false; } if (currentZoom == 0) { clearInterval(timer); timer = window.setInterval(spiral, 200); } } //]]> </script> <body onload="load()" onunload="GUnload()"> <div id="pano" style='width:500px; height:400px'></div> <br/> <input type="button" onclick="stopAndZoom()" value="クリックするとズームインします"/> <div><p style="font-size:x-small;color:gray;">このWebアプリは、<a href="http://code.google.com/apis/maps/documentation/demogallery.html">Google Maps API Demo Gallery</a>の<a href="http://gmaps-samples.googlecode.com/svn/trunk/streetview/streetview_lazy.html">Lazy Street View</a>を参考にしています。</p></div> </body> </html>""")

続きを読む...

2008年8月1日金曜日

プログラミング言語Erlangを覚えよう

このブログ記事をはてなブックマークに追加

以前から関数型プログラミング言語であるErlang(アーラン)を覚えたかったので、「プログラミング言語Erlang入門」を読んだ。この本自体は本当に入門者というか初心者向けの本となっている。しかし、自分自身はほとんど関数型言語について知識がないので、こういった入門書で概略を掴めるのはありがたい。C言語などの命令型プログラミング言語と比べるとスタイルが大きく異なり、それらの知識をベースとすることができないので、先日のLuaの入門書ほどお手軽ではなかったが、内容自体は非常に簡易なので1日あれば余裕で理解できるだろう。

概略以上のことを学びたいのなら最初から「プログラミングErlang」を読むのが良い。こちらも手元にあるが、かなり良い本だと思う。ちゃんとしたプログラムを書きたいのならこちらがお勧め。

ところで、Erlangは並列処理言語としても注目を集めている。非常に効率よく並列処理ができるのが特徴で、それぞれのスレッドが完全に独立して動くように設計されている。独立しているスレッドであるためErlangではそれをプロセスと呼んでいる。spawn()関数で各プロセスを実行することができ、非常に簡単に並列処理プログラムを作ることができる。

プログラミングErlang」に内包リストを使ってアナグラムを標準出力に表示するプログラムが載っていたのだが、それを並列処理化して一つのファイルに書き出すように変更したプログラムを書いたので、このエントリの最後に示しておく。二つの文字列を二つのプロセスを使って同時にアナグラムを生成し、その結果を一つのファイルに書き出している。このコードでおぼろげながらでもErlangの雰囲気が分かるだろうか。

test.erl

-module(test). -export([perms/1, write/3, wait/1, anagram/2]). %% 順列生成. perms([]) -> [[]]; perms(L) -> [[H|T] || H <- L, T <- perms(L--[H])]. %% ファイルへの書き出し. %% S: ファイル, L: 文字列, P: プロセス識別子. write(S, L, P) -> lists:foreach(fun(X) -> io:format(S, "~s~n", [X]) end, perms(L)), P ! L. %% すべてのメッセージを受け取るまで待機. wait(0) -> io:format("terminated.~n", []); wait(N) -> receive Msg -> io:format("Anagram: ~s~n", [Msg]), wait(N - 1) end. %% アナグラム生成 メイン関数. %% File: ファイル名, [L1, L2]: アナグラムを行う文字列のリスト. anagram(File, [L1, L2]) -> P = self(), {ok, S} = file:open(File, write), spawn(test, write, [S, L1, P]), spawn(test, write, [S, L2, P]), wait(2), file:close(S).

コンパイルおよび実行

test.erlをコンパイルし、test:anagram()関数を使ってabcとdefのアナグラムをanagram.txtに書き出す。

1> c(test). {ok,test} 2> test:anagram("anagram.txt", ["abc", "def"]). Anagram: abc Anagram: def terminated. ok

anagram.txt

abc def acb dfe bac edf bca efd cab fde cba fed

続きを読む...