2013-03-19
はてぶ数を取得する その3
昨日のエントリの続き。はてブ数は、XMLRPCを公開しているのでそれを使うのが簡単だったorz。50件まで同時に引数に渡せるとのこと。ついでにTweet数(JSON)といいね数(FQL)も合わせてとるURLクラスを定義してみた。
import re, time, urllib, urllib2, json import feedparser class URL(object): def __init__(self,url): self.id = url self.url = url def __getattr__(self,attr): if attr == 'hbuser': return self.__hb() elif attr == 'liked': return self.__fb() elif attr == 'tweeted': return self.__tweeted() def __hb(self): import xmlrpclib proxy = xmlrpclib.ServerProxy("http://b.hatena.ne.jp/xmlrpc") ret = proxy.bookmark.getCount( *[self.url,] ) return ret[self.url] def __fb(self): import xml.etree.ElementTree as ET url = self.url qstr = urllib.urlencode({'query':'''SELECT total_count FROM link_stat WHERE url="%(url)s"'''%locals()}) fh = urllib.urlopen("https://api.facebook.com/method/fql.query?"+qstr) etree = ET.ElementTree().parse(fh) liked = etree.find("{http://api.facebook.com/1.0/}link_stat/{http://api.facebook.com/1.0/}total_count").text return int(liked) def __tweeted(self): import json qstr = urllib.urlencode({'url':self.url}) apiurl = "http://urls.api.twitter.com/1/urls/count.json?"+qstr return json.load(urllib.urlopen(apiurl))["count"] u = URL("http://www3.asahi.com/rss/index.rdf") print u.hbuser, u.liked, u.tweeted
2013-03-18
はてぶ数を取得する その2
昨日のエントリの続き。リダイレクト先のURLを知りたいだけならカスタムなリダイレクトハンドラは不要だった。こっちのほうが簡潔で筋もよい。
import urllib2, re def hb(url): try: gifurl = urllib2.urlopen("http://b.hatena.ne.jp/entry/image/%s"%(url,)).geturl() mo = re.search(r"(\d+)\.gif$",gifurl) return int(mo.group(1)) except Exception,e: print e return 0 if __name__ == '__main__': print hb("http://d.hatena.ne.jp/")
はてぶ数を取得する
はてブ数(xxxuser)というのはある意味そのページの人気度を計る上で有用だ。はてなは、任意のURLに対してはてブ数を取得できるAPIがある。http://b.hatena.ne.jp/help/count
これは、以下のような動きをするため、user数を直接知ることはできない
- imgタグのsrcとしてHTMLに埋め込み
- ブラウザがsrcにアクセスすると, HTTPリダイレクトされ
- ブラウザは, xxxuserの画像URLにアクセスしそれをimgタグの場所に埋め込む
なんらかのプログラムで使用する際は, 具体的な数字として知りたいので以下のような感じで対処した。
import re, urllib2 class LocationException(Exception): pass class MyHTTPRedirectHandler(urllib2.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, hdrs, newurl): raise LocationException(newurl) def retrieve_bookmarks(url): retval = 0 try: urllib2.install_opener(urllib2.build_opener(MyHTTPRedirectHandler())) urllib2.urlopen("http://b.hatena.ne.jp/entry/image/%s"%(url,)).read() except LocationException,e: gifname = str(e).split("/")[-1] mo = re.match(r"^(\d+)\.gif$",gifname) assert mo retval = int(mo.group(1)) finally: urllib2.install_opener(urllib2.build_opener(urllib2.HTTPRedirectHandler())) return retval print retrieve_bookmarks("http://d.hatena.ne.jp/") #891
要はリダイレクト先URLからユーザ数を抽出している。ということ。
2012-11-04
urllib2でやっておいた方がよい初期設定
pythonでHTTPを使う際 urllib2 を使うことが多い。デフォルト設定だと若干使い勝手がよくないので以下の設定をするのがいいように思う。
- クッキーサポート
- User-Agent偽装
- Accept-Languageの設定
import urllib2, cookielib cj = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) opener.addheaders=[ ('User-Agent', "Mozilla/5. (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11"), ('Accept-Language','ja,en-us;q=0.7,en;q=0.3') ] urllib2.install_opener(opener)
2012-10-24
東証一部上場銘柄を抽出する
東京証券取引所は一部上場企業の一覧をExcel形式で発表している。これを抽出する。Excelからの情報収集には xlrd と呼ばれるモジュール(pip installで導入可)を使う。HTMLのパースには libxml の付属 python bidingであるところの libxml2 を利用する。
import re, urllib, tempfile import libxml2 import xlrd class TSE1(object): def __init__(self): self.target_url = self._extract_xls_link() assert self.target_url self._parse_xls() assert len(self.stock_codes) >= 1000 def _parse_xls(self): with tempfile.NamedTemporaryFile() as tfh: tfh.write(urllib.urlopen(self.target_url).read()) tfh.flush() sheet = xlrd.open_workbook(tfh.name).sheets()[0] self.stock_codes = [ str(int(sheet.cell(row,1).value)) for row in range(1,sheet.nrows) ] def _extract_xls_link(self): URL ="http://www.tse.or.jp/market/data/listed_companies/index.html" root =libxml2.htmlReadFile(URL,'utf8', libxml2.HTML_PARSE_RECOVER|libxml2.HTML_PARSE_NOERROR) for anode in root.xpathEval("//a"): hrefs = anode.xpathEval('attribute::href') if len(hrefs) == 0: continue href = hrefs[0].content if re.search(r"first-d-j.xls",href): return "http://www.tse.or.jp" + href return None if __name__ == '__main__': tse1 = TSE1() print "number: %d" % (len(tse1.stock_codes), ) print "/".join(tse1.stock_codes[:15]) + "..." """output: number: 1675 1301/1332/1334/1352/1377/1379/1414/1417/1514/1515/1518/1605/1606/1661/1662... """
2012-10-20
日経225銘柄を抽出する
日経平均の銘柄一覧のページ(http://indexes.nikkei.co.jp/nkave/index/component?idx=nk225) から証券コードを抽出する。 libxml の公式pythonbinding であるところの libxml2 モジュールを使う。
import re, libxml2 URL ="http://indexes.nikkei.co.jp/nkave/index/component?idx=nk225" xpath="//div[@id='CONTENTS_MAIN']//table[@class='cmn-table']//td[@class='cmn-stock_border']" root =libxml2.htmlReadFile(URL,'utf8', libxml2.HTML_PARSE_RECOVER|libxml2.HTML_PARSE_NOERROR) N225_codes = [] nodes = root.xpathEval(xpath) for node in nodes: if re.match(r"\d+", node.content): N225_codes.append(node.content) assert len(N225_codes) == 225 for i, code in enumerate(N225_codes): print '"%s",' %(code,), if i % 10 == 9: print "\n", """output: "4151", "4502", "4503", "4506", "4507", "4519", "4523", "4568", "6479", "6501", "6502", "6503", "6504", "6506", "6508", "6674", "6701", "6702", "6703", "6752", "6753", "6758", "6762", "6767", "6770", "6773", "6841", "6857", "6902", "6952", "6954", "6971", "6976", "7735", "7751", "7752", "8035", "7201", "7202", "7203", "7205", "7211", "7261", "7267", "7269", "7270", "4543", "4902", "7731", "7733", "7762", "9412", "9432", "9433", "9437", "9613", "9984", "8303", "8304", "8306", "8308", "8309", "8316", "8331", "8332", "8354", "8355", "8411", "8253", "8601", "8604", "8628", "8630", "8725", "8729", "8750", "8766", "8795", "1332", "1334", "2002", "2269", "2282", "2501", "2502", "2503", "2531", "2801", "2802", "2871", "2914", "3086", "3099", "3382", "8233", "8252", "8267", "8270", "9983", "4324", "4689", "4704", "9602", "9681", "9735", "9766", "1605", "3101", "3103", "3105", "3401", "3402", "3861", "3864", "3865", "3893", "3405", "3407", "4004", "4005", "4021", "4041", "4042", "4043", "4061", "4063", "4183", "4188", "4208", "4272", "4452", "4901", "4911", "5002", "5020", "5101", "5108", "3110", "5201", "5202", "5214", "5232", "5233", "5301", "5332", "5333", "5401", "5406", "5411", "5413", "5541", "3436", "5703", "5706", "5707", "5711", "5713", "5714", "5715", "5801", "5802", "5803", "5901", "2768", "8001", "8002", "8015", "8031", "8053", "8058", "1721", "1801", "1802", "1803", "1812", "1925", "1928", "1963", "5631", "6103", "6113", "6301", "6302", "6305", "6326", "6361", "6366", "6367", "6471", "6472", "6473", "7004", "7011", "7013", "7003", "7012", "7911", "7912", "7951", "8801", "8802", "8803", "8804", "8815", "8830", "9001", "9005", "9007", "9008", "9009", "9020", "9021", "9022", "9062", "9064", "9101", "9104", "9107", "9202", "9301", "9501", "9502", "9503", "9531", "9532", """
2012-09-11
リストのgrep をpythonで書く
ruby では配列にgrep ができてとても便利。
irb(main):001:0> ['1-1', '11-a', '1', '00-', '1-0'].grep(/\d+-\d+/) => ["1-1", "1-0"]
python ではどう書くのかなぁと思って少し考えて、(1) リスト内包表記, (2) filter(ビルトイン関数) の二パターンがあるかと思う。
>>> import re >>> l = ['1-1', '11-a', '1', '00-', '1-0'] >>> [s for s in l if re.match(r"\d+-\d+",s)] #(1) ['1-1', '1-0'] >>> filter(lambda x: re.match(r"\d+-\d+",x), l) #(2) ['1-1', '1-0']
リストが大きかったり、リスト自体がイテレータなら itertools.ifilter がよいかもしれない。
>>> import itertools >>> itertools.ifilter(lambda x: re.match(r"\d+-\d+",x), l) <itertools.ifilter object at 0x7f4d9aeabb10> >>> list(itertools.ifilter(lambda x: re.match(r"\d+-\d+",x), l)) ['1-1', '1-0']
2012-09-01
pythonで動的にメソッドを変更する
python は動的にクラス/インスタンスメソッドを変更可能なのでやりたい放題できる。そこで、「動的に」メソッドを 新規追加/上書き/調整 をすることができる。その方法論をまとめた。このあたりをちゃんとマスターできると既存のライブラリに手を入れずにその挙動をピンポイントで変えることができるようになり、コーディングの幅がかなり広がる。黒魔術なので多用は無用だが…
[補足] 普通はこんなことせずに継承をつかいましょう…
以下のようなクラスC がメソッド print_x を持っているとすると、これをインスタンスメソッドとして呼び出せる。これは普通の使い方。
class C(object): def __init__(self): self.x = 1 self.y = 2 def print_x(self,arg1,arg2): print self.x , arg1, arg2 c = C() c.print_x(9,99) """output: 1 9 99 """
続いて、このクラス/インスタンス に新たなメソッド print_xy を動的に足してみる。注意するべきは、 c.print_more = print_xy ではないってこと。このあたりは python言語仕様としての bound method と unbound method と function の違いがわかれば明らか。適当にググるとわかるのでぜひ調べてほしい。
def print_xy_impl(self): print self.x, self.y C.print_xy = print_xy_impl c.print_xy() """output: 1 2"""
さて、追加できるってことは、上書きも当然できる。足したばっかの print_xy を上書きすることで挙動を変更してみよう。
def print_xy_impl2(self): print self.x, self.y, 1 C.print_xy = print_xy_impl2 c.print_xy() """output: 1 2 1"""
さて、応用問題。既存のメソッドに対して、「オリジナルの処理はそのままで」何らかの前処理/後処理を加えることができる。別の言い方をすれば、インスタンスメソッドに動的にデコレータをかますことができる。先ほどの例では、 オリジナルの print_x()に対して、前処理として self.x を 10倍して、後処理として self.x を元に戻すことを考えてみよう。 これは、かなり強力な手法で、「サードパーティ製のライブラリの動作を、コードを修正せずに変更」するなどの応用につながる。
def add_something(original): def revised(self,arg1,arg2): print "mulitple self.x by 10 before original print_x()" self.x *= 10 ret = original(self,arg1,arg2) self.x /= 10 print "revert self.x after original print_x()" return ret return revised C.print_x = add_something( C.print_x ) c.print_x(9,99) """output mulitple self.x by 10 before original print_x() 10 9 99 revert self.x after original print_x() """
この辺の挙動は、pythonの名前解決方法, bound/unboundなメソッドの違い, descriptor仕様(+getattr系の話), デコレータ周りがわかればすっきり頭に入ってくると思う。
2012-08-30
四元数を使って三次元座標系で点を回転する その3
先日のエントリ(http://d.hatena.ne.jp/lolloo-htn/20120829/1346242863)の続き。
Blender でxy平面上を半径4の円運動をしている物体の回りを10倍の角速度で回る半径1の物体の軌跡を書くアニメーション。
太陽の周りを回る地球の周りを回る月の軌跡のようなもの。 Blenderのスクリプティングとしてはひどいが、まぁいいか。
import math import bpy, mathutils class RotationTest(object): def __init__(self): self._create_material() self._add_cube( mathutils.Vector((0,0,0)), self.red ) def _create_material(self): self.red = bpy.data.materials.new("mat.red") self.red.diffuse_color = (1,0,0) self.red.diffuse_shader = 'LAMBERT' self.red.diffuse_intensity = 1.0 self.red.specular_color = (1,1,1) self.red.specular_shader = 'COOKTORR' self.red.specular_intensity = 0.5 self.red.alpha = 0.5 self.red.ambient = 1 def _add_cube(self, position, material, size=0.1): bpy.ops.mesh.primitive_cube_add(location=position) bpy.context.object.data.materials.append(self.red) bpy.context.object.scale = (size,size,size) def main(self): r1 = mathutils.Vector((4,0,0)) r2 = mathutils.Vector((1,0,0)) q1 = mathutils.Quaternion( (0,0,1),math.radians(1) ) q2 = mathutils.Quaternion( (0,0,1),math.radians(10) ) for angle in range(360): r1.rotate(q1) r2.rotate(q2) r = r1 + r2 bpy.context.object.location = r[:] bpy.context.object.keyframe_insert(data_path="location", frame=angle) RotationTest().main()
2012-08-29
四元数を使って三次元座標系で点を回転する その2
先日のエントリ(http://d.hatena.ne.jp/lolloo-htn/20120826/1345989421)の続き。
blenderの提供するmathutilsでは、以下が存在する。
- ベクトルを表す概念として mathutils.Vector
- 回転(などの線形変換/Affine変換)を表す概念として mathutils.Quaternion, mathutils.Matrix.XXXX, mathutils.Euler
点Pを回転させるというような用途では、 mathutils.Vector#rotate を使うのがよい。
点P(3,0,3)をz軸を中心に30度ずつ回転させた点を表示するには以下のようにする。
import math, mathutils v = mathutils.Vector((3,0,3)) r = mathutils.Quaternion((0,0,1), math.radians(30) ) for i in range(0,360,30): v.rotate(r) print ( "({:.2f}, {:.2f}, {:.2f})".format(v.x, v.y, v.z) ) """ Output: (2.60, 1.50, 3.00) (1.50, 2.60, 3.00) (-0.00, 3.00, 3.00) (-1.50, 2.60, 3.00) (-2.60, 1.50, 3.00) (-3.00, -0.00, 3.00) (-2.60, -1.50, 3.00) (-1.50, -2.60, 3.00) (0.00, -3.00, 3.00) (1.50, -2.60, 3.00) (2.60, -1.50, 3.00) (3.00, 0.00, 3.00) """
この例では、rotateメソッドに対してQuaternionを渡したが、Matrix, Euler を渡すことも可能であり、使い勝手がよい。
2012-08-26
四元数を使って三次元座標系で点を回転する
blenderを pythonから使おうとAPIを見ていた際に、mathutils.Quaternion という聞きなれないものがあったので調査した際のメモ。
「点Pをある軸を中心に回転させた点Qを求める」という問題は、四元数を使った代数演算を経由することで解決できる。
1. 点Pを表す四元数 p を作成する
2. 回転操作を表す四元数 q, r を作成する
3. これらを使って四元数の演算(x = qpr) を行う
4. 演算結果の四元数 x は、点Qを表す四元数のため、 点Qの座標がわかる
blender には回転軸と、回転させたい角度を与えることで 四元数を表すクラス mathutils.Quaternion が存在する。これを使って、四元数を使った回転を試してみた。
import math import mathutils def rotate(target, axis, theta): """ returns the cordinate to be rotated.""" p = mathutils.Quaternion((0.0, target[0], target[1], target[2])) q = mathutils.Quaternion(axis, theta) r = q.conjugated() x = q * p * r return (x[1],x[2],x[3]) p = (3,0,3) axis = (0,0,1) for theta in range(0, 360, 30): r = rotate(p,axis, math.radians(theta) ) print ("({:.2f}, {:.2f}, {:.2f})".format(r[0],r[1],r[2])) print ("") """ Output: (3.00, 0.00, 3.00) (2.60, -1.50, 3.00) (1.50, -2.60, 3.00) (0.00, -3.00, 3.00) (-1.50, -2.60, 3.00) (-2.60, -1.50, 3.00) (-3.00, -0.00, 3.00) (-2.60, 1.50, 3.00) (-1.50, 2.60, 3.00) (0.00, 3.00, 3.00) (1.50, 2.60, 3.00) (2.60, 1.50, 3.00) """
なお、rotateメソッドもあるのだが、この目的では使えないように見える。
