唯物是真 @Scaled_Wurm

プログラミング(主にPython2.7)とか機械学習とか

小説家になろうの作品タイトルは長文化しているのか?

いささか旧聞ですが「ライトノベルのタイトルが長文化しているのではないか?」という話題がありました

Web小説投稿サイトである小説家になろうは、最近作品がいくつも書籍化されて話題になっています
Web小説もライトノベルからも大きな影響を受けていると考えられるので、同様にタイトルの文字数の増加が起きているのではないかと思って調べてみました

データの収集方法

小説家になろうでは以下のようなAPIが公開されていて、本文などを除いた一部の情報については簡単に取得することができます

例えば次のURLにアクセスするとその下のような結果が帰ってきます

--- - allcount: 194835 - title: 異世界迷宮で奴隷ハーレムを - title: '無職転生 - 異世界行ったら本気だす -' - title: 理想のヒモ生活 - title: こちら討伐クエスト斡旋窓口 - title: > この世界がゲームだと俺だけが知っている

これを利用して各年ごとの作品のデータを取得したいのですが、一つ大きな問題があります。
このAPIでは検索条件に最初の投稿日時を含めることができません
日時関係で指定できるのは最終更新日時だけです

しょうがないので近似的にデータを集めました。
具体的には各年ごとに、最終更新日時がその年の範囲に含まれる作品の中から人気順に取り出してきたもの最大2000件をAPIでとってきて全体のデータセットとしました

データセットの詳細

APIを使って収集した結果、17557件のデータが得られました
それぞれの作品についてタイトルと最初の投稿日時を取得しました。
最初の投稿年ごとのデータ数は以下のようになっています
このグラフを見ると、200420052013年が相対的に少ないデータしか集められていないことがわかります。
なので、これらの年についての分析結果は少し信頼性が低いかもしれません
f:id:sucrose:20130725165326p:plain

タイトルの文字数の分析

例として2012年のタイトルの長さの分布を示します
タイトルの文字数についてはサブタイトルやカッコの有無などは考慮せず、その全体の文字を数えました
10文字弱あたりにピークがある、右に裾の長い分布であることがわかります
f:id:sucrose:20130725175652p:plain

それぞれの年の分布を比較するために、文字数を1-5, 6-10, 11-15, 16-20, 21- の5区分に分けて、年ごとにその割合を調べてみました
以下のグラフを見ると、1-5文字のタイトルが減って、16文字以上のタイトルが増え続けているのがよくわかると思います
f:id:sucrose:20130725165800p:plain

またそれぞれの年のタイトルの文字数の平均値は次のように年々増え続けています
f:id:sucrose:20130725172127p:plain

以上の結果を見ると、小説家になろうの作品タイトルも長文化傾向にあるといえるでしょう
このままのペースで増え続けたらすごいことになりますね

ソースコードと収集したデータ

作品名と初投稿日時のデータを収集するスクリプトです

import urllib2
import gzip
import json
import StringIO
import urllib

class NarouAPIWrapper(object):
    def __init__(self, URL='http://api.syosetu.com/novelapi/api/', **default_parameter):
        self.URL = URL
        self.default_parameter = default_parameter.copy()
        
    def isGzipEnabled(self, parameter):
        return 'gzip' in parameter and 1 <= parameter['gzip'] <= 5
    
    def decompressGzipString(self, string):
        strIO = StringIO.StringIO(string)
        return gzip.GzipFile(fileobj=strIO).read()
    
    def encodeQuery(self, parameter):
        return urllib.urlencode({k: v.encode('utf-8') if isinstance(v, unicode) else v for k, v in parameter.iteritems()})
    
    def requestAPI(self, urlencoded_query):
        res = urllib2.urlopen(self.URL + '?' + urlencoded_query)
        return res.read()
    
    def get(self, **parameter):
        temp_parameter = self.default_parameter.copy()
        temp_parameter.update(parameter)
        
        query = self.encodeQuery(temp_parameter)
        result = self.requestAPI(query)
        
        if self.isGzipEnabled(temp_parameter):
            result = self.decompressGzipString(result)
        
        return result

if __name__ == "__main__":
    import datetime
    import time
    import collections
    import numpy
    
    date = collections.Counter()
    length = collections.defaultdict(list)
    
    t = NarouAPIWrapper(gzip=5, out='json', lim=200, order='favnovelcnt', of='t-gf')

    titles = []
    for year in xrange(2004, 2014):
        for i in xrange(10):
            time.sleep(1)
            start = int(time.mktime(datetime.datetime(year, 1, 1, 0, 0, 0).timetuple()))
            end = int(time.mktime(datetime.datetime(year, 12, 31, 23, 59, 59).timetuple()))
            js = json.loads(t.get(st=1 + i * 200, lastup='{}-{}'.format(start, end)))
            for item in js:
                if u'title' in item:
                    print item['title'].encode('utf-8', 'ignore'), item['general_firstup'].encode('utf-8', 'ignore')