2011年3月20日

Hack for Japan の Google モデレーターのデータを見る

Hack For Japan が開催されています。

三連休を利用したスプリントで、

  1. 3/19, 3/20 - 何をするか?というアイデア出し。
  2. 3/21 - 実際にアイデアを実現する。

ということで、アイデアを見てみます。 モデレーターのデータから生成した HTML はこちらに置いてあります。

http://dl.dropbox.com/u/1880322/hack4jp-moderators.html

アイデアの一覧を見る

アイデアは Google モデレーター で管理されています。 モデレーターは、個人的な感覚だと、閲覧には不向きな印象です。 「プレゼンテーション表示」だと一覧性が向上しますので、ザーッと眺めるだけだとこちらが便利です。

CSV で出力できますので、Excel などで閲覧することもできます。。。 と思いましたが、ダウンロードしたファイルはユニコードをエンコードしていませんでしたので、 普通の人だと日本語を読むことができません。 あと、カラムもズレている印象ですね。

それは困った... ということで、データだけを抜き出して HTML に整形し直します。 とはいえ、この辺りは将来的には改善されることでしょう。

Google スプレッドシートのデータプロジェクトリスト (となっていますが、実際には議論のまとめ) もあります。

API を使って Moderator のデータを抜き出す

モデレーターにも API が用意されており、 google-api-python-client もこれに対応しています。 サンプルも同梱されていますので、これをベースにしてデータを抜き出します。 moderator に関するサンプルは2つあります。

  • samples/moderator/moderator.py - 認証に OAuth 1.0 系を使うコマンドラインツール
  • samples/oauth2/moderator/moderator.py - 認証に OAuth 2.0 系を使うコマンドラインツール

OAuth 2.0 系の方をベースにします。 認証フローに関しては Google API Python Client (2010年11月の記事) で書いたものとほぼ一緒です。 認可を許可するスコープが異なりますので、認可の画面は「モデレーターのデータにアクセスしても良いですか?」という表示になります。 認証部分のコードはこんな感じです。 認証キーなどをセットしてから run を呼び出すだけです。

from oauth2client.file import Storage
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run

FLOW = OAuth2WebServerFlow(
    client_id='XXXXXXXX',
    client_secret='XXXXXXXX',
    scope='https://www.googleapis.com/auth/moderator',
    user_agent='moderator-cmdline-sample/1.0')

def main():
  storage = Storage('moderator.dat')
  credentials = storage.get()

  if credentials is None or credentials.invalid == True:
    credentials = run(FLOW, storage)

次に、サービスディスカバリの機構を使って moderator 用のサービスオブジェクトを構築します。 (ディスカバリに関しては GData 3 の discovery の中身 (2010年11月の記事) も参考に)

import httplib2
from apiclient.discovery import build

def main():

  # .. 上記の認証フロー

  http = httplib2.Http(cache=".cache")
  http = credentials.authorize(http)

  service = build("moderator", "v1", http=http)

モデレーターのデータは次の4種類から成ります。 (Moderator concepts)

  • シリーズ (Series)
  • トピック (Topic)
  • サブミッション (Submission)
  • 投票 (Vote)

それぞれのデータを扱うためにサービスからリソースを生成します。 今回はシリーズとサブミッションを扱います。 シリーズとトピックの ID は、取り扱うモデレーターのページの URL を参考にします。 (10進数表記と16進数表記に注意)

def main():

  # .. 上記の続き

  sid = 0x6441b  # seriesId of target contens.
  tid = 0x40     # topicId of target contens.

  def print_votes(vote):
    print 'Votes: +%d, -%d' % (vote['plusVotes'], vote['minusVotes'])

  series = service.series().get(seriesId=sid).execute()
  counts = int(series['counters']['submissions'])

  print series['name']
  print series['description']
  print 'Submissions: %d' % (counts,)
  print_votes(series['counters'])

  per_page = 20
  start = 0

  def printer(items):
    for item in items:
      print item['text']
      print_votes(item['counters'])

  while start < counts:
    submissions = service.topics().submissions().list(seriesId=sid,
          topicId=tid, max_results=per_page, start_index=start).execute()
    printer(submissions['items'])
    start += per_page

HTML に整形し直す

標準出力に print しただけだと見栄えが悪いので HTML に整形し直します。 ここでは、テンプレートエンジンとして Jinja2 を使います。

次の4種類の情報を表示します。

  • シリーズの概要、統計情報
  • サブミッションのテキスト
  • サブミッションの投票状況
  • サブミッションのオリジナルデータへのリンク

リンクを生成するときに、ID の文字列を16進数表記にマッピングする必要があります。

テンプレート部分

import jinja2

BASE = u'''
<!DOCTYPE HTML>
<html lang="ja"><head>
  <meta charset="UTF-8" />
  <meta name="viewport" value="width=device-width,user-scalable=no" />
  <title>Hack For Japan Moderators - Mirror</title>
</head><body><div id="wrapper">
  <div id="header">{% block series %}{% endblock %}</div>
  <div id="main">{% block submissions %}{% endblock %}</div>
  <div id="footer">Generated around {{ now }}.</div>
</div></body></html>
'''

TEMPLATE = u'''
{% extends "base.html" %}

{% block series %}
<a href="{{ series.id|to_original_url }}">
  {{ series.name }}</a> - Mirror
<p>{{ series.description }}</p>
<p>Submissions: {{ series.counters.submissions }},
   Total Votes: +{{ series.counters.plusVotes }},
                -{{ series.counters.minusVotes }}</p>
{% endblock %}

{% block submissions %}
{% for item in submissions %}
<div>
  <p>{{ item.text }}</p>
  <p>Votes: +{{ item.voteCounters.plusVotes }}
            -{{ item.voteCounters.minusVotes }}
    <a href="{{ item.id|to_original_url }}">-&gt;Go</a>
  </p>
</div>
{% endfor %}
{% endblock %}
'''

TEMPLATES = {'base.html': BASE, 'template.html': TEMPLATE}

書き出す部分

from datetime import datetime

def main():

  # .. 上記の続き

  url = 'https://www.google.com/moderator/#15/e=%s&t=%s.%s' % (hex(sid)[2:],
        hex(sid)[2:], hex(tid)[2:])

  def to_original_url(id):
    if 'submissionId' in id:
      return '%s&q=%s.%s' % (url,
            hex(int(id['seriesId']))[2:], hex(int(id['submissionId']))[2:])
    return url

  params = {'now': datetime.now(),
        'series': series, 'submissions': submissions}
  env = jinja2.Environment(loader=jinja2.DictLoader(TEMPLATES))
  env.filters['to_original_url'] = to_original_url
  tpl = env.get_template('template.html')
  writer = open('hack4jp-moderators.html', 'wb')
  writer.write(tpl.render(params).encode('utf-8'))
  writer.close()

あとは出力された HTML ファイルのデザインなどを調整すれば冒頭の HTML の出来上がりです。 使い捨てスクリプトなので jinja2.DictLoader を使っていますが、 通常は jinja2.FileSystemLoader などを使って HTML は別ファイルで管理した方が良いでしょう。

NOTE:

  • 3/20 13:20 くらい - Submissions: 130, Total Votes: +2243, -439
  • 3/20 21:30 くらい - Submissions: 179, Total Votes: +3081, -887

まとめ

Google モデレーターのデータを HTML に変換しました。 ざっくり拝見したところ、1日で成果が出そうなことから中長期的に継続していかなくてはいけないことまで、 多種多様なアイデアがありそうです。

  • いろいろなマッチング
  • 継続的な心のケア
  • 計画停電の情報
  • スマートフォンのアプリケーション、オフライン対応
  • フィーチャーフォンへの画面変換、IT に不慣れな方への対応

一方でバックエンドの仕組みとしては、汎用的に使えることと、ローカルな情報を絞り込むことの兼ね合いかなぁ、と思います。 いずれにしても、相互に交換できるデータを使って、みんなで共有できると良さそうです。

コメントを投稿