Tumblrランキングサイトの作り方

# Reblogen ランキング
Tumblrの直近24時間のうちで、リブログ、ライクの多かったポストをランキング形式で表示する「Reblogen ランキング」というサイトを作りました。同系統のコンセプトを持っている先行サイトとして「たんぶらうざ」「Tumblr PORT」といったサイトがありました、しかし現在では、それぞれ更新停止、サイト閉鎖と全滅状態となってしまっていたため、今回代替サイトを立ち上げました。Tumblr沼に引き込むための入り口となるサイトです。

[Reblogen ランキング](https://reblogen.com/ranking/)
<img src="https://qiita-image-store.s3.amazonaws.com/0/57879/d36e2477-0b99-6bda-4e20-3b14b05d97ff.png" width=50%>

本記事では、どうやってランキングを生成しているかのロジック、及び手間がかかった事などをまとめます。もし同系統のサイトを立ち上げようとしている方がいらっしゃったら、参考にして頂けると幸いです。一緒にTumblrを盛り上げて行きましょう！

# ランキングの生成ロジック
かなりシンプルな方法でランキングを生成しています。ざっくり言うと適当にアルファリブロガーのアカウントをリストアップし、その人達の直近のポストを走査する事でランキングを生成します。

実際のフロー
1. アルファリブロガーと思われる人を、数十人リストアップする
2. リストアップしたアカウントから順番に走査
3. アカウント毎に、Tumblr API をつかって直近100ポストを取得
4. 重複を防ぐために 投稿元のポストID（reblogged_root_id）でユニーク化 
5. 24時間以内のポストからライク・リブログ数でランキングを生成

特記した方が良い工程の詳細は以下です。

## アルファリブロガーと思われる人を、数十人リストアップする
これが地味に一番難しかった。色々試したのだけれども、結局良い方法が思いつきませんでした。ランキングのベースとするためなので、自分から投稿をするタイプの人ではなく、ひたすらリブログしまくる系のアルファリブロガーの人が適任。よいランキングを作るために、アンテナの高い人がいい。ランキングの偏りを無くすために、さまざまなジャンルの人を集める必要がある。サイトのターゲットは、日本人が対象なので日本人の人がいいかな。そんなオーダーなのですが、そんな要望を満たすリストは、この世界のどこにもなかった！ 

とりあえず、死ぬほど有名な otsuneさん、ak47さん、katoyuuさんなんかを追加。「Tumblr 有名 アカウント」でGoogle検索して出てきた、[日本で有名なtumblrアカウント](http://cineraria.tumblr.com/post/24323780607/%E6%97%A5%E6%9C%AC%E3%81%A7%E6%9C%89%E5%90%8D%E3%81%AAtumblr%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88) を参考に追加。[たんぶらうざ](http://mao.s151.xrea.com/tumbrowser/)に登場したアカウントを追加。自分がフォローしている人からちょいちょい追加、などなどを行って、なんとかかんとかリストを作成しました。うーん、無理矢理感がすごいので、もっといい方法があるといいのですが。この点だけ模索中。

<table>
<tr>
<td><a href="http://8823dsn.tumblr.com/" target="_blank">8823dsn</a></td>
<td><a href="http://ak47.tumblr.com/" target="_blank">ak47</a></td>
<td><a href="http://appbank.tumblr.com/" target="_blank">appbank</a></td>
<td><a href="http://at1980independent.tumblr.com/" target="_blank">at1980independent</a></td>
<td><a href="http://atm09td.tumblr.com/" target="_blank">atm09td</a></td>
<td><a href="http://dannnao.tumblr.com/" target="_blank">dannnao</a></td>
</tr>
<tr>
<td><a href="http://deepspeed.tumblr.com/" target="_blank">deepspeed</a></td>
<td><a href="http://do-nothing.tumblr.com/" target="_blank">do-nothing</a></td>
<td><a href="http://dotnuke.tumblr.com/" target="_blank">dotnuke</a></td>
<td><a href="http://exposition.tumblr.com/" target="_blank">exposition</a></td>
<td><a href="http://ginzuna.tumblr.com/" target="_blank">ginzuna</a></td>
<td><a href="http://handa.tumblr.com/" target="_blank">handa</a></td>
</tr>
<tr>
<td><a href="http://haru3uvvv.tumblr.com/" target="_blank">haru3uvvv</a></td>
<td><a href="http://highlandvalley.tumblr.com/" target="_blank">highlandvalley</a></td>
<td><a href="http://hkdmz.tumblr.com/" target="_blank">hkdmz</a></td>
<td><a href="http://hkj.tumblr.com/" target="_blank">hkj</a></td>
<td><a href="http://hzmnryk.tumblr.com/" target="_blank">hzmnryk</a></td>
<td><a href="http://inumoto.tumblr.com/" target="_blank">inumoto</a></td>
</tr>
<tr>
<td><a href="http://itokonnyaku.tumblr.com/" target="_blank">itokonnyaku</a></td>
<td><a href="http://jacony.tumblr.com/" target="_blank">jacony</a></td>
<td><a href="http://jessy-019181340likes.tumblr.com/" target="_blank">jessy-019181340likes</a></td>
<td><a href="http://johnnychallenge.tumblr.com/" target="_blank">johnnychallenge</a></td>
<td><a href="http://kamatama-udon.tumblr.com/" target="_blank">kamatama-udon</a></td>
<td><a href="http://katoyuu.tumblr.com/" target="_blank">katoyuu</a></td>
</tr>
<tr>
<td><a href="http://katsuwojp.tumblr.com/" target="_blank">katsuwojp</a></td>
<td><a href="http://kazukij.tumblr.com/" target="_blank">kazukij</a></td>
<td><a href="http://kikuzu.tumblr.com/" target="_blank">kikuzu</a></td>
<td><a href="http://konishiroku.tumblr.com/" target="_blank">konishiroku</a></td>
<td><a href="http://kotoripiyopiyo.tumblr.com/" target="_blank">kotoripiyopiyo</a></td>
<td><a href="http://mcsgsym.tumblr.com/" target="_blank">mcsgsym</a></td>
</tr>
<tr>
<td><a href="http://megane4141.tumblr.com/" target="_blank">megane4141</a></td>
<td><a href="http://nekoshige.tumblr.com/" target="_blank">nekoshige</a></td>
<td><a href="http://nemoi.tumblr.com/" target="_blank">nemoi</a></td>
<td><a href="http://non117.tumblr.com/" target="_blank">non117</a></td>
<td><a href="http://ohachi3.tumblr.com/" target="_blank">ohachi3</a></td>
<td><a href="http://otsune.tumblr.com/" target="_blank">otsune</a></td>
</tr>
<tr>
<td><a href="http://overhilowsee.tumblr.com/" target="_blank">overhilowsee</a></td>
<td><a href="http://pema001.tumblr.com/" target="_blank">pema001</a></td>
<td><a href="http://petapeta.tumblr.com/" target="_blank">petapeta</a></td>
<td><a href="http://pipco.tumblr.com/" target="_blank">pipco</a></td>
<td><a href="http://reonald.tumblr.com/" target="_blank">reonald</a></td>
<td><a href="http://reretlet.tumblr.com/" target="_blank">reretlet</a></td>
</tr>
<tr>
<td><a href="http://sak.tumblr.com/" target="_blank">sak</a></td>
<td><a href="http://shayol.tumblr.com/" target="_blank">shayol</a></td>
<td><a href="http://shinoddddd.tumblr.com/" target="_blank">shinoddddd</a></td>
<td><a href="http://skaholic.tumblr.com/" target="_blank">skaholic</a></td>
<td><a href="http://starlightride.tumblr.com/" target="_blank">starlightride</a></td>
<td><a href="http://suyhnc.tumblr.com/" target="_blank">suyhnc</a></td>
</tr>
<tr>
<td><a href="http://suzukichiyo.tumblr.com/" target="_blank">suzukichiyo</a></td>
<td><a href="http://sytoh.tumblr.com/" target="_blank">sytoh</a></td>
<td><a href="http://taisukeorg.tumblr.com/" target="_blank">taisukeorg</a></td>
<td><a href="http://takaakik.tumblr.com/" target="_blank">takaakik</a></td>
<td><a href="http://taku007.tumblr.com/" target="_blank">taku007</a></td>
<td><a href="http://toyolina.tumblr.com/" target="_blank">toyolina</a></td>
</tr>
<tr>
<td><a href="http://uinyan.tumblr.com/" target="_blank">uinyan</a></td>
<td><a href="http://usaginobike.tumblr.com/" target="_blank">usaginobike</a></td>
<td><a href="http://wyohkan.tumblr.com/" target="_blank">wyohkan</a></td>
<td><a href="http://yaruo.tumblr.com/" target="_blank">yaruo</a></td>
<td><a href="http://yellowblog.tumblr.com/" target="_blank">yellowblog</a></td>
<td><a href="http://yorozuna.tumblr.com/" target="_blank">yorozuna</a></td>
</tr>
<tr>
<td><a href="http://yozoran.tumblr.com/" target="_blank">yozoran</a></td>
<td><a href="http://zenigata.tumblr.com/" target="_blank">zenigata</a></td>
</tr>
</table>

## アカウント毎に、Tumblr API をつかって直近100ポストを取得
OAuthしてのTumblr APIコールの仕方は私が以前の記事にまとめたのがありますので良かったらそっちを参照してください。
[Tumblr API OAuth認証の方法（PHPサンプル付き）](https://qiita.com/newton/items/1114e443a90e204746ad)

アカウント毎に100ポストとしているのは、ランキングが特定のアカウントに偏るのが嫌だったのでこのようにしました。色々なアカウントからポストを収集した方がいいかなと。

## 24時間以内のポストからライク・リブログ数でランキングを生成
24時間縛りもちょっとだけ面倒でした。ランキングの鮮度を保つためにも、ぜひ時間の縛りを入れたかった。なのにAPIから取得出来るのはそのポストが投稿された日付だけです。そのためリブログの場合は、元のポストが投稿された日時ではなく、リブログがされた日時になってしまいます。単純に投稿日時で絞ってしまうと、何年も前のポストがたまたま24時間以内にリブログされてしまうとランキングに登場してしまうのです。それはなんか違うかなと思いまして、あくまで元のポストが投稿された日時で絞りたかった。

でもAPIからは、投稿元のポストID（reblogged_root_id）は取得出来るのですが、その投稿元のポストが投稿された日時は取得出来ません。そこで、投稿元のポストID（reblogged_root_id）を使って元のポストが何時頃に投稿されたかを類推する方法を使いました。下記の記事で作成した日時とポストID対応テーブルから投稿日時を逆算しています。

パワー系Endless Summer。TumblrでランダムなポストIDを取得
https://qiita.com/newton/items/1114e443a90e204746ad

# エロ対策
エロ対策は、今回のランキングサイト作成にあたって最重要事項と言ってよいでしょう。Tumblrは文化なのか知りませんが、エロ投稿は息をするように行われます。それも「さっきまでずっと猫画像ばっかり投稿していた人が突然エロ画像をリブログしてきた」なんて事も普通に発生します。しかも、無修正的なアレだったりする事も多く、我が国の法律に照らし合わせて色々まずいんじゃないかという事もしばしば。ランキングサイトは、不特定多数の公衆に向けて公開するサイトです。ですからエロ対策は絶対に必要です。

## アダルト可のサーバーに移転する
エロ対策ステップ1は、サーバーの移転です。とりあえずサーバーはアダルト可のサーバーに移転しておいた方がいいかなと思いまして。色々調べた結果、今回はGMOクラウドのVPSを利用させて頂きました。GMOさん、エロ可サーバーのご運営ありがとうございます。大変助かります。

[GMOクラウド FAQ](https://help.gmocloud.com/app/answers/detail/a_id/48/~/アダルトサイトの運営は可能ですか？)より引用
> Q. アダルトサイトの運営は可能ですか？
> A. 可能です。
> ただし、アダルトサイトにかかわらず、法令または公序良俗に反する内容は禁止とさせていただいております。
> 禁止行為の詳細はご契約プランの利用約款をご参照ください。

## 画像解析APIを用いたエロ画像フィルタリング
アダルト可といっても、上記の通り法令に違反するような行為はご法度となります。そのため結局エロ画像のフィルタリングは必要になってきます。今回は、画像解析APIを利用してエロ画像のフィルタリングを行いました。画像解析APIで有名な「[Google Cloud Vision API](https://cloud.google.com/vision/?hl=ja)」と「[Microsoft Azure Computer Vision API](https://azure.microsoft.com/ja-jp/services/cognitive-services/computer-vision/)」の2つで性能評価を行った上でどちらを使うかを選定しました。

### 性能検証の対象画像
今回性能検証を行う上で、ほとんど裸の2人が汗だくでくんずほぐれつしている画像を用意しました。

![84138bc1865b2959f740fb309317031a.jpg](https://qiita-image-store.s3.amazonaws.com/0/57879/d6142638-9d53-8253-3475-704982ec9374.jpeg)

お相撲さんの判定はかなり難しいはずなのです。「クロールしてエロ画像を自動収集するBOTを作ったらお相撲さんだらけになった」なんて笑い話があるぐらいです。画像判定にとって、お相撲さんのエロ判定は永遠のテーマ。性能検証にうってつけといってよいでしょう。この画像をそれぞれのAPIで検証した結果が以下となります。

#### Google Cloud Vision API
```json
   "safeSearchAnnotation": {
       "adult": "POSSIBLE",
       "spoof": "VERY_UNLIKELY",
       "medical": "VERY_UNLIKELY",
       "violence": "VERY_UNLIKELY"
   }
```
※ 左に行くほど可能性が高い。VERY_LIKELY > LIKELY >  POSSIBLE > UNLIKELY > VERY_UNLIKELY

#### Microsoft Azure Computer Vision API
```json
 "adult": {
        "isAdultContent": false,
        "isRacyContent": true,
        "adultScore": 0.28257384896278,
        "racyScore": 0.93793398141861
   }
```

#### 性能検証結果
上記のような結果となりました。Googleの方は、アダルト判定が「POSSIBLE」6段階の真ん中ですので、エロいのかどうなのか判定出来ず。迷いが感じられます。一方で、Microsoft Azureは、「isAdultContent」が「false」できっぱりとアダルト画像であることを否定しています。素晴らしい！ ただ「isRacyContent」は「true」なので際どい？ いやらしい？ とは判定しているようです。まあ際どいと言えば際どいと思うので気にしない事にします。

というわけで、今回はよりアダルト判定の性能が高かった Microsoft Azure Computer Vision API を選定いたしました。

### 節約の必要性
Computer Vision API を組み込んだ仕組みを構築したのですが、一点だけ問題がありました。Computer Vision APIの無料枠は月に5000コールまでです。今回の施策での想定として、1日のコール数は最大200程度。毎日ランキングを更新したとすると 200 * 30 = 6000コールなので無料枠を超えてしまう恐れがありました。

まあ、お金払えよって話なんですが、もうちょっと節約したら無料枠に収まりそうだったのでなんとかしたかった。なんかないかなーと考えたのですが、私重要な事に気づいてしまいました。Computer Vision API よりもっともっと高性能なエロ画像判定が出来る奴ありましたわ。私自身のエロ判定の方が信頼出来る。APIなんぞに負けはしない。そんなわけでバッチと私の共同作業、マーメイドスタイルで行く事にしました。

#### エロ判定マーメイドスタイル詳細
マーメイドスタイルの概要はこうです。ランキング生成のバッチ、自動でのエロ判定、手動でのエロ判定、それぞれのタイミングをずらすことでバッチと人間の共同作業を可能にしました。以下に実行タイミングを図にまとめました。


![ranking3.png](https://qiita-image-store.s3.amazonaws.com/0/57879/27eb0b21-b98d-878f-d1d7-08c7e8ac18c5.png)


このように、19時に非公開状態でランキングをデータベース上に生成、そして何もしないと0時にバッチが動いてアダルト判定と公開処理を行います。もしくは、19時～0時の間に手動アダルト判定用の管理画面に私がアクセスしてアダルトチェックした場合は、その時点で公開され0時のバッチ実行がスキップされます。バッチと人間がお互い支え合う、なんと美しい。