2010年11月24日水曜日

50音別インデックスの実装

 Aozoramazonでは、作者毎に50音別の作品名インデックスを用意してあります。青空文庫には全作者を網羅した長大な50音別作品リストがありますが、Aozoramazonでは表示するリストを小分けにする必要がありました。これは、Aozoramazonを動かしているGoogle App Engineでは、1000件を超えるエントリーを一度に取り出せないと言う制限があるためです。この制限と折り合いを付けるために、全作者ではなく作者毎の50音別リストにしました。

前準備として、作者毎に作品頭文字リストを用意します。

  • 作品情報の更新時に青空文庫の図書カードを解析して作品の読みを取得する
  • 作品読みの頭文字を、作者情報の頭文字リストに追加する。リストに存在する文字なら何もしない。
こうすると、例えば菊池寛の作者情報に作品頭文字リスト「あいきくみえむおらせしたちとわかなへやさこふて」が出来上がります。並び順に意味は無いので、ソートは行いません。

表示の際の強調の有無は、CSSのclassで区別します。考え方としては、強調のclassを与えるのではなく、非強調のclassを取り除く事で相対的にリストにある文字だけが強調されて見えるようにします。一見、逆(有効な文字を強調する)でも良いように思えますが、不要な文字が普通のリンクとして見えては都合が悪いため、不要な文字にも目立たせないためのスタイル指定が必要になります。したがって、必要な文字だけ非強調のclassを取り除く方が近道です。

まず、非強調用のCSSクラス"gapCell"を指定した50音表を用意します。(gapCellという名前に深い意味はありません。元々は文字の無いセルを目立たなくするために用意したCSSクラスでした。)
各文字のセルはUTF-8をベースにしたIDで識別できるようにしておきます。

<table class="novelInitialTable"> 
       <caption>作品名インデックス</caption> 
       <tr> 
  <td class="gapCell" id="i_e38182">
  <a href="/person?authorId=83&amp;initial=%e3%81%82">あ</a>
  </td> 
  <td class="gapCell" id="i_e38184">
  <a href="/person?authorId=83&amp;initial=%e3%81%84">い</a>
  </td> 
  <td class="gapCell" id="i_e38186">
  <a href="/person?authorId=83&amp;initial=%e3%81%86">う</a>
  </td> 
  <td class="gapCell" id="i_e38188">
  <a href="/person?authorId=83&amp;initial=%e3%81%88">え</a>
  </td> 
  <td class="gapCell" id="i_e3818a">
  <a href="/person?authorId=83&amp;initial=%e3%81%8a">お</a>
  </td> 
  <td class="gapCell">&nbsp;</td> 
  <td class="gapCell" id="i_e381af">
  <a href="/person?authorId=83&amp;initial=%e3%81%af">は</a>
  </td> 
      ...

CSSの中では、非強調用のgapCellクラスを背景に対して目立たない色で定義します。

td.gapCell a:link {
    color: gray;
}
td.gapCell a:visited {
    color: gray;
}
td.gapCell a:hover {
    color: gray;
}

ページがロードされた直後に実行されるJavaScriptで、強調したい文字のgapCellクラスを取り除きます。
非強調が解除される、すなわち、非強調に対比して強調されているように見えるようになります。

