RSSより便利なAtomデータの詳細と利用方法について簡単にまとめてみた
概要
Google Data APIがAtom形式のプロトコルを採用しているように、RSS/Atomというサイト情報を配信する仕組みからWebAPIでのリクエストやレスポンスにまでAtomが広く利用されつつあります。Atomって単純なXML文書の拡張でRSSとさほど変わりないと思っていたのですが、最近仕事でAtomに触れる機会が増えてきているので簡単にまとめた記事を書きます。
RSSとの違い
Atomが生まれた経緯はRSSが古い仕様かつRSS1.0/2.0の仕様策定で色々と揉めていたときにAtomを作ろうという動きになったようです。RSSのバージョンごとに非互換性であったり、仕様が明確になっていなかった事が利用者からの不満が多くありました。今後のRSS仕様の策定は2.0から改変が加えられる様子は無いです。RSSの不満は仕様が曖昧な故に配信者に委ねられるという点です。例えばHTMLのマークアップがコンテンツに含めて良いのか?などの規定がありません。AtomはRSSでの反省を踏まえて、特定のベンダに依存しない、全ての人が自由に実装できる、誰でも自由に拡張可能である、明確にかつ詳細に定義する理念を掲げています。
Atomについて
Atomには二つの仕様があります。一つ目はコンテンツ配信フィード用で「Atom配信フォーマット」(Atom Syndication Format)と呼ばれています。もう一つはコンテンツを編集するための「Atom出版プロトコル」(Atom Publishing Protocol)でAtomAPIやAtomPPと呼ばれる事もあるようです。Atom Syndication FormatはRFC4287で、Atom Publishing ProtocolはRFC5023で策定がされています。Atomのメリットをいくつか挙げるとすると独自の名前空間が使えるのでデータを自由にカスタマイズできる、配信内容がSummary(要約)とContent(内容)に明確に分離されているのでデータ構造が分かりやすい、contentフィールドにはテキストは勿論のことながらHTMLや画像/動画も含めて配信する事が可能になります。またIETFという機関が仕様を管理しているのでRSSのように配信者の意向に左右されることはありません。以下はAtomのサンプルです。
<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> <title type="text">dive into mark</title> <subtitle type="html"> A <em>lot</em> of effort went into making this effortless </subtitle> <updated>2005-07-31T12:29:29Z</updated> <id>tag:example.org,2003:3</id> <link rel="alternate" type="text/html" hreflang="en" href="http://example.org/"/> <link rel="self" type="application/atom+xml" href="http://example.org/feed.atom"/> <rights>Copyright (c) 2003, Mark Pilgrim</rights> <generator uri="http://www.example.com/" version="1.0"> Example Toolkit </generator> <entry> <title>Atom draft-07 snapshot</title> <link rel="alternate" type="text/html" href="http://example.org/2005/04/02/atom"/> <link rel="enclosure" type="audio/mpeg" length="1337" href="http://example.org/audio/ph34r_my_podcast.mp3"/> <id>tag:example.org,2003:3.2397</id> <updated>2005-07-31T12:29:29Z</updated> <published>2003-12-13T08:29:29-04:00</published> <author> <name>Mark Pilgrim</name> <uri>http://example.org/</uri> <email>f8dy@example.com</email> </author> <contributor> <name>Sam Ruby</name> </contributor> <contributor> <name>Joe Gregorio</name> </contributor> <content type="xhtml" xml:lang="en" xml:base="http://diveintomark.org/"> <div xmlns="http://www.w3.org/1999/xhtml"> <p><i>[Update: The Atom draft is finished.]</i></p> </div> </content> </entry> </feed>
AtomAPIについて
Atom Publishing ProtocolではXMLの送受信によってサーバ上のデータを更新します。送受信で用いる形式がXMLなので文字コードに悩まされる必要がありません。そして開発者は名前空間を独自に拡張可能なのでスケーラブルなデータ形式定義が可能です。またRESTfulのサポートだけではなくSOAPでの通信もできます。XML-RPCというXML形式のシリアライズデータを格納する形式がありますがセキュリティや名前空間が使えない等の拡張性、日本語等が少し前まで利用できないなどの問題があったためAtomAPIが策定されることになりました。AtomAPIの代表的な認証方式としてWSSEというものがあり、これはハッシュアルゴリズム(SHA1)でパスワード(password)とランダム文字列(Nonce)や作成日時(Created:ISO-8601形式)をもとに生成し、HTTP Headerに設定します。
はてなのAtomを返却するAPIを使ってみる
はてなダイアリーのAtom Publishing Protocolを使ってみます。仕様についてはhttp://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%C0%A5%A4%A5%A2%A5%EA%A1%BCAtomPubここに記述してあるので詳しい内容は省略しますがWSSEというアカウントとパスワードベースの認証が必要になります。認証を通過するPHPコードをサンプルで書いてみました。コマンドラインで引数指定の利用を想定してます。またソースを実行して得られるAtomデータも載せておきます。
<?php //timezone設定 date_default_timezone_set('Asia/Tokyo'); //コマンドラインから引数を入力 $user = $argv[1]; $password = $argv[2]; //WSSE認証 $wsse[ 'UsernameToken Username' ] = $user; $wsse[ 'Created' ] = date( 'Y-m-d\TH:i:s\Z' ); $wsse[ 'Nonce' ] = pack( 'H*', sha1( md5( time() ) ) ); $wsse[ 'PasswordDigest' ] = base64_encode( sha1( $wsse[ 'Nonce' ] . $wsse[ 'Created' ] . $password, true ) ); foreach( $wsse as $key => $value ) { $wsse_headers[] = $key . '="' . $value . '"'; } $headers[] = 'X-WSSE: ' . implode( ',', $wsse_headers ); $headers[] = 'Content-Type: application/xml; Charset="UTF-8"'; //リクエスト $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, 'http://d.hatena.ne.jp/yutakikuchi/atom/blog/20111231/1325310004' ); curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); $ret = curl_exec($ch); print( $ret );<?xml version="1.0" encoding="utf-8"?> <entry xmlns="http://www.w3.org/2005/Atom"> <updated>2011-12-31T14:40:04+09:00</updated> <published>2011-12-31T14:40:04+09:00</published> <app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-31T14:40:04+09:00</app:edited> <id>tag:d.hatena.ne.jp,2011:diary-yutakikuchi-20111231-1325310004</id> <link rel="edit" href="http://d.hatena.ne.jp/yutakikuchi/atom/blog/20111231/1325310004"/> <link rel="alternate" type="text/html" href="http://d.hatena.ne.jp/yutakikuchi/20111231/1325310004"/> <author> <name>yutakikuchi</name> </author> <title>[自然言語処理][Python]「魔法少女まどか☆マギカ」の台詞をNLTK(Natural Language Toolkit)で解析する</title> <content type="html"> <div class="section"> <h4> <span style="font-weight:bold;font-size:large;" class="deco">目次</span></h4> <blockquote> <ol> <li> 魔法少女まどか☆マギカ</li> <li> NLTK</li> <li> NLTKコーパス</li> <li> まど☆マギ台詞単語解析</li> <li> まど☆マギ台詞形態素解析</li> </ol> </blockquote> <br> <h4> <span style="font-weight:bold;font-size:large;" class="deco">魔法少女まどか☆マギカ</span></h4> <blockquote> <p><iframe width="560" height="315" src="http://www.youtube.com/embed/k-M8BkFTbpw" frameborder="0" allowfullscreen></iframe></p> (略) </entry>折角なのでAtomをArray形式にパースする処理も加えたいと思います。phpのsimplexml_load_stringを利用しているだけです。simplexml_load_stringについては以前記事を書いたので詳しくはこちらを参照してください。SimpleXMLElement Objectの参照 - Yuta.Kikuchiの日記 以下では上のサンプルphpコードに対する追加行だけを記載しています。例えばentry/titleというフィールドを取得したい場合は次のような処理を書きます。
<?php (略) //リクエスト $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, 'http://d.hatena.ne.jp/yutakikuchi/atom/blog/20111231/1325310004' ); curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); $ret = curl_exec($ch); //print( $ret ); //entryフィールド取得 $entry = simplexml_load_string( $ret ); echo $entry->title;実行結果として[自然言語処理][Python]「魔法少女まどか☆マギカ」の台詞をNLTK(Natural Language Toolkit)で解析するという内容を得る事ができます。ついでのついでですがPythonでtitleを取得するような処理を書くと以下のようになります。AtomのパースにはBeautifulSoupを利用しています。
#!/usr/bin/env python # -*- coding: utf-8 -*- import sys,random,datetime,time import base64,sha import urllib2,urllib from BeautifulSoup import BeautifulSoup def createAuthHeader( user, passwd ): nonce = sha.sha(str(time.time() + random.random())).digest() nonce64 = base64.encodestring(nonce).strip() created = datetime.datetime.now().isoformat() + 'Z' passdigest = sha.sha(nonce + created + passwd).digest() pass64 = base64.encodestring(passdigest).strip() wsse = 'UsernameToken Username="%(u)s", PasswordDigest="%(p)s", Nonce="%(n)s", Created="%(c)s"' value = dict(u = user, p = pass64, n = nonce64, c = created) return wsse % value def main(): user = sys.argv[1] passwd = sys.argv[2] wsse = createAuthHeader( user, passwd ) contenttype = 'application/xml; Charset="UTF-8"' opener = urllib2.build_opener() url = 'http://d.hatena.ne.jp/yutakikuchi/atom/blog/20111231/1325310004' opener.addheaders = [( 'X-WSSE', wsse ),( 'Content-Type', contenttype )] atom = opener.open( url ).read() soup = BeautifulSoup( atom ) print( soup.find( 'title' ) ) if __name__ == '__main__': main()