[JavaScript] 2014アニメのデータベースを作りたい その1 | Express編

2014/12/8

こんにちは。きんくまです。

swiftでSQLiteのことをやっているのですが、やっているうちにSQLiteで生のSQLの練習をしたいと思いました。
で、サンプルのデータベースを作って、それを使ってやろうと思いました。サンプルのデータは、ダミーの会社情報とか、商品情報とかでもいいのかと思いましたが、結果が返ってきても自分的にイマイチ盛り上がりません。なのでアニメのDB作ったら楽しいんじゃないかと思いました。

実装の進め方

いろいろと考えた結果、こんな感じに進めようと思いました。

1. Wikiのデータを決められた書式のjsonフォーマットに落とす
2. jsonフォーマットのデータを展開して、SQLiteのファイルを作成する。

ざっくりいって2段構えです。今回は1までです。

Wikiのデータを決められた書式のjsonフォーマットに落とす

アニメの情報をもとのデータをどこかで調べてくる必要があります。
Wikiでこのページが見つかりまして、このデータを集めようと思いました。

>> Category:2014年のテレビアニメ

はい。ずいぶん多いですね。見たことあるやつも、そうでないやつもたくさんございます。

さて、どこまでデータを入れるかなんですが、最終的に

作品名やスタッフ名で検索ができる

ということを目的に集めることにしました。キャラ名やあらすじなんかもあるんですが、そこまでやると学習目的にしては大掛かりすぎて大変かなと。

下の画像の赤枠で囲った部分が今回集めるデータになります。

wiki_anime_db

実際に集める

ここで、サーバー関連のすごい人たちなら、ページをまるごと拾ってきてパースしてということをやると思うのですが、自分には無理です。なので、自動+手動のハイブリッド方式をとることにしました。
必要なデータを編集用のWebページにコピペしてJSでパース、結果をNode.jsを使ってjsonファイルに書き出すという感じです。

Nodeはちゃんとやったことがないので、参考書をたよりにすすめました。
(買ったものの積ん読状態だったこの本です)

で、最終的にできたページがこちらです。

input_data_anime

左側のテキストエリアなどに文字を入力していって、左下の変換ボタンを押すと、右側にjson形式で吐き出されます。(プレビュー)
間違いがないことを確認して、書き出しボタンを押すとクライアントからajaxでnodeのスクリプトにデータが渡されてjsonファイルが保存されるという仕組みです。
一部スタッフ名のところは手動で改行や区切り文字を入れる必要があるのですが、そこは運用の範囲ということで今回はそこは手を抜きました。

参考書にはNodeのサーバーはExpressっていうのがよく使われてるって書いてあったのでそれを使いました。

>> Express

Expressではテンプレートエンジンでhtmlをレンダリングするみたいです。
EJSJadeの2種類がメインのテンプレートで、今回はJadeにしました。

input_data.jade

html
    head
        meta(charset='utf-8')
        title データ入力画面
        link(rel='stylesheet', href='/css/style.css')
        script(src='js/jquery-1.11.0.min.js', type='text/javascript')
        script(src='js/underscore.1.7.0.js', type='text/javascript')
        script(src='js/input_main.js', type='text/javascript')
    body
        div(id='wrapper')
            div(id='input_area')
                h2 データ入力
                section
                    label(for='title') 作品名
                    input(type='text', id='title')

                section
                    label(for='summary') 概要
                    textarea(id='summary')

                section
                    label(for='genre') ジャンル
                    input(type='text', id='genre')

                section
                    label(for='staff_credit') スタッフクレジット
                    textarea(id='staff_credit')


                section
                    div(id="convert_anime_button") 変換

                section
                    textarea(id='stock')

            div(id='output_area')
                    h2 データ出力
                    section
                        textarea(id='output')

                    section
                        div(id="export_anime_button") 書き出し

                    section
                        div(id="message_area")  

                    section
                        div(id="clear_button") クリアー

