Google Reader API

Google ReaderAPIの仕様は非公開ですが、個人的に解析されている方もいて、少ないながら資料もあるので、使ってみました。

目的

ニュースサイトのRSSGoogle Readerに登録して使っています。
カテゴリごとにフィードがあるのですが、複数のカテゴリに属している記事は、複数のフィードに重複して含まれることになります。
それらの重複記事を事前にフィルタし、既読状態にすることが目的です。
あえて載せていませんが、広告などの不要記事を既読にするといった使い方もできます。

手順

  1. 認証
  2. トークン取得
  3. 記事リスト(reading list)取得
  4. 指定記事を既読にする

1. 認証

  • 認証用URIに、gmailのメールアドレス、パスワード等をPOSTします。
  • 成功するとAuth、SID、LSIDが返されます。以降のリクエストには、これらの値を設定します。
# リクエスト設定
my %req = (
  service  => 'reader',
  Email    => $email,
  Passwd   => $password,
  source   => 'test_reader',
  continue => 'http://www.google.com/',
);

# 認証URI に POST
my $ua = LWP::UserAgent->new;
my $res = $ua->post('https://www.google.com/accounts/ClientLogin', \%req);
die $res->status_line if $res->is_error;

# 受け取ったパラメータを次回以降のリクエストのヘッダに設定
my $content = $res->content;
my ($auth) = $content =~ /Auth=(.+?)\n/;
$ua->default_header( 'Authorization' => "GoogleLogin auth=$auth" );
my ($sid) = $content =~ /SID=(.+?)\n/;
$ua->default_header( 'Cookie' => "SID=$sid" );

2. トークン取得

  • データを変更するためには、書き込み用トークンというものをあらかじめ取得する必要があります。変更の必要がなければトークンは不要です。
  • 取得用URIにリクエストすると、トークンが返されます。
# トークン取得URI に GET
$res = $ua->get('http://www.google.com/reader/api/0/token');
die $res->status_line if $res->is_error;

# 受け取ったパラメータを保持
$content = $res->content;
my ($token) = $content =~ m!//(.+?)$!;

3. 記事リスト(reading list)取得

  • 1回のリクエストで、いくつかの記事エントリがリストとしてまとまって落ちてきます。
  • 次のリストを取得するには、リストに含まれている「continuation文字列」を指定して再度リクエストします。
my $continuation = "";
my $loops = 0;

while ($loops++ < 100) {
  # 大量のリストを要求するので、少し間をあける
  sleep 1;
  
  # entry のリストを要求する。
  # 前回の続きを要求するときは、前回の continuation (/feed/gr:continuation/text()) を
  # クエリ文字列に設定してリクエストする。
  $res = $ua->get('http://www.google.com/reader/atom/user/-/state/com.google/reading-list'
       . '?c=' . uri_escape($continuation));
  die $res->status_line if $res->is_error;
  $content = $res->content;

  # feed を XML パーサで解析する。
  my $feed = XMLin($content, ForceArray => ['entry', 'category'], KeyAttr => [], ForceContent => 1);

  ...

  # 続きを要求するために、continuation を保存
  if ($feed->{'gr:continuation'}{content}) {
    $continuation = $feed->{'gr:continuation'}{content};
  } else {
    last;
  }
}

4. 指定記事を既読にする

  • 記事エントリには「ID」が付けられており、記事に対する操作を行うときは、対象記事のIDを指定します。
  • 記事エントリには、0個以上のラベルおよびステートが付けられています。エントリに既読ステートを追加することにより、その記事を既読状態にすることができます。
  • 記事エントリに付けられたラベルにより、フィードが属するフォルダを判別することもできます。以下の例では、「news」フォルダ以外の記事は処理しないようにしています。
# <entry> を全部処理
entries:
foreach my $entry (@{$feed->{entry}}) {
  my $isnews = 0;
  
  # <entry> 内の <category> を全部処理
  foreach my $cat (@{$entry->{category}}) {
    if ($cat->{term}) {
      if ($cat->{term} =~ m!/state/com.google/read$!) {
        # 既読アイテム
        next entries;
      } elsif ($cat->{term} =~ m!/label/news$!) {
        # news フォルダのアイテム
        $isnews = 1;
      }
    }
  }
  next if !$isnews;
  
  # <entry> 直下の <title> のテキストを取得
  my $title;
  if ($entry->{title} && $entry->{title}{content}) {
    $title = $entry->{title}{content};
  } else {
    next;
  }
  
  my $ismulti = 0;
  
  # title/text() が重複する news アイテムは、既読にする。
  # title/text() を %entrytitles に登録していくことで、重複を検知する。
  if ($entrytitles{$title}) {
    $entrytitles{$title}++;
    $ismulti = 1;
  } else {
    $entrytitles{$title} = 1;
  }
  
  if ($ismulti) {
    # 指定アイテムを既読にする
    
    my %req = (
      i => $entry->{id}{content},
      a => 'user/-/state/com.google/read',
      T => $token,
    );

    $res = $ua->post('http://www.google.com/reader/api/0/edit-tag', \%req);
  }
}

ちょっとメモ

全然関係ないですが、RubySSL接続する際のサーバ証明書についてです。

IEGoogleのログインページに行き、証明書をエクスポートする」と解説されているサイトが多かったのですが、これではなぜかRubyがエラーを返してきてしまい、うまくいきませんでした。

もしかすると、エクスポートする際のオプションが間違っていたのかもしれません。
試しにFirefoxでエクスポートした証明書を使ってみたら、問題なく動いたので、それ以上は調べていません。

参考にしたサイト

GoogleReaderAPI - pyrfeed
リファレンス形式でわかりやすくまとめられています。少し古いためか、一部、実際と異なる箇所がありました。
Google Reader APIの認証がまた変わった? - イントレ。
認証方法について、参考にさせていただきました。