$(document).ready(function(){      
      $("#i_e38182").removeClass("gapCell");      
      $("#i_e38184").removeClass("gapCell");      
      $("#i_e3818d").removeClass("gapCell");
      ...

テンプレートの中で、指定された文字に対してのみremoveClass()するJavaScriptを出力します。テンプレートを処理すると、上のようなJavaScriptソースになります。

$(document).ready(function(){
      {% for i in initialList %}
      $("#i_{{i}}").removeClass("gapCell");
      {% endfor %}

PythonのCGIでは、作品頭文字リストをテンプレートに渡します。
HTMLで表示できるように、UTF-8を16進表記にしてエスケープ文字(\x)を取り除いた文字列のリストとして渡しています。

initialList = []
if author.initialList:
    initialList = [c.encode('utf-8').encode('string-escape').replace('\\x', '') for c in author.initialList]
template_values['initialList'] = initialList

最終的に、下のような表示になります。

2010年11月19日金曜日

作品IDとISBNの対応データ

青空文庫の底本に対するISBN検索結果をダウンロードできるようにしました。
http://aozoranavi.appspot.com/ISBN.zip

ZIPファイルにUTF-8でエンコードしたCSVファイル(ISBN.CSV)が一つだけアーカイブしてあります。ISBNデータが存在する作品だけをリストアップしています。

底本のISBNを検索して紐付け

 青空文庫の底本のISBNをAmazonのProduct Advertising APIで検索して紐付けしてみました。昨夜から、Aozoramazonの作品情報でISBNを表示しています。

  • 公開されている作品数 9617
  • ISBNと関連付けできた作品数 4902 (51%)
  • 関連付けが無い作品数 4715(49%)

関連付けされていない4715作品の内訳。

  • 底本と出版社が書かれている作品数 4550
  • 底本のみで出版社が未記載の作品数 17
  • 底本も出版社も未記載の作品数 148

関連付けできた作品はほぼ半分です。関連付けした作品の一部は手入力ですが(122作品、底本は『龍馬の手紙』)、他は底本タイトル、作者、出版社を指定して検索した結果をそのまま使っています。
 結果の正当性は数が膨大なので確認していません。検索条件に出版年や版数を入れていないので、『人間失格』等のように底本(1952年)と異なる改定版が関連付けられている場合もありますが、無関係の的外れな作品が紐付けされている例はあまり無いだろうと思っています。

毎日未明に走らせている更新スクリプトでもISBNを検索するようにしました。ただし、『海援隊約規』のように底本の著者が青空文庫作品の作者と異なる場合は、関連付けに失敗します。

2010年11月17日水曜日

カスタマーレビューの有無によって表示を変更

 JavaScriptだとクロスドメインの制限があるので、レビューの有無だけを調べるcgiを用意してajaxで問い合わせるようにしました。レビューのリンクは最初はグレーで表示し、レビューがあれば通常のリンク色(青)に、レビューが無ければ薄いグレーに変化します。背景色に対して目立つか溶け込むかでレビューの有無が分かります。

2010年11月16日火曜日

Amazonカスタマーレビューの表示判定

 Product Advertising APIの仕様変更によって、カスタマーレビューを表示する方法がIFRAMEのみに限定されてしまいました。しかし、渡されたIFRAMEURLを常に表示していては、レビュー無しの空振りが多すぎます。IFRAMEの内容を加工しようとは思いませんが、IFRAMEの中を先読みして表示する/しないの切り替えは実装しようと思います。

底本とISBNの紐付け

 青空文庫の底本情報にISBNがあれば便利と書きましたが、公開されているDB項目(data_item.csv)を見たらISBNはありませんでした。Amazonの検索を使えばかなり整備できると思うので、独自にやってみようと思います。底本にISBNが無い場合もあるので、全ての作品を関連付けることは出来ません。でも、一度やっておけばAmazonのAPI呼び出し回数を劇的に減らせるので、Aozoramazonとしては充分にメリットがあります。

2010年11月15日月曜日

青空文庫の底本を検索してわかった事

 Aozoramazonは当初、検索結果が空振り(検索結果なし)だったり、 関係無い書籍がヒットして本来の底本が埋もれてしまう事が今以上に起きていました。底本検索の考え方や、精度向上のために行った工夫を書き残しておきます。


主な 元ネタは公開日に書き込んだtwitterのログです。
http://twilog.org/ThinTube/date-101107


  • 検索は2種類の方法でやっています。基本的にItemSearchという点では共通ですが、条件の与え方が違います。一つはタイトル、出版社、著者を指定した底本検索、もう一つはこれらをキーワードとして同列に列挙するキーワード検索。
  •  出版社名に「xx文庫、xx社」と入力されている場合が多いので、底本検索の検索条件に出版社を渡す場合は「、」の前を全て削除します。さもないと、まずヒットしません。
  • 文庫名と類似する問題ですが、底本名に著者名を入れてしまっている場合があり、これも検索の障害になります。最近登録された例では、「龍馬の手紙、宮地佐一郎」があります。作品に登録されている著者と書籍の著者が異なるのですが、底本情報に著者の項目が無いため、他の項目に紛れ込んでいます。底本のタイトルに読点を含む他の作品に影響するため、一概に「、」以降を削除する訳にも行きません。この対策は諦めて、キーワード検索にマッチする事に望みを繋いでいます。
  • 底本に「第五巻」とあるのをそのまま渡しても、AmazonのAPIでは大抵ヒットしません。Amazonでは「第5巻」のように巻数がアラビア数字になっている場合が多いです。そのままの底本名でヒットしない場合は漢数字をアラビア数字に置き換えて検索します。定量的に調べてはいませんが、これでずいぶんヒット率上がりました。あと、「巻」を省略することでヒットする事もあります。
  • 底本優先で探すので、Titleの条件は底本名の次に作品名をトライします。次に作品名をTitleとして検索します。
  • 底本がヒットしないとキーワード検索で適当に探します。新しい発見という意味では、その方が作品との出会いは増えますが、青空文庫の作品選択を助けるという意味では脱線気味です。
  • 底本検索は出版社と著者を指定するので、割と確度が高いです。それに比べるとキーワード検索はいいかげん。でも、底本が見つからない場合は仕方ないので表示しますし、関連商品との出会いも楽しいといえば楽しいので、作品毎のページでは両方を表示します。
  • 底本の情報にISBNがあれば、検索はとても楽、というか、検索するまでもなくカーリル等の書籍情報をダイレクトに表示できるはずです。 
  今でも検索結果が18禁のオンパレードとか、笑顔が引きつりそうなページを表示してしまう事が多々あります。Amazonの検索結果には対象年齢の情報もあるっぽいので、よい子が見ても大丈夫なように、そのうちフィルタリングをしようと思います。

生没年月日の不明な作者

 青空文庫で公開されている作品の作者は、ペンネームの使い分けなどによる重複を含めて述べ695名。そのうち、生年月日未登録が186名、没年月日未登録が181名。Aozoramazonでは、その日に生まれた作者をトップページで表示しているが、生年月日不明な場合は表示対象とならない。日替わりトピックのネタとしては不完全なので、全ての作者を日替わりで満遍なく取り上げる良い術が無い物かと考えている。

2010年11月11日木曜日

青空文庫の第3第4水準漢字注記をUnicodeに置き換える

 最近はWindowsもMacもJIS X 0213の第3第4水準を表示できるようなので、Aozoramazonで表示する作者名や作品名に含まれている青空文庫の注記をUnicodeに置き換えるようにしました。

で、そのために作ったPythonの変換モジュールとテーブルを置いておきます。

 コメントに書いてありますが、テーブルの元データはProject X0213からダウンロードしました。

Pythonで漢数字をアラビア数字に変換

Aozoramazonの製作過程で漢数字をアラビア数字に変換する必要が生じて作りました。


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re

def kansuji2arabic(text):
"""漢数字からアラビア数字への変換"""

KANNUM_PATTERN = re.compile(u'(?P<kansuji>[壱一二弐三参四五六七八九十拾百千万萬億兆〇1234567890,,\d]+)')

index = 0
while index < len(text):
matched = KANNUM_PATTERN.search(text[index:])
if matched:
kansuji = matched.group('kansuji')
startindex = matched.start('kansuji') + index
endindex = matched.end('kansuji') + index
result = 0
digit = 1
numgroup = 1
kanindex = len(kansuji)
while kanindex > 0:
c = kansuji[(kanindex - 1):kanindex]
kanindex -= 1
if c == u'〇00':
digit *= 10
elif c in u'十拾':
digit = 10
elif c == u'百':
digit = 100
elif c == u'千':
digit = 1000
elif c in u'万萬':
numgroup = 10000
digit = 1
elif c in u'億':
numgroup = 10000*10000
digit = 1
elif c in u'兆':
numgroup = 10000*10000*10000
digit = 1
elif c in u',,':
pass
else:
if c in u'壱一11':
result += digit * numgroup
elif c in u'二弐22':
result += 2 * digit * numgroup
elif c in u'三参33':
result += 3 * digit * numgroup
elif c in u'四44':
result += 4 * digit * numgroup
elif c in u'五55':
result += 5 * digit * numgroup
elif c in u'六66':
result += 6 * digit * numgroup
elif c in u'七77':
result += 7 * digit * numgroup
elif c in u'八88':
result += 8 * digit * numgroup
elif c in u'九99':
result += 9 * digit * numgroup
digit *= 10
text = u'%s%d%s' % (text[:startindex], result, text[endindex:])
index = startindex + len('%d' % (result,))
else:
break
return text

 使用例


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import kan2arabic

def main():
examples = [u'平成22年度一般会計予算は92兆2292億円',
u'平成22年度一般会計予算は92兆2,292億円',
u'平成22年度一般会計予算は92兆2,292億円']
for text in examples:
print text, '->', kan2arabic.kansuji2arabic(text)

if __name__ == '__main__':
main()


出力

平成22年度一般会計予算は92兆2292億円 -> 平成22年度1般会計予算は92229200000000円
平成22年度一般会計予算は92兆2,292億円 -> 平成22年度1般会計予算は92229200000000円
平成22年度一般会計予算は92兆2,292億円 -> 平成22年度1般会計予算は92229200000000円

2010年11月9日火曜日

カーリルへのリンクを追加

 カーリルはリンクフリーと書いてあったので、AozoramazonのXSLTスタイルシートを修正して、ISBNのある本にカーリルへのリンクを追加した。青空文庫の作品を探しにきた人をカーリルへ、そしてその先の図書館へと誘導するのは、デジタルデータを求めにきたユーザーの志向を無視しているような矛盾は感じるが、まあいいじゃないか。カーリルのユルいロゴが気に入ってるんだもの。

50音別インデックスを追加

 Aozoramazonの作者別ページに、作品名の50音別インデックスを追加した。昨日の段階では実装途中だったので、選んだ文字で始まる作品が無い「外れ」ページがあるにもかかわらず、その存在が分かりにくかった。その後、作者毎に有効な作品名頭文字を予め調べておいて、有効な文字を強調表示するようにした。
 Aozoramazonの開発は、ここまでで一段落。作品長マップとか構想はあるけど、UIをどう用意するかまるで考えてないし、今すぐには取り掛かる気分じゃないのでまだやれない。

 本当は、「作品ページに表示されるQRコードを携帯で認識させるとiアプリをオンデマンドで生成してダウンロードできる仕組み」とかも作りたいけど(一度やってるのでたぶん作れるけど)、さらにハードルが高いので相当気合を入れないと無理。

2010年11月8日月曜日

作品一覧にキーワード検索を入れるか否か

 キーワード検索の結果にノイズが多いと思って、新着と作者別の作品一覧にキーワード検索を入れるのをやめてみた。結果、「龍馬の手紙」が完全に空振り。理由は、登録されている底本のタイトルが「龍馬の手紙、宮地佐一郎」で、著者名をタイトルの一部としてしまっているため。底本検索では、
  • Title
  • Author
  • Publisher
の全て、またはAuthorかPublishを省略した2つを検索パラメータに指定してProduct Advertising APIを読んでいる。Titleには青空文庫の作品名または底本タイトルを渡す。
「龍馬の手紙」は、Amazon上では
  • Author: 宮地佐一郎
  • Titel: 龍馬の手紙
であるため、いずれのパラメータの組み合わせでもヒットしない。ちなみに、ヒットしなかったパラメータの組み合わせは以下の通り。
{'Publisher': '講談社', 'Author': '坂本 竜馬', 'Title': '龍馬の手紙、宮地佐一郎'},
{'Publisher': '講談社', 'Title': '龍馬の手紙、宮地佐一郎'},
{'Publisher': '講談社', 'Author': '坂本 竜馬', 'Title': '手紙 109 慶応三年九月二十七日 本山只一郎あて'},
{'Publisher': '講談社', 'Title': '手紙 109 慶応三年九月二十七日 本山只一郎あて'},
{'Title': '龍馬の手紙、宮地佐一郎', 'Author': '坂本 竜馬'},
{'Title': '手紙 109 慶応三年九月二十七日 本山只一郎あて', 'Author': '坂本 竜馬'}
どの組み合わせを取ってもAmazonの商品情報と一致しない。

 根本的な問題は、青空文庫の底本情報が底本タイトル、出版社、発行日しか無いこと。この限られた項目の中に底本を特定するための情報を無理やり入れているため、出版社に「新潮文庫、新潮社」と出版社名以外の情報を入れたり、タイトルに編集者名を入れてしまう事になる。著者は作品の作者と同じだろうという前提で項目を省略しているのだと思うが、書籍となった場合は商品としての著者・編者は原作品の作者と異なってしまうこともある。
 出版社の方は比較的問題は簡単で、出版社名に句読点が入ることは常識的に無いので、「、」より前の部分を削除するとだいたい正確にマッチする。タイトルは自由度が高いので、そうはいかない。

 「龍馬の手紙」に限って言えば、「、」以降を削除することで検索が成功するだろう。しかし、このような一部の作品に特化した加工をすると、他の作品の検索でぼろが出る気がする。タイトルについてはこれ以上深入りはしない事にして、作品一覧上にキーワード検索を表示する動作に戻そうと思う。

MA6に応募

 Google App Engineを使っていればエントリーの要件を満たすことが分かったので、AozoramazonMashup Award 6に応募してみました。

Mashup的要素はGAEではなく、Product Advertising APIを使っている所なのですが、Amazon.co.jpは協賛企業ではないので、この点はエントリー要件に寄与しません。それどころか、協賛しているコンペティターにとっては不参加要件にしたいくらいでしょう。

2010年11月6日土曜日

AozoramazonをAjax化した

 レスポンスの遅さ問題を緩和するためのAjax化を完了。作品リストに遅れて検索結果が現れるようになった。

 サービスの名前は、当初は青空ナビにでもしようと思ったが、ナビと言えるほど案内機能が充実していないので、青空文庫とAmazonをつなげる意味で、単純に名前を連結してAozoramazonとした。

 誰も見てないと思うけど、ここ

やった事とやってない事

作ろうと思っていた機能のうち、以下の物はできた。

  • タブメニュー
  • 新着情報
  • レビューのポップアップ(jQueryのDialog)
  • 作品単位のページ。
やろうと思っていてやっていないのは、

  • 作品長、作品数で作家を分類してマッピングする仕組み。ジャンル関係なし、読むのに必要な時間をキーにしてエイヤッ!で選ぶような感じ。
  • 背景を実写青空写真に変更。
  • ロゴ画像
である。しかしその前に、AJAX化を優先させることにした。1ページを表示するまでにAmazonへ何度も検索をかけるため、どうにもレスポンスが悪すぎる。コレに比べたら、他の問題はたいしたこと無い。

2010年11月4日木曜日

Amazonへのリンクが一応できた

 青空文庫の作品に関連するAmazon商品情報をリストアップする仕組みが大体できた。作者毎の作品リストで、各作品の底本とキーワード検索結果の画像を列挙し、画像からAmazonへリンクされるようになっている。
 これから作らなければならないもの。
  • タブメニュー
  • 新着情報
  • レビューのポップアップ
  • 作品単位のページ。今は青空文庫の作品カードへジャンプするが、関連商品の検索結果を経由するようにしたい。
  • 作品長、作品数で作家を分類してマッピングする仕組み。ジャンル関係なし、読むのに必要な時間をキーにしてエイヤッ!で選ぶような感じ。
  • 背景を実写青空写真に変更。
  • ロゴ画像