と書くとこんな感じにhtmlが吐き出されました。1行で出てきちゃってるけどたぶん複数行で出す方法があるかもしれない。

<html><head><meta charset="utf-8"/><title>データ入力画面</title><link rel="stylesheet" href="/css/style.css"/><script src="js/jquery-1.11.0.min.js" type="text/javascript"></script><script src="js/underscore.1.7.0.js" type="text/javascript"></script><script src="js/input_main.js" type="text/javascript"></script></head><body><div id="wrapper"><div id="input_area"><h2>データ入力</h2><section><label for="title">作品名</label><input type="text" id="title"/></section><section><label for="summary">概要</label><textarea id="summary"></textarea></section><section><label for="genre">ジャンル</label><input type="text" id="genre"/></section><section><label for="staff_credit">スタッフクレジット</label><textarea id="staff_credit"></textarea></section><section><div id="convert_anime_button">変換</div></section><section><textarea id="stock"></textarea></section></div><div id="output_area"><h2>データ出力</h2><section><textarea id="output"></textarea></section><section><div id="export_anime_button">書き出し</div></section><section><div id="message_area"> </div></section><section><div id="clear_button">クリアー</div></section></div></div></body></html>

ブラウザ側のjs

input_main.js

$(function(){
    var staffProps = {
        original_by:"原作"
        ,genre:"ジャンル"
        ,directed_by:"監督"
        ,series_composition:"シリーズ構成"
        ,written_by:"脚本"
        ,character_design:"キャラクターデザイン"
        ,mechanic_design:"メカニックデザイン"
        ,music_by:"音楽"
        ,studio:"アニメーション制作"
        ,original_run:"放送期間"
        ,episodes:"話数"
        ,production:"製作"
    };

    function paddingZero(num){
        if(num.length == 0){
            num = "00";
        }else if(num.length == 1){
            num = "0" + num;
        }
        return num;
    }

    function parseRunDate(dateStr){
        dateStr = dateStr.replace(/\s+/g, '');
        var dateArr = dateStr.split('-');
        var fromMonth = dateArr[0];
        fromMonth = fromMonth.match(/([0-9]+)年([0-9]+)月/);
        fromMonth = paddingZero(fromMonth[1]) + '-' + paddingZero(fromMonth[2]);
        var toMonth = null;
        if(dateArr.length > 1 && dateArr[1].length >= 1){
            toMonth = dateArr[1];
            toMonth = toMonth.match(/([0-9]+)月/);
            toMonth = '2014-' + paddingZero(toMonth[1]);
        }
        return {from:fromMonth, to:toMonth};
    }

    function parseSummary(summaryText){
        summaryText = summaryText.replace(/\[[0-9]+\]/, '');
        return summaryText;
    }

    function parseCompanyStaffNamesArr(namesArr){
        var name;
        for(var i = 0, len = namesArr.length; i < len; i++){
            name = namesArr[i];
            name = name.replace(/\s+/g, '');
            name = name.replace(/(.+)/g, '');
            name = name.replace(/\(.+\)/g, '');
            if(name.length > 0){
                namesArr[i] = name;
            }
        }
        return namesArr;
    }

    function addCreditData(resultObj){
        var staffText = $('#staff_credit').val();
        var staffArr = staffText.split('\n');
        var genreStr = $('#genre').val();
        if(genreStr.length == 0){
            //throw new Error('ジャンルが空です');
        }else{
            genreStr = "ジャンル\t" + genreStr;
            staffArr.push(genreStr);
        }
        var staffRow;
        var key, value, valueArr;
        for(var i = 0, len = staffArr.length; i < len; i++){
            if(staffArr[i].length == 0){
                continue;
            }
            staffRow = staffArr[i].split('\t');
            key = staffRow[0];
            value = staffRow[1];
            for(var staffPropKey in staffProps){
                if(staffProps[staffPropKey].indexOf(key) >= 0){
                    switch(staffPropKey){
                        case "episodes":{
                            value = value.match(/([0-9]+)話/);
                            resultObj[staffPropKey] = parseInt(value[1]);
                            break;
                        }
                        case "original_run":{
                            var parsedMonth = parseRunDate(value);
                            resultObj["run_month_from"] = parsedMonth.from;
                            resultObj["run_month_to"] = parsedMonth.to;
                            break;
                        }
                        default:{
                            value = value.replace(/`/g, '、');
                            valueArr = value.split('、');
                            resultObj[staffPropKey] = parseCompanyStaffNamesArr(valueArr);
                            break;
                        }
                    }
                    break;
                }
            }
        }
    }

    function convertData(){
        var title = $('#title').val();
        if(title.length == 0){
            throw new Error('タイトルが空です');
        }
        var summary = $('#summary').val();
        if(summary.length == 0){
            throw new Error('サマリーが空です');
        }
        summary = parseSummary(summary);
        var resultObj = {
            "title":title
            ,"summary":summary
        };
        addCreditData(resultObj);
        var outputStr = JSON.stringify(resultObj, null, 4);
        $('#output').val(outputStr);
        return resultObj;
    }

    var convertButton = $('#convert_anime_button');
    var exportButton = $('#export_anime_button');

    var exportJSONData = null;

    convertButton.click(function(){
        $('#message_area').html(' ');
        exportJSONData = convertData();
    });

    exportButton.click(function(){
        $.post('/export_json', exportJSONData, function(resultData){
            $('#message_area').text(resultData.result);
        });
    });

    $('#clear_button').click(function(){
        $('#title').val('');
        $('#summary').val('');
        $('#genre').val('');
        $('#staff_credit').val('');
        $('#stock').val('');
        $('#message_area').html(' ');
        $('#output').val('');
    });
});

これに反応するnodeというかサーバー側のjs

index.js

var express = require('express');
var router = express.Router();
var fs = require('fs');

router.get('/input_data', function(req, res) {
  res.render('input_data', {

  });
});

router.post('/export_json', function(req, res){
  var outputFilePath = './anime_data_jsons/' + req.body.title + '.json';
  fs.writeFileSync(outputFilePath, JSON.stringify(req.body, null, 2));
  res.send({result:'成功しました'});
});

module.exports = router;

そして書き出されたjsonの例

{
  "title": "結城友奈は勇者である",
  "summary": "『結城友奈は勇者である』(ゆうきゆうなはゆうしゃである)は、Studio五組制作によるテレビアニメ。2014年10月よりMBSほか『アニメイズム』B2にて放送中。",
  "original_by[]": "Project2H",
  "directed_by[]": "岸誠二",
  "series_composition[]": "上江洲誠",
  "written_by[]": [
    "上江洲誠",
    "タカヒロ",
    "村田治"
  ],
  "character_design[]": [
    "BUNBUN",
    "酒井孝裕"
  ],
  "music_by[]": [
    "岡部啓一",
    "MONACA"
  ],
  "studio[]": "Studio五組",
  "production[]": [
    "結城友奈は勇者である製作委員会",
    "MBS"
  ],
  "run_month_from": "2014-10",
  "run_month_to": "",
  "genre[]": [
    "変身ヒロイン",
    "戦闘美少女"
  ]
}

一部、想定したフォーマットではない作品がありました。それについては今回は面倒だった学習目的ということで時間をかけない方針なので見送ることにしました。(仕事だったらやるけど、、)

で、これを使ってチクチクとコピペでデータを収集して、最終的には180枚のjsonファイルが集まりました。
次回は集めたファイルをパースして、Nodeでsqliteにデータを入れて、2014アニメデータの完成まで持っていきたいです。

今回はNodeといっても実際に使わなきゃいけなかったのは、ファイルの書き出し部分のみでした。だけど、jsでお手軽にそれができるっていうのは、ツール作りの選択肢が広がって良いなと思いました。

できたjson一式はこちらです。

権利関係はわからないので、商業利用はやめた方がよさそうだと思いますが、個人で楽しむ分には大丈夫じゃないかと。

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る