Python で simpleXML ライク。
SimpleXML は、PHP で XML を簡単に扱うためのモジュールで、XML のデータを PHP の連想配列などのデータ型で扱うことができます。
Python でも、XML データを辞書(dict,dictionary)型やリスト(list)型で扱えたら便利です。
次に掲げるページでも、SimpleXML ライクな Python ツールを紹介しています。
PythonでSimpleXML - tomoemonの日記
joestump/python-simplexml - GitHub
ただ、ぼくの環境ではうまく動きませんでした。
Python 初心者なので、コードのどこを修正すればよいかわかりません。
それなら、自分で考えたほうが早いと思って作ったのが、次の timpleXML 関数です。
「timpleXML」は、「陳腐な SimpleXML」という意味です。
例外処理は考えていません。また、タグ名とテキストを取得することだけを前提とし、属性の取得はしません。
検証もほとんどしていないので、おかしなところがあるかもしれませんが、これでも欲しいデータは取れたので、自分用のメモとして公開することにしました。
引数には、xml.etree.ElementTree で取得した エレメントを取ります。
エレメント型が持つ、tag と text プロパティを取得し、getchildren() メソッドで取得した子エレメントを再帰的に処理します。
なお、タグ名の名前空間部分は、削除します。
def timpleXML(elm): import re re_ns = re.compile("\{.*\}") tag = re_ns.sub("",elm.tag) if len(elm.getchildren()): res = {} tags = {} for e in elm.getchildren(): etag = re_ns.sub("",e.tag) if etag in tags : if tags[etag] != 'list' : tags[etag] = 'list' res[etag] = [res[etag]] res[etag] .append(timpleXML(e)) elif not len(e.getchildren()): tags[etag] = 'text' if e.text: res[etag] = e.text else: res[etag] = '' else: tags[etag] = 'dict' res[etag] = timpleXML(e) elif elm.text: res=elm.text; else : res='' return res
辞書型またはリスト型のデータが返ります。
兄弟要素で同じタグ名が使われている場合には、そのタグ名をキーとし、値はリスト型になります。
JSON に変換したい場合は、simplejson(Django) にぶち込むと簡単です。
次に、その処理の一部を示します。
Google App Engine での利用を前提としています。念のため。
from google.appengine.ext import webapp from google.appengine.ext.webapp.util import run_wsgi_app import xml.etree.ElementTree as etree from django.utils import simplejson from timpleXML import timpleXML root = etree.fromstring(result_xml) result_obj = timpleXML(root) result_json = simplejson.dumps(result_obj,ensure_ascii=False) callback = self.request.get('_callback') if callback: result_json = callback + "(" + result_json + ")" self.response.headers['Content-Type'] = 'text/javascript' self.response.out.write(result_obj)
result_xml は、XML文字列です。
XMLファイルからエレメントツリーを取得する場合は、次のように記述すればよいでしょう。
xml = urlfetch.fetch(url) root = etree.fromstring(xml.content))
急用があるので、今回はここまで。
説明が不十分ですが、お許し下さい。
Python:re.findall, HTMLから文字列を抽出するのに便利。
HTMLから文字列を抽出するときに、re モジュールの findall() を使うと便利ですね。
特にリンク文字列を抽出したいときに重宝しています。
例えば、次のようなリスト要素の中に A要素が入っている場合。
<li><span class="span"><a href="#%E3%82%A2%E3%82%AB%E3%83%9A%E3%83%A9">アカペラ</a></span></li>
次のように括弧で抽出したい部分を指定すると、その部分の文字列のリストを取得できます。
matchlist = re.findall('<a .+">(.+)</a>',html)
re.findall() では、括弧の使い方によって、返されるリストの内容が変わります。
0)括弧がない場合
マッチした部分のリスト
['<a href="#a">A</a>','<a href="#b">B</a>:' ]
1)括弧が1つの場合
マッチした部分の括弧内の文字列のリスト
['A','B' ]
2)括弧が2つ以上の場合
括弧の順番によるタプルのリスト
[('#a','A'),('#b','B') ]
(例)re.findall('<a href="(.*)">(.+)</a>',html)
東京電力管内の電力の需給状況
東京電力管内の電力の需給状況を調べるページを作成しました。1時間ごとの電力消費量と供給量をグラフと表で表示します
http://netarrows.appspot.com/power
この2つの画像は、自由に転載して結構です。ぜひ紹介して下さい。
この夏の電力対策としては、まず節電の啓発が第一です。すでに色々なサイトが類似のものを作成していると思いますが、重なれば重なるだけ、みんなの意識が高まると信じます。そこで、微力ながら、私もこれに加わらせていただきました。
今のところ、「2011-04-24」以降のものしか表示しませんが、それでもある程度のことはわかります。
例えば、「2011-04-24」は日曜日でしたが、この日の電力供給は、3500万kWh。消費量のピークは 19時で、3025万kWh、使用量率は 86% に達しました。
昨日「2011-04-25」の月曜日は、電力供給4000万kWh。消費量のピークはやはり19時で、3338万kWh、使用量率は 83% 。「2011-04-26」電力供給は、3950万kWh。
今日「2011-04-26」の電力供給は、3950万kWh。ピークで 85% ぐらいの消費量率になるように調整したのかなと思わせます。
まだ作り込んではいませんが、公開は早い方がよいと考え、紹介する次第です。
URL をご覧になればわかるとおり、Google App Engine を利用しています。初心者ながら、とりあえず公開することができました。これまで学んできたことをどうにか応用できたという感じです。
不十分な点は多々あると思いますが、作成の過程でつまづいた点など順次明らかにしていきたいと思います。失敗も誰かの参考になるでしょうから。
Google App Engine Python: リソースパスを引数のように webapp で処理する。
WEBサービスの API のリクエストURL などでは、リソースパス部分を読み取ってサーバー側で処理を分岐していると思われるものがあります。
例えば、Twitter の API で、ユーザー名 bob の friend のタイムラインを取得するには、次のようなリクエストURLを用います。
http://api.twitter.com/1/statuses/friends_timeline/bob.json
実際の Twitter API のサーバーの構造は知りませんが、普通に考えれば、リソースパス部分(http://api.twitter.com 以降の部分)に相当する静的なディレクトリや静的なファイルがあるとは考えられません。少なくとも bob の friend がツイートするたびに静的な bob.json ファイルを更新するとは思えません。
推測ですが、ステータスデータを保存するデータベースがあって、そこからリクエストに応じて、データを取り出して応答しているのではないでしょうか?
そうだとすれば、ステータス管理サーバーは、bob の friend のタイムラインを JSON 形式で返して欲しいというリクエストをこのリソースパスで受け付けられるようになっていることになります。
つまり、パラメータを使って、リクエストパラメータを次のようなものにしても同じようなことができたはずです。
http://api.twitter.com/1/statuses/?type=friends_timeline&user=bob&output=json
それなら、なぜこのようにしなかったのか?
上記の2つの URL を比べれば、答えるまでもありませんね。
上の方が簡明だからですね。
URL に、いちいちパラメータ名を付加しなくても、何を求めているかはわかります。
同じようなことが Google App Engine で簡単に実現できるのでしょうか?
それができるんです。
何を使うかって?
チュートリアルでも、最初の方に紹介されている webapp を使います。
webapp では、リクエストハンドラのマッピングに正規表現を使って振り分けをしていましたね。そこでは、括弧「()」を使った正規表現のグルーピング機能も有効です。そして、その順番でリクエストハンドラ・クラスの get() メソッドにマッチング文字列を引数として渡す仕組みになっているんです。
例えば、リクエストハンドラのマッピング部分を次のように記述してみましょう。
application = webapp.WSGIApplication([('/([^/]*)/?([^/]*)/?(.*)',MainPage])
この正規表現は、リソースパスが次のいずれであってもマッチします。
/ /A /A/ /A// //B //B/ /A/B /A/B/ ///C /A//C /A/B/C /A/B/C?q=D
要するに、括弧で囲まれた3つの部分をそれぞれ抽出する正規表現なのです。
このように3つの部分になる場合には、リクエストハンドラの get メソッドにも対応する3つの引数を指定します。
class MainPage(webapp.RequestHandler): def get(self,s1,s2,s3):
以降、この引数を用いて処理を記述できます。
また、リクエストパラメータも同時に利用できます。
q というパラメータが想定されているとすると、次のような記述でその値を取得できます。
class MainPage(webapp.RequestHandler): def get(self,s1,s2,s3): q=self.request.get('q');
最後に簡単な例ですが、リソースパスから値を取り出す例を紹介します。
from google.appengine.ext import webapp from google.appengine.ext.webapp.util import run_wsgi_app class MainPage(webapp.RequestHandler): def get(self,s1,s2,s3): q=self.request.get('q'); path =s1 + '/' + s2+ '/' + s3 if q: path += '?q='+q self.response.out.write(path) application = webapp.WSGIApplication( [('/([^/]*)/?([^/]*)/?(.*)', MainPage)], debug=True) def main(): run_wsgi_app(application) if __name__ == "__main__": main()
リソースパスが、なぜファイルパスではなく、「リソースパス」と呼ばれるのかも、何となく分かりますね。
Google App Engine Python: memcache と template でコンテンツを変化させる。
前3回に渡って「スケジュール、キャッシュ、テンプレートによる効率的更新」という記事を掲載した。それは、cron によって定期的にコンテンツを更新しようというものだった。
ニーズがあるかはわからないが、決められたコンテンツの中から選んで表示する場合には、cron を使うまでもない。
memcache に保存できるのは、文字列だけではない。基本的には、すべてのオブジェクトを格納できる。
今回は、memcache にリストを保存するサンプルとして、アクセスの度に表示されるコンテンツが変化するページを作成してみよう。
from google.appengine.ext.webapp.util import run_wsgi_app from google.appengine.ext.webapp import template import os from google.appengine.api import memcache class MainPage(webapp.RequestHandler): def get(self): lis = ["A","B","C","D","E"] pubword= memcache.get('pubword') if pubword : for i in lis : if i in pubword : continue else : pubword.append(i) break if i==lis[-1] : pubword=[i] else : i = lis[0] pubword=[i] memcache.set('pubword',pubword) frg='<h2>'+i+'</h2>' path = os.path.join(os.path.dirname(__file__), 'main.tpl') self.response.out.write(template.render(path, { 'frg':frg })) application = webapp.WSGIApplication([('/', MainPage)],debug=True) def main(): run_wsgi_app(application) if __name__ == "__main__": main()
もっとよいロジックがあるかもしれないが、サンプルなので許して欲しい。
アクセスがある度に、"A","B","C","D","E"を順番にテンプレートに埋め込むというものである。
表示した文字は、"pubword"という名のリストに追加していく。そして、そのリストを memcache に保存する。ページにアクセスがあると、"pubword"に保存されている文字以外の文字を1つ表示する。最後の文字"E"を表示したら、リスト"pubword"の内容は、その最後の文字"E"だけにする。
こうすることで、"A","B","C","D","E"が順番に表示されるという仕掛けだ。
テンプレートは、文字を埋め込む場所にテンプレート変数 {{ frg }} をおけばよい。
<html> <head> <title>changing words</title> </head> <body> <h1>changing words</h1> {{ frg }} </body> </html>
Google App Engine Python:スケジュール、キャッシュ、テンプレートによる効率的更新(3)。- memcache,template
前々回は、スクリプトを指定した時間に起動できるスケジュール・タスクを実現するための「cron.yaml」の記述方法について、前回は、そのスケジュールにしたがって実行されるスクリプトによって、外部から取得したデータを加工したうえで、キャッシュに保存する方法について説明した。今回は、キャッシュからデータを取り出して、テンプレートに埋め込んで表示させよう。
前回のサンプルによって、memcache の"gooRanking"キーには、次のような文字列が格納されている。
<ul> <li>キーワード1</li> <li>キーワード2</li> <li>キーワード3</li> <li>キーワード4</li> </ul>
これを取り出すのは簡単だ。キー名を引数に取り、memcache の get メソッドを用いる。
memcache.get("gooRanking")
後は、この取り出した文字列をテンプレートに埋め込めばよい。
from google.appengine.ext import webapp from google.appengine.ext.webapp.util import run_wsgi_app import os from google.appengine.ext.webapp import template from google.appengine.api import memcache from daily import updateKeywords class MainPage(webapp.RequestHandler): def get(self): gooRanking = memcache.get('gooRanking') if not gooRanking : updateKeywords() gooRanking = memcache.get('gooRanking') path = os.path.join(os.path.dirname(__file__), 'index.html') self.response.out.write(template.render(path, { 'gooRanking':gooRanking })) application = webapp.WSGIApplication([('/', MainPage)],debug=True) def main(): run_wsgi_app(application) if __name__ == "__main__": main()
「if not gooRanking :」のブロックは、前回も説明したが、memcache のデータが失われた場合に、緊急避難として、データを再作成するためのもの。
テンプレート・ファイルの"index.html"には、データを埋め込むためのテンプレート変数を記述しておく。その部分に取得した文字列が埋め込まれる。
<html> <head> <title>memcache から取得したデータをテンプレートに埋め込んだサンプル</title> </head> <body> <h1>memcache から取得したデータをテンプレートに埋め込んだサンプル</h1> {{ gooRanking }} </body> </html>
このページにアクセスすると、リストが埋め込まれた状態で表示される。
もちろん、「app.yaml」でリクエストハンドラーを設定しておき、このページのURLにアクセスできるようにしておかなければならない。
前回と前々回で説明した cron によって、リストの内容は、毎日0時12分を境に書き換わる。
Google App Engine Python:スケジュール、キャッシュ、テンプレートによる効率的更新(2)。- memcache
前回は、スクリプトを指定した時間に起動できるスケジュール・タスクを実現するための「cron.yaml」の記述方法について説明した。今回は、そのスケジュールにしたがって実行されるスクリプトで、外部から取得したデータを加工したうえで、キャッシュに保存する方法について説明しよう。
外部から取得するデータには、前々回の「Python:xml.etree.ElementTree, 外部 RDF。」の記事で用いた goo のランキングRDFを今回もサンプルとする。
まず、サンプルとして「daily.py」のソースコードを紹介する。
def updateKeywords(): from google.appengine.api import urlfetch from google.appengine.api import memcache import xml.etree.cElementTree as etree url='http://ranking.goo.ne.jp/rss/keyword/keyrank_all1/index.rdf' xml=urlfetch.fetch(url).content rootTree=etree.fromstring(xml) titles = rootTree.findall('.//{http://purl.org/rss/1.0/}title') if titles: frg = '<ul>' n=0 for i in titles: if n>0: frg += '<li>'+item+ '</li>' n=n+1; frg += '</ul>' memcache.set("gooRanking",frg) if __name__ == "__main__": updateKeyword()
ここで、updateKeyword()関数を定義しているのは、別のスクリプトでインポートして、この関数を利用できるようにするためである。キャッシュは、何らかの事情で失われる危険性があるので、それに備えて、キャッシュが取得できない場合には、このupdateKeyword()関数を実行できるようにしておく。
cron.yaml で指定した時刻には、このスクリプトファイルが実行される。
その場合には、「if __name__ == "__main__":」の記述にしたがって、updateKeyword() が実行される。
インポートするモジュール
インポートするのは、次の3つのモジュール。
from google.appengine.api import urlfetch
外部ファイルをフェッチするため。
from google.appengine.api import memcache
memcache を利用するため。
import xml.etree.cElementTree as etree
XML からデータを取得するため。
HTML断片の生成
取得した title を HTML のリストとした。
なお「n>1:」として、最初の title を排除しているのは、最初の title が channel 要素の title だからだ。キーワード自体は、item 要素内の title なので、最初の title は除いた。
memcache への保存
ご覧の通り、memcache.set()で、キーと値を指定するだけ。
これで、memcache にデータが保存される。
memcache からのデータの取得については、次回触れる。