【統(tǒng)計(jì)向】2008年至今Pixiv上關(guān)于初音未來(lái)或VOCALOID的插畫(huà)的投稿量逐月變化趨勢(shì)
一、摘要
????????本篇專(zhuān)欄通過(guò)Python編寫(xiě)的爬蟲(chóng)自動(dòng)完成了2008年1月1日至目前(2022年7月16日)初音未來(lái)、VOCALOID兩個(gè)Tag的逐月(30天)的Pixiv插畫(huà)投稿量統(tǒng)計(jì)。統(tǒng)計(jì)結(jié)果體現(xiàn)了十?dāng)?shù)年來(lái)VOCALOID文化經(jīng)過(guò)的“擴(kuò)大——興盛——衰退——復(fù)興”四階段變化。統(tǒng)計(jì)代碼可供將來(lái)其它人物 / 企劃的插畫(huà) / 小說(shuō)統(tǒng)計(jì)使用。

二、統(tǒng)計(jì)方法
????????統(tǒng)計(jì)通過(guò)Python代碼進(jìn)行,代碼如下:
# coding:utf-8
import requests
import urllib.parse
import urllib3
import openpyxl
import time
import html
class Date:
? ?# 建立一個(gè)日期類(lèi),以供后續(xù)代碼使用
? ?__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):
? ? ? ?# 返回自身過(guò)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):
? ?# 返回同目錄下一文件內(nèi)的內(nèi)容,asStr為T(mén)rue時(shí)以字符串的形式返回,否則返回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:
? ?# 通過(guò)requests訪問(wèn)Pixiv,返回Tag自scd日期開(kāi)始到ecd日期為止form形式的創(chuàng)作的統(tǒng)計(jì)信息,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,得到的數(shù)據(jù)將不準(zhǔn)確。
? ? ? ?'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獲取統(tǒng)計(jì)信息后,將作品總數(shù)數(shù)據(jù)從中提取出來(lái)
? ?# form can be: 'novel', 'illust'
? ?return tagInfo["body"][form]["total"]
def PixivTime(theme: str, startDate: Date, dayInterval: int, samplePointN: int):
? ?# theme:決定輸出.xlsx文件的標(biāo)題。startDate:統(tǒng)計(jì)開(kāi)始時(shí)間,為之前建立的Date類(lèi)格式。
? ?# dayInterval:統(tǒng)計(jì)粒度,即每個(gè)數(shù)據(jù)的時(shí)間間隔,單位為天。samplePointN:每個(gè)Tag的統(tǒng)計(jì)數(shù)據(jù)數(shù)量。
? ?# dayInterval 必須 < 365,因?yàn)镻ixiv的按時(shí)間查找作品功能支持的時(shí)長(zhǎng)為一年之內(nèi)。
? ?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("插圖統(tǒng)計(jì)")
? ?novelSheet = f.create_sheet("小說(shuō)統(tǒng)計(jì)")
? ?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用于存放要統(tǒng)計(jì)的Tag,內(nèi)容為:
('初音ミク','VOCALOID')
????????ZhNames.txt中可以存放JpNames.txt中Tag的中文翻譯,便于閱讀統(tǒng)計(jì)結(jié)果。本次統(tǒng)計(jì)中ZhNames.txt的內(nèi)容與JpNames.txt的內(nèi)容相同。
????????cookie.txt的內(nèi)容為訪問(wèn)Pixiv時(shí)要用到的Cookie信息,出于信息安全因素,該文件內(nèi)容不予公開(kāi)。Cookie的獲取方法可參考https://blog.csdn.net/c406495762/article/details/78123502。獲取前需確保你已登錄一顯示滿18周歲的賬號(hào),且設(shè)置 - 瀏覽限制中的R-18、R-18G開(kāi)關(guān)均為打開(kāi)狀態(tài),以確?!八阉鳁l件”功能可以正常使用,且可以統(tǒng)計(jì)到R-18、R-18G相關(guān)插圖 / 小說(shuō)的數(shù)據(jù)。另外,在“屏蔽設(shè)置”中不應(yīng)有Tag和用戶(hù)被屏蔽,以避免不必要的統(tǒng)計(jì)誤差。
????????該代碼的運(yùn)行結(jié)果為在同目錄下生成帶相應(yīng)數(shù)據(jù)的.xlsx文件,文件名格式為PixivTime_[運(yùn)行完成時(shí)刻]mikuAndVocalo.xlsx。

三、統(tǒng)計(jì)結(jié)果
????????通過(guò)簡(jiǎn)單的數(shù)據(jù)處理,得折線圖以及詳細(xì)統(tǒng)計(jì)結(jié)果如下:



四、數(shù)據(jù)分析
????????初音未來(lái)折線在每年出現(xiàn)兩個(gè)峰值,分別為MIKU之日(3月9日)和MIKU的紀(jì)念日(8月31日)的創(chuàng)作高峰所致。
? ? ????在折線圖中,初音未來(lái)折線與VOCALOID折線在峰值、走勢(shì)上都具有較強(qiáng)的相關(guān)性,可以認(rèn)為VOCALOID折線在很大程度上受初音未來(lái)折線的控制。
????????從折線的大體走勢(shì)以及峰值的高低來(lái)看,VOCALOID相關(guān)插圖統(tǒng)計(jì)量在統(tǒng)計(jì)時(shí)間段內(nèi)經(jīng)歷了增加——穩(wěn)定——減少——增加四個(gè)階段的變化。如果試以插圖投稿量作為VOCALOID文化熱度的量化指標(biāo),則可歸納近十?dāng)?shù)年來(lái)VOCALOID文化經(jīng)歷的“擴(kuò)大——興盛——衰退——復(fù)興”階段如下:
①擴(kuò)大期: - 約2011年初,VOCALOID亞文化圈快速地?cái)U(kuò)大聲勢(shì)。實(shí)際擴(kuò)張速度可能慢于折線數(shù)據(jù)所體現(xiàn)的,因?yàn)檫€要從這一速度中減去Pixiv網(wǎng)站本身的熱度增長(zhǎng)所帶來(lái)的影響。
②興盛期:約2011年初 - 2013年中,VOCALOID文化熱度在這一時(shí)期內(nèi)緩慢上升,但上升速度逐漸減緩。
③衰退期:2013年中 - 2017年中,VOCALOID熱度在該時(shí)間段內(nèi)逐漸減退。以2017年夏末發(fā)生的一次爆發(fā)式投稿事件為界,該衰退期迎來(lái)結(jié)束。
④復(fù)興期:2017年中 - 至今。VOCALOID熱度逐漸回暖,并在目前基本上回到了興盛期初水平。

五、結(jié)論與展望
????????VOCALOID的插圖投稿量在很大程度上取決于初音未來(lái)的插圖投稿量。從該投稿量的月度變化曲線中,我們注意到初音未來(lái)以及VOCALOID文化熱度經(jīng)歷了“擴(kuò)大——興盛——衰退——復(fù)興”的四階段變化。
????????Pixiv網(wǎng)站本身的熱度增長(zhǎng)對(duì)插圖投稿量曲線的影響程度如何,以及導(dǎo)致曲線呈現(xiàn)出四階段變化的原因?yàn)楹?、?biāo)志性事件為何,有待將來(lái)進(jìn)一步展開(kāi)探討?!?br/>