欢迎光临散文网 会员登陆 & 注册

【统计向】2008年至今Pixiv上关于初音未来或VOCALOID的插画的投稿量逐月变化趋势

2022-08-07 17:55 作者:涼风_青叶  | 我要投稿

一、摘要

        本篇专栏通过Python编写的爬虫自动完成了2008年1月1日至目前(2022年7月16日)初音未来、VOCALOID两个Tag的逐月(30天)的Pixiv插画投稿量统计。统计结果体现了十数年来VOCALOID文化经过的“扩大——兴盛——衰退——复兴”四阶段变化。统计代码可供将来其它人物 / 企划的插画 / 小说统计使用。

二、统计方法

        统计通过Python代码进行,代码如下:

# coding:utf-8
import requests
import urllib.parse
import urllib3
import openpyxl
import time
import html


class Date:
    # 建立一个日期类,以供后续代码使用
    __dayPerMonth = ("Foo", 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

    def __init__(self, y: int, m: int, d: int):
        # y, m, d为年、月、日
        self.y = y
        self.m = m
        self.d = d

    def isInLeap(self):
        # 该日期处于闰年中则返回True,否则返回False
        if self.y % 400:
            return not self.y % 4 and self.y % 100
        else:
            return True

    def printSelf(self):
        # 打印自身
        return "{}-{:0>2d}-{:0>2d}".format(self.y, self.m, self.d)

    def __add__(self, other: int):
        # 返回自身过other天后的日期
        newDate = Date(self.y, self.m, self.d)
        newDate.d += other
        while newDate.d > self.__dayPerMonth[newDate.m] + (1 if newDate.isInLeap() and newDate.m == 2 else 0):
            newDate.d -= self.__dayPerMonth[newDate.m] + (1 if newDate.isInLeap() and newDate.m == 2 else 0)
            newDate.m += 1
            if newDate.m == 13:
                newDate.m -= 12
                newDate.y += 1
        return newDate

    def __sub__(self, other):
        # 返回自身的other天前的日期
        newDate = Date(self.y, self.m, self.d)
        newDate.d -= other
        while newDate.d <= 0:
            if newDate.m == 1:
                newDate.m = 13
                newDate.y -= 1
            newDate.d += self.__dayPerMonth[newDate.m - 1] + (1 if newDate.isInLeap() and newDate.m - 1 == 2 else 0)
            newDate.m -= 1
        return newDate


def loadFile(filename: str, asStr: bool):
    # 返回同目录下一文件内的内容,asStr为True时以字符串的形式返回,否则返回eval(自身)
    infp = open(filename, "r", encoding="UTF-8")
    cont = infp.read()
    infp.close()
    if not asStr:
        cont = eval(cont)
    return cont


def tagInfoGet(form: str, Tag: str, scd="", ecd="", mode="all") -> dict:
    # 通过requests访问Pixiv,返回Tag自scd日期开始到ecd日期为止form形式的创作的统计信息,mode可以指定
    # form can be: 'novels', 'illustrations'
    # mode can be: 'all', 'safe', 'r18'
    # scd and ecd goes like: 2022-01-27
    maxTryTimes = 5
    if form not in ("novels", "illustrations"):
        raise Exception('parameter "form" cannot be "{}"'.format(form))
    if mode not in ("all", 'safe', 'r18'):
        raise Exception('parameter "mode" cannot be "{}"'.format(mode))
    originalTag = Tag
    Tag = urllib.parse.quote(Tag, 'utf-8')
    false, true, null = False, True, None
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/41.0.2272.118 Safari/537.36',
        'Referer': "https://www.pixiv.net/",
        # 如果在headers中不提供Cookie,得到的数据将不准确。
        'Cookie': loadFile("cookie.txt", True)
    }
    if scd and ecd:
        url = "https://www.pixiv.net/ajax/search/{}/{}?word={}&order=date_d&mode={}&scd={}&ecd={}" \
              "&p=1&s_mode=s_tag&gs=0".format(form, Tag, Tag, mode, scd, ecd)
    else:
        url = "https://www.pixiv.net/ajax/search/{}/{}?word={}&order=date_d&mode={}" \
              "&p=1&s_mode=s_tag&gs=0".format(form, Tag, Tag, mode)
    print(url)
    for i in range(maxTryTimes):
        try:
            h = requests.get(url, headers=headers, verify=False)
            break
        except:
            print("Get {} failed! This is the try-{}".format(originalTag,i+1))
    h.encoding = "UTF-8"
    s = h.text
    s = html.unescape(s)
    print("Get", originalTag, "successfully!")
    return eval(s)


def tagTotalGet(tagInfo: dict, form: str):
    # 在从Pixiv获取统计信息后,将作品总数数据从中提取出来
    # form can be: 'novel', 'illust'
    return tagInfo["body"][form]["total"]


def PixivTime(theme: str, startDate: Date, dayInterval: int, samplePointN: int):
    # theme:决定输出.xlsx文件的标题。startDate:统计开始时间,为之前建立的Date类格式。
    # dayInterval:统计粒度,即每个数据的时间间隔,单位为天。samplePointN:每个Tag的统计数据数量。
    # dayInterval 必须 < 365,因为Pixiv的按时间查找作品功能支持的时长为一年之内。
    ZhNames = loadFile("ZhNames.txt", False)
    JpNames = loadFile("JpNames.txt", False)
    days = [startDate + dayInterval * i for i in range(samplePointN+1)]
    if len(ZhNames) != len(JpNames):
        raise Exception("len(JpNames) is {} while len(ZhNames) is {}!".format(len(JpNames), len(ZhNames)))
    f = openpyxl.Workbook()
    dws = f.active
    illuSheet = f.create_sheet("插图统计")
    novelSheet = f.create_sheet("小说统计")
    f.remove_sheet(dws)
    illuSheet.cell(1, 1).value = "姓名\\日期"
    novelSheet.cell(1, 1).value = "姓名\\日期"
    for i in range(len(days)):
        illuSheet.cell(1, i + 2).value = days[i].printSelf()
        novelSheet.cell(1, i + 2).value = days[i].printSelf()
    for i in range(len(JpNames)):
        illuSheet.cell(i + 2, 1).value = ZhNames[i]
        novelSheet.cell(i + 2, 1).value = ZhNames[i]
        for j in range(len(days)-1):
            illuSheet.cell(i + 2, j + 2).value = tagTotalGet(tagInfoGet('illustrations', JpNames[i], scd=days[j].printSelf(),
                                                                        ecd=(days[j+1]-1).printSelf()), 'illust')
        print(str(i * 2 + 1) + ' / ' + str(len(JpNames) * 2))
        for j in range(len(days)-1):
            novelSheet.cell(i + 2, j + 2).value = tagTotalGet(tagInfoGet('novels', JpNames[i], scd=days[j].printSelf(),
                                                                         ecd=(days[j+1]-1).printSelf()), 'novel')
        print(str(i * 2 + 2) + ' / ' + str(len(JpNames) * 2))
    timeString = time.asctime(time.localtime(time.time()))
    timeString = timeString.replace(":", "")
    f.save("PixivTime_" + timeString + theme + ".xlsx")
    print("Pixiv Part Finished")


PixivTime("mikuAndVocalo",Date(2008,1,1),30,177)

        其中,JpNames.txt用于存放要统计的Tag,内容为:

('初音ミク','VOCALOID')

        ZhNames.txt中可以存放JpNames.txt中Tag的中文翻译,便于阅读统计结果。本次统计中ZhNames.txt的内容与JpNames.txt的内容相同。

        cookie.txt的内容为访问Pixiv时要用到的Cookie信息,出于信息安全因素,该文件内容不予公开。Cookie的获取方法可参考https://blog.csdn.net/c406495762/article/details/78123502。获取前需确保你已登录一显示满18周岁的账号,且设置 - 浏览限制中的R-18、R-18G开关均为打开状态,以确保“搜索条件”功能可以正常使用,且可以统计到R-18、R-18G相关插图 / 小说的数据。另外,在“屏蔽设置”中不应有Tag和用户被屏蔽,以避免不必要的统计误差。

        该代码的运行结果为在同目录下生成带相应数据的.xlsx文件,文件名格式为PixivTime_[运行完成时刻]mikuAndVocalo.xlsx。

三、统计结果

        通过简单的数据处理,得折线图以及详细统计结果如下:

四、数据分析

        初音未来折线在每年出现两个峰值,分别为MIKU之日(3月9日)和MIKU的纪念日(8月31日)的创作高峰所致。

        在折线图中,初音未来折线与VOCALOID折线在峰值、走势上都具有较强的相关性,可以认为VOCALOID折线在很大程度上受初音未来折线的控制。

        从折线的大体走势以及峰值的高低来看,VOCALOID相关插图统计量在统计时间段内经历了增加——稳定——减少——增加四个阶段的变化。如果试以插图投稿量作为VOCALOID文化热度的量化指标,则可归纳近十数年来VOCALOID文化经历的“扩大——兴盛——衰退——复兴”阶段如下:

  • ①扩大期: - 约2011年初,VOCALOID亚文化圈快速地扩大声势。实际扩张速度可能慢于折线数据所体现的,因为还要从这一速度中减去Pixiv网站本身的热度增长所带来的影响。

  • ②兴盛期:约2011年初 - 2013年中,VOCALOID文化热度在这一时期内缓慢上升,但上升速度逐渐减缓。

  • ③衰退期:2013年中 - 2017年中,VOCALOID热度在该时间段内逐渐减退。以2017年夏末发生的一次爆发式投稿事件为界,该衰退期迎来结束。

  • ④复兴期:2017年中 - 至今。VOCALOID热度逐渐回暖,并在目前基本上回到了兴盛期初水平。

五、结论与展望

        VOCALOID的插图投稿量在很大程度上取决于初音未来的插图投稿量。从该投稿量的月度变化曲线中,我们注意到初音未来以及VOCALOID文化热度经历了“扩大——兴盛——衰退——复兴”的四阶段变化。

        Pixiv网站本身的热度增长对插图投稿量曲线的影响程度如何,以及导致曲线呈现出四阶段变化的原因为何、标志性事件为何,有待将来进一步展开探讨。■

【统计向】2008年至今Pixiv上关于初音未来或VOCALOID的插画的投稿量逐月变化趋势的评论 (共 条)

分享到微博请遵守国家法律