読者です 読者をやめる 読者になる 読者になる

はてなAPIとS3を使って隊員ごとのブクマ数をブログパーツ化してみた

このブログは複数人で同一アカウントを使って書いていて、だれが書いたかはカテゴリに『X号』ってのをつけて区別してます。

開設から2週間たって記事もたまってきたので、隊員ごとの記事数、はてブ数を表示するブログパーツを作ってみました。
サイドバーの下のほうにこんな感じでついてるやつです。

f:id:oretachino:20141213181650p:plain

今のところスマホだと出ません(´・ω・`)

実装について

はてなのAPIで記事情報とブックマーク数を取得してS3にアップロード、ブログパーツから読み込む、という感じで作ってみました。

はてなブログの記事URLとカテゴリははてなブログAtomPubで取得しました。
Basic認証であればcurlコマンドで試せて、こんな感じにXMLで記事リストが取れます。

$ curl --user oretachino:<APIキー> https://blog.hatena.ne.jp/oretachino/oretachino.hatenablog.com/atom/entry

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:app="http://www.w3.org/2007/app">

  <link rel="first" href="https://blog.hatena.ne.jp/oretachino/oretachino.hatenablog.com/atom/entry" />
  <link rel="next" href="https://blog.hatena.ne.jp/oretachino/oretachino.hatenablog.com/atom/entry?page=1417683554" />
  
  <title>俺たちのブログ</title>
  <link rel="alternate" href="http://blog.oretachino.com/"/>
...
  <entry>
<id>tag:blog.hatena.ne.jp,2013:blog-oretachino-8454420450075581217-8454420450076797777</id>
<link rel="edit" href="https://blog.hatena.ne.jp/oretachino/oretachino.hatenablog.com/atom/entry/8454420450076797777"/>
<link rel="alternate" type="text/html" href="http://blog.oretachino.com/entry/2014/12/12/032757"/>
<author><name>oretachino</name></author>
<title>test-unitさえあればご飯大盛り三杯はイケる</title>
<updated>2014-12-12T03:27:57+09:00</updated>
<published>2014-12-12T03:27:57+09:00</published>
<app:edited>2014-12-12T11:52:39+09:00</app:edited>
<summary type="text">rspec3 に疲弊している皆さんこんにちは。今日は rspec3 に疲れた皆さんへ憩いの場として test-unit gem のお話を提供したいと思います。 test-unit gem について 「Rubyのテスティングフレームワークの歴史(2014年版)」の記事に詳しく書いて…</summary>
<content type="text/x-markdown">rspec3 に疲弊している皆さんこんにちは。今日は rspec3 に疲れた皆さんへ憩いの場として test-unit gem のお話を提供したいと思います。

## test-unit gem について
...
<category term="7号" />

<app:control>
  <app:draft>no</app:draft>
</app:control>

  </entry>
  
  <entry>
...

記事ごとのはてブ数は、はてなブックマーク件数取得APIを使って取得します。

$ curl 'http://api.b.st-hatena.com/entry.counts?url=http://blog.oretachino.com/entry/2014/12/04/175914&url=http://blog.oretachino.com/entry/2014/12/05/001837'
{
  "http://blog.oretachino.com/entry/2014/12/04/175914":0,
  "http://blog.oretachino.com/entry/2014/12/05/001837":191
}

この2つのAPIを使って、隊員ごとのブックマーク数を取得、S3にアップロードするrubyスクリプトをやっつけ気味に作成してみました。

#!/usr/bin/env ruby
require 'uri'
require 'net/http'
require 'rexml/document'
require 'json'
require 'aws-sdk'
require 'pp'

# はてなブログAPI
BLOG_USERNAME = 'oretachino'
BLOG_API_KEY = '<APIキー>'
BLOG_DOMAIN = 'oretachino.hatenablog.com'
BLOG_API_ENDPOINT = "https://blog.hatena.ne.jp/#{BLOG_USERNAME}/#{BLOG_DOMAIN}/atom/entry"

# はてなブックマーク
BOOKMARK_API_ENDPOINT =  'http://api.b.st-hatena.com/entry.counts'

# AWS
AWS_S3_BUCKET = 'oretachino'
AWS_ACCESS_KEY_ID = '<アクセスキー>'
AWS_SECRET_ACCESS_KEY = '<シークレットアクセスキー>'

# はてなブログのエントリ取得
# TODO: タイトルとかいらん情報取ってる
def get_entries
  entries = {}
  next_endpoint = BLOG_API_ENDPOINT

  while next_endpoint do
    uri = URI.parse next_endpoint
    req = Net::HTTP::Get.new uri.request_uri
    req.basic_auth BLOG_USERNAME, BLOG_API_KEY
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true)  do |http|
      http.request(req)
    end
  
    doc = REXML::Document.new(res.body)
    doc.elements.each("feed/entry") do |entry|
      next if entry.elements["app:control/app:draft"].text == 'yes'
  
      url = entry.elements["link[@rel='alternate']"].attributes["href"]
      title = entry.elements["title"].text
      categories = []
      entry.elements.each("category") do |category|
        categories << category.attributes["term"]
      end
  
      entries[url] = {}
      entries[url][:title] = title
      entries[url][:account] = categories.find{|c| c =~ /^\d+$/}
    end
  
    next_link = doc.elements["feed/link[@rel='next']"]
    next_endpoint = next_link ? next_link.attributes["href"] : nil
  end

  entries
end

# ブックマーク数の取得
# TODO: 1回で取得できる件数は最大50件なので、記事増えたらエラーになる
def get_bookmark_counts(urls)
  uri = URI.parse BOOKMARK_API_ENDPOINT
  path = uri.request_uri + '?' + urls.map{|url| "url=#{URI.escape(url)}"}.join('&')
  req = Net::HTTP::Get.new path
  res = Net::HTTP.start(uri.host, uri.port)  do |http|
    http.request(req)
  end
  JSON.parse(res.body)
end

# S3に書き込み
def write_s3_object(path, json)
  AWS.config(
    access_key_id: AWS_ACCESS_KEY_ID,
    secret_access_key: AWS_SECRET_ACCESS_KEY,
    region: 'ap-northeast-1'
  )
  s3 = AWS::S3.new
  
  bucket = s3.buckets[AWS_S3_BUCKET]
  object = bucket.objects[path]
  object.write(json, acl: :public_read, content_type: 'application/json')
end

entries = get_entries
bookmark_counts = get_bookmark_counts(entries.keys)
bookmark_counts.each do |url, count|
  entries[url][:bookmark_count] = count
end

counts_by_account = {}
entries.each do |url, entry|
  counts_by_account[entry[:account]] ||= {
    entry_count: 0,
    bookmark_count: 0
  }
  counts_by_account[entry[:account]][:entry_count] += 1
  counts_by_account[entry[:account]][:bookmark_count] += entry[:bookmark_count]
end

write_s3_object('counts_by_account.json', counts_by_account.sort.to_json)

実行すると、こんな感じでS3上にJSONファイルが作成されます。

このスクリプトをテキトウなサーバで15分毎に実行するようにしてます。

[1gou@batch01 ~]$ crontab -l
*/15 * * * * /home/1gou/bin/update_oretachino_counts.rb

S3のバケットは事前に作成しておきます。
設定としてはStatic Website Hostingの有効化と、今回はJSONのまま外部JavaScriptで利用するので、Cross Origin Resource Sharing (CORS)設定も入れておきます。CORS設定はS3バケットのプロパティから『Permissions → Add CORS Configuration → デフォルトのままSave』するだけです。

あとは、はてなブログのデザイン設定で、サイドバーのモジュール追加から『HTML』を選択してコードを貼り付けると作業完了です。

f:id:oretachino:20141213182919p:plain

貼り付けたHTMLの中身はこんな感じです。

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<script type="text/javascript">
$(function() {
  $.ajax({
    type: "GET",
    url: "https://s3-ap-northeast-1.amazonaws.com/oretachino/counts_by_account.json",
    dataType: "json",
    success: function(data) {
      $.each(data, function(){
        var account = this[0];
        var entry_count = this[1].entry_count;
        var bookmark_count = this[1].bookmark_count;

        var category_link = "archive/category/" + account;
        var text = account + " : " + entry_count + " / " + bookmark_count;
        var ul = $("div#hatena-module-counts-by-account > ul");

        ul.append("<li><a href='" + category_link + "'>" + text + "</a></li>");
      });
    }
  });
});
</script>

<div id='hatena-module-counts-by-account'><ul></ul></div>

jqueryをそのまま読み込んでて行儀悪いですね。

まとめ

というわけで、はてなのAPIとS3を使って簡単な初ブログパーツを作ってみました。

しかし6号、7号のはてブ数すげえな・・・