RSSより便利なAtomデータの詳細と利用方法について簡単にまとめてみた

[WebAPI] : RSSより便利なAtomデータの詳細と利用方法について簡単にまとめてみた


概要

Google Data APIAtom形式のプロトコルを採用しているように、RSS/Atomというサイト情報を配信する仕組みからWebAPIでのリクエストやレスポンスにまでAtomが広く利用されつつあります。Atomって単純なXML文書の拡張でRSSとさほど変わりないと思っていたのですが、最近仕事でAtomに触れる機会が増えてきているので簡単にまとめた記事を書きます。

RSSとの違い

Atomが生まれた経緯はRSSが古い仕様かつRSS1.0/2.0の仕様策定で色々と揉めていたときにAtomを作ろうという動きになったようです。RSSのバージョンごとに非互換性であったり、仕様が明確になっていなかった事が利用者からの不満が多くありました。今後のRSS仕様の策定は2.0から改変が加えられる様子は無いです。RSSの不満は仕様が曖昧な故に配信者に委ねられるという点です。例えばHTMLのマークアップがコンテンツに含めて良いのか?などの規定がありません。AtomRSSでの反省を踏まえて、特定のベンダに依存しない、全ての人が自由に実装できる、誰でも自由に拡張可能である、明確にかつ詳細に定義する理念を掲げています。

Atomについて

Atomには二つの仕様があります。一つ目はコンテンツ配信フィード用で「Atom配信フォーマット」(Atom Syndication Format)と呼ばれています。もう一つはコンテンツを編集するための「Atom出版プロトコル」(Atom Publishing Protocol)でAtomAPIAtomPPと呼ばれる事もあるようです。Atom Syndication FormatはRFC4287で、Atom Publishing ProtocolはRFC5023で策定がされています。Atomのメリットをいくつか挙げるとすると独自の名前空間が使えるのでデータを自由にカスタマイズできる、配信内容がSummary(要約)とContent(内容)に明確に分離されているのでデータ構造が分かりやすい、contentフィールドにはテキストは勿論のことながらHTMLや画像/動画も含めて配信する事が可能になります。またIETFという機関が仕様を管理しているのでRSSのように配信者の意向に左右されることはありません。以下はAtomのサンプルです。

xml version="1.0" encoding="utf-8"?>
xmlns="http://www.w3.org/2005/Atom">
  <span class="synType">type</span>=<span class="synConstant">"text"</span><span class="synIdentifier">></span>dive into mark<span class="synIdentifier">
  type="html">
A &lt;em&gt;lot&lt;/em&gt; of effort
went into making this effortless
  
  2005-07-31T12:29:29Z
  tag:example.org,2003:3
  rel="alternate" type="text/html"
    hreflang="en" href="http://example.org/"/>
  rel="self" type="application/atom+xml"
    href="http://example.org/feed.atom"/>
  Copyright (c) 2003, Mark Pilgrim
  uri="http://www.example.com/" version="1.0">
Example Toolkit
  
  
</span>Atom draft-07 snapshot<span class="synIdentifier">
rel="alternate" type="text/html"
      href="http://example.org/2005/04/02/atom"/>
rel="enclosure" type="audio/mpeg" length="1337"
      href="http://example.org/audio/ph34r_my_podcast.mp3"/>
tag:example.org,2003:3.2397
2005-07-31T12:29:29Z
2003-12-13T08:29:29-04:00

  Mark Pilgrim
  http://example.org/
  f8dy@example.com


  Sam Ruby


  Joe Gregorio

type="xhtml" xml:lang="en"
      xml:base="http://diveintomark.org/">
  
xmlns="http://www.w3.org/1999/xhtml">

[Update: The Atom draft is finished.]

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データも載せておきます。



//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"?>
xmlns="http://www.w3.org/2005/Atom">
  2011-12-31T14:40:04+09:00
  2011-12-31T14:40:04+09:00
  <app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-31T14:40:04+09:00app:edited>
  tag:d.hatena.ne.jp,2011:diary-yutakikuchi-20111231-1325310004
  rel="edit" href="http://d.hatena.ne.jp/yutakikuchi/atom/blog/20111231/1325310004"/>
  rel="alternate" type="text/html" href="http://d.hatena.ne.jp/yutakikuchi/20111231/1325310004"/>
  
yutakikuchi
  
  </span>[自然言語処理][Python]「魔法少女まどか☆マギカ」の台詞をNLTK(Natural Language Toolkit)で解析する<span class="synIdentifier">
  type="html">
&lt;div class="section"&gt;
&lt;h4&gt; &lt;span style="font-weight:bold;font-size:large;" class="deco"&gt;目次&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote&gt;

&lt;ol&gt;
&lt;li&gt; 魔法少女まどか☆マギカ&lt;/li&gt;
&lt;li&gt; NLTK&lt;/li&gt;
&lt;li&gt; NLTKコーパス&lt;/li&gt;
&lt;li&gt; まど☆マギ台詞単語解析&lt;/li&gt;
&lt;li&gt; まど☆マギ台詞形態素解析&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;br&gt;

&lt;h4&gt; &lt;span style="font-weight:bold;font-size:large;" class="deco"&gt;魔法少女まどか☆マギカ&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;iframe width="560" height="315" src="http://www.youtube.com/embed/k-M8BkFTbpw" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;/p&gt;

(略)

折角なのでAtomをArray形式にパースする処理も加えたいと思います。phpのsimplexml_load_stringを利用しているだけです。simplexml_load_stringについては以前記事を書いたので詳しくはこちらを参照してください。SimpleXMLElement Objectの参照 - Yuta.Kikuchiの日記 はてなブックマーク - SimpleXMLElement Objectの参照 - Yuta.Kikuchiの日記 以下では上のサンプルphpコードに対する追加行だけを記載しています。例えばentry/titleというフィールドを取得したい場合は次のような処理を書きます。


()

//リクエスト
$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()