起点,作为一个8年的老书虫肯定是知道。既然学习了数据分析,就看看起点的数据。
1 获取数据
首先,肯定要先获取数据,巧妇难为无米之炊,没有数据也是白搭。没有现成的数据,只能通过爬虫来爬取我们需要的数据,这里就不写怎么获取数据了。爬虫的代码是我写的第一个爬虫,准确的说应该是复制粘贴。写得也真是够烂的,爬取过程一直断,只好分小说类型一点一点爬取。庆幸的是起点中文网并没有什么反爬虫措施,不然连数据都拿不到。
主要爬取的内容有:
属性 | 说明 |
---|---|
id | 小说在起点的id |
title | 小说名 |
author | 作者 |
chapter_nums | 章节数 |
word_nums | 字数 |
last_update_date | 最后更新时间 |
first_update_date | 第一次更新时间 |
category | 一级分类 |
sub_category | 二级分类 |
rate | 评分 |
discuss_nums | 讨论数 |
click_nums | 点击数 |
commend_nums | 推荐数 |
sex | 性别 |
crawl_time | 爬取时间 |
爬取到的数据存储到mysql,主要是对mysql比较熟悉,数据量有点大存到文本就有点不适合了。
2 分析
导包和配置
%matplotlib inline
import pandas as pd
import matplotlib.pyplot as plt
from wordcloud import WordCloud
import jieba
import re
import PIL
from datetime import datetime
from datetime import timedelta
import seaborn as sns
# 中文乱码设置
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['font.family']='sans-serif'
# 设置x, y轴刻度字体大小
plt.rcParams['xtick.labelsize'] = 16
plt.rcParams['ytick.labelsize'] = 16
plt.style.use('ggplot')
2.1 读取数据
读取数据库使用SQLAlchemy,不过SQLAlchemy本身是无法操作数据库,需要安装mysql驱动。SQLAlchemy结合pandas使用读取数据还是很方便的。
import sqlalchemy as sqla
db = sqla.create_engine('mysql+mysqlconnector://root:123456@localhost:3306/article_spider')
df_novels = pd.read_sql('select * from qidian_novel', db)
2.2 数据预处理
一般情况下,数据是不干净的,数据清洗会花去很多的时间。据说,做到了一定程度,如果数据是干净的,反而会觉得很不舒服。
df_novels.info()
有一列是空的,discuss_nums 评论数没有抓取, 然后还有一些存在空值。
- 删除空列
df_novels = df_novels.drop('discuss_nums', axis=1)
- 过滤缺失数据
df_novels = df_novels.dropna(how='any')
2.3 数据分析
2.3.1 连载与完本
在爬取的数据中,接近100万条,也就是有100万本小说。
status_counts = df_novels['novel_status'].value_counts()
status_labels = ['连载', '完本', '暂停']
plt.figure(figsize=(16, 8))
ax1 = plt.subplot(121)
sns.barplot(status_labels, status_counts, alpha=0.8)
ax1.set_ylabel('')
ax1.set_title('小说分布', size=26)
for x, y in zip(range(3), status_counts.values):
ax1.text(x, y, '%d(%.1f%%)' %(y, y/966022*100), ha='center', va='bottom',fontsize=16, color='b')
可以从图中看出,完本小说只占到6%,连载中的小说占了近90%,难道起点中文网这么火爆吗?
如果对起点、纵横等小说网站的写作有所了解,对这个数据不会感到多少意外。毕竟写作门槛很低,随便创个账号就好了,很多人都会想着去创作。很多小说只有几章的,因为发现写了一点就写不下去了。曾经我也有过这个念头,只是没有行动过。
chapter_zeroToTen = df_novels[(df_novels['chapter_nums'] <= 10) & (df_novels['novel_status'] == '连载')].title.count()
chapter_all = df_novels[(df_novels['chapter_nums'] > 10) & (df_novels['novel_status'] == '连载')].title.count()
chapter_data = [chapter_zeroToTen, chapter_all]
labels = ['10章以下的小说', '11章以上的小说']
ax = plt.figure(figsize=(8, 8)).add_subplot(111)
patches, texts, autotexts = ax.pie(chapter_data, labels=labels, autopct='%.1f%%', startangle=90, colors=['wheat', 'skyblue'])
for t in texts:
t.set_size('xx-large')
for at in autotexts:
at.set_size('xx-large')
plt.title('10章以下的小说比例', size=26)
plt.show()
10章以下的小说占了连载小说的67.1%,一般的网络小说会一天1到3更,几十万小说不可能是这几天才开始写的,所以说90多万小说水分还是极大的。
数据没有爬取签约小说这个标签,如果小说总量以签约小说来算应该更为合适。
2.3.2 性别
起点分男生网和女生网,男生网面向的男读者,女生网面向的是女读者。当然,如果男读者看女生网的小说是没有问题,肯定没有人会限制你。
sex_counts = df_novels['sex'].value_counts()
labels = ['男生', '女生']
ax = plt.figure(figsize=(8, 8)).add_subplot(111)
patches, texts, autotexts = ax.pie(sex_counts.values, labels=labels, autopct='%.1f%%', startangle=90, colors=['wheat', 'skyblue'])
for t in texts:
t.set_size('xx-large')
for at in autotexts:
at.set_size('xx-large')
plt.title('男女生小说数量比例', size=26)
plt.show()
从图中,可以看出起点小说主要还是男生小说为主,现实中沉迷于网络小说的也是男的多,相应的男生网的小说比较多是比较正常的。
2.3.3 小说类型
很多时候,没有看过网络小说的人认为看网络小说都是看武侠小说。其实小说分类很多,武侠小说只是其实一种,在网络小说中,武侠小说反而不是那么流行。由于连载小说有大部分是只有几章的,这里只分析完本的。
novel_wb = df_novels[df_novels['novel_status'] == '完本']
novels_gg = novel_wb[novel_wb['sex'] == 'gg']
novels_gg_counts = novels_gg.category.value_counts()
ax = plt.figure(figsize=(16, 8)).add_subplot(111)
sns.countplot(x='category', data=novels_gg, order=novels_gg.category.value_counts().index)
ax.set_xlabel('')
ax.set_ylabel('')
ax.set_title('男生网小说一级分类', size=26)
# 设置标签
for a, b in zip(range(15), novels_gg_counts):
ax.text(a, b, '%d' % b, ha='center', va='bottom',fontsize=16, color='b')
plt.show()
这是男生网分类小说分类,最多的还是玄幻小说,说得比较多的武侠小说反而比较少。
novels_mm = novel_wb[novel_wb['sex'] == 'mm']
novels_mm_counts = novels_mm.category.value_counts()
ax = plt.figure(figsize=(12, 8)).add_subplot(111)
sns.countplot(x='category', data=novels_mm, order=novels_mm.category.value_counts().index)
ax.set_xlabel('')
ax.set_ylabel('')
ax.set_title('女生网小说一级分类', size=26)
# 设置标签
for a, b in zip(range(9), novels_mm_counts):
ax.text(a, b, '%d' % b, ha='center', va='bottom',fontsize=16, color='b')
plt.show()
从女生网小说看,类型划分相对较少,一般我们认为女生小说就是言情小说,其实也没错,主要还是言情小说为主,只是言情小说还有分类。
上面说的分类都是一级分类,在每一个分类又细分出很多小说类型。这里只看看看一级类型书最多的的子类型。
- 玄幻
category_xh = novel_wb[novel_wb['category'] == '玄幻']
category_xh_counts = category_xh.sub_category.value_counts()
ax = plt.figure(figsize=(8, 6)).add_subplot(111)
sns.barplot(x=category_xh_counts.index, y=category_xh_counts, order=category_xh_counts.index)
ax.set_ylabel('')
ax.set_title('玄幻小说的子分类', size=26)
# 设置标签
for a, b in zip(range(4), category_xh_counts):
ax.text(a, b, '%d' % b, ha='center', va='bottom',fontsize=16, color='b')
plt.show()
玄幻小说下面的子分类并不多,主要还是以东方玄幻和异世大陆为主。
- 现代言情
category_xdyq = novel_wb[novel_wb['category'] == '现代言情']
category_xdyq_counts = category_xdyq.sub_category.value_counts()
ax = plt.figure(figsize=(14, 6)).add_subplot(111)
sns.barplot(y=category_xdyq_counts.index, x=category_xdyq_counts, order=category_xdyq_counts.index)
ax.set_xlabel('')
ax.set_title('现代言情小说的子分类', size=26)
# 设置标签
for a, b in zip(range(11), category_xdyq_counts):
ax.text(b, a, '%d' % b, ha='left', va='center',fontsize=16, color='b')
plt.show()
在现代言情中又细分出很多,不过主要以豪门世家和都市生活这两个类型为主。
2.3.4 长久未更的小说
在一些书的评论中,往往会看到“太监了”,其实说的就是小说连载中的小说不更了,或者是小说结局写得太烂,烂尾了。在这里通过最后更新时间来看看停止更新的小说。
day_list = [30, 90, 180, 365]
day_label = ['one_month', 'three_month', 'half_year', 'one_year']
date = datetime.date(datetime(2017, 8, 26))
novel_lz = df_novels[df_novels['novel_status'] == '连载']
duan_geng = []
for day in day_list:
duan_geng.append(novel_lz[date-novel_lz['last_update_date'].values > timedelta(day)].novel_status.count())
ax = plt.figure(figsize=(8, 6)).add_subplot(111)
sns.barplot(x=day_label, y=duan_geng)
ax.set_xticklabels(['断更一个月', '断更三个月', '断更半年', '断更一年'])
ax.set_title('小说断更数量', size=26)
for x, y in zip(range(4), duan_geng):
ax.text(x, y, '%d' % y, ha='center', va='bottom',fontsize=16, color='b')
plt.show()
起点上的小数断更的还是占大部分的。判断小说是否太监,这里只从最后更新时间看,以多少天没更新为主。一年不更新的小说还是挺多的吗,一年不更新的小说,基本都可以说是不更新了,就算存在也是很少的。如果以断更1年为标准,起点上断更的小说超过一半。
2.3.5 小说字数前十
网络小说通常字数都是比较多的,也是多亏网络这个平台,不然都是印刷,成本不知道多高,出版的小说还是少数的。
novels_ten = df_novels.sort_values(by='word_nums', ascending=False)[:10]
ax = plt.figure(figsize=(12, 8)).add_subplot(111)
sns.barplot(y='title', x='word_nums', data=novels_ten)
ax.set_xlim(0, 27500000, 5000000)
ax.set_xlabel('')
ax.set_ylabel('')
for x, y in zip(novels_ten.word_nums.values, range(10)):
ax.text(x, y, '%d' % x, ha='left', va='center', fontsize=16, color='b')
ax.set_title('小说字数排名前十', size=26)
plt.show()
从图中看,网络小说的字数多的还是很多的,不过前三之后就急速下降了,但是1千万字依旧是一个很大数字。当前小说的字数不一定就是小说的最终字数,这里还包括连载中的小说。
novels_ten = df_novels[df_novels['novel_status'] == '连载'].sort_values(by='word_nums', ascending=False)[:10]
ax = plt.figure(figsize=(12, 8)).add_subplot(111)
sns.barplot(y='title', x='word_nums', data=novels_ten)
ax.set_xlim(0, 27500000, 5000000)
ax.set_xlabel('')
ax.set_ylabel('')
for x, y in zip(novels_ten.word_nums.values, range(10)):
ax.text(x, y, '%d' % x, ha='left', va='center', fontsize=16, color='b')
ax.set_title('连载中的小说字数前十', size=26)
plt.show()
字数排名第一的小说依旧在更新,这是要稳拿起点小说字数第一的宝座,这个作者真是好毅力(真能水)。
2.3.6 小说写作时间
很多小说一写就是好几年,不过很多跟一些经典著作是没得比的,所以网络小说是快餐小说为主。
df_write_year = pd.read_sql('SELECT FLOOR((TO_DAYS(last_update_date)-TO_DAYS(first_update_date))/365) AS write_year from qidian_novel WHERE (TO_DAYS(last_update_date)-TO_DAYS(first_update_date)) > 0', db)
write_year_counts = df_write_year.groupby(by='write_year')['write_year'].count()
ax = plt.figure(figsize=(12, 8)).add_subplot(111)
ax.bar(range(15), write_year_counts)
ax.set_xticks(range(15))
ax.set_xticklabels(range(15))
ax.set_xlabel('创作时间(年)', size=20)
ax.set_title('小说创作时间', size=26)
for x, y in zip(range(15), write_year_counts):
ax.text(x, y, '%d' % y, ha='center', va='bottom', fontsize=16, color='magenta')
plt.show()
从图中看,1年内的小说占极大部分,可以看出小说一般都是3年内,能够写10年以上的小说不多。虽然说10年以上的不一定是好的,但是能写10年还是很难得的。
2.3.7 字数最多的作者
在快餐小说盛行的现在,产量是一个很重要的指标,保持每天更新一定字数才有人看,所以字数产量还是很重要的。
novel_nums_author = novels.groupby(by='author').word_nums.sum()
most_words = novel_nums_author.sort_values(ascending=False)[:20]
ax = plt.figure(figsize=(12, 8)).add_subplot(111)
sns.barplot(y=most_words.index, x=most_words.values)
ax.set_xlim(0, 40000000, 5000000)
ax.set_xlabel('字数', size=20)
ax.set_ylabel('作者', size=20)
for x, y in zip(most_words.values, range(20)):
ax.text(x, y, '%d' % x, ha='left', va='center', fontsize=16, color='b')
ax.set_title('创作总字数排名前20', size=26)
plt.show()
排名第一的唐家三少,大名鼎鼎,可以说看网络小说的,还是挺少人不知道他的。毕竟人家产量高,更新稳定能够吸引很多人气。
2.3.8 书最多的作者
创作的总字数最多的不一定是书最多的作者,毕竟一本书的字数是没有明确规定。
novel_nums = df_novels[df_novels['novel_status'] == '完本'].author.value_counts()
most_books = novel_nums[1:21]
ax = plt.figure(figsize=(12, 8)).add_subplot(111)
sns.barplot(y=most_books.index, x=most_books.values)
ax.set_xlim(0, 28)
ax.set_xlabel('小说数量', size=20)
ax.set_ylabel('作者', size=20)
for x, y in zip(most_books.values, range(20)):
ax.text(x, y, '%d' % x, ha='left', va='center', fontsize=16, color='b')
ax.set_title('完本小说数量排名前20的作者', size=26)
plt.show()
在小说创作数量前20看,还是挺高产的。
bins = [1, 2, 3, 6, 11, 16, 20, 25]
books_group = pd.cut(novel_nums.values, bins=bins, right=False)
books_group_counts = pd.value_counts(books_group)
retbins = ['1本', '2本', '3-5本', '6-10本', '11-15本', '16-20本', '21-25本']
ax = plt.figure(figsize=(8, 6)).add_subplot(111)
sns.barplot(retbins, books_group_counts)
ax.set_xlabel('人数', size=20)
ax.set_title('创作数量分布', size=26)
for x, y in zip(range(7), books_group_counts):
ax.text(x, y, '%d' % y, ha='center', va='bottom', fontsize=16, color='magenta')
plt.show()
从创作数量看,起点上的作者创作一本的占了,创作10本以上的就寥寥无几了。
2.3.9 笔名
一个好的笔名还是很重要的,让人容易记住还是很关键的,如果都是什么斯基,估计出名还是挺难的。
author_gg = df_novels[df_novels['sex'] == 'gg']['author']
author_gg_text = ' '.join([author_ for author_ in author_gg])
filename_author_gg = 'E:/python/qidian/author_gg.txt'
with open(filename_author_gg,'w',encoding='utf-8') as f:
f.write(author_gg_text)
f.close()
author_mm = df_novels[df_novels['sex'] == 'mm']['author']
author_mm_text = ' '.join([author_ for author_ in author_mm])
filename_author_mm = 'E:/python/qidian/author_mm.txt'
with open(filename_author_mm,'w',encoding='utf-8') as f:
f.write(author_mm_text)
f.close()
font = r'C:\Windows\Fonts\simli.ttf'
author_gg_wordlist = jieba.cut(author_gg_text, cut_all=True)
author_gg_split = ' '.join(author_gg_wordlist)
coloring_gg = np.array(PIL.Image.open("E:/python/qidian/gg.png"))
gg_wordcloud = WordCloud(background_color="white",
max_words=2000,
mask=coloring_gg,
max_font_size=250,
random_state=42,
scale=2,
font_path=font).generate(author_gg_split)
author_mm_wordlist = jieba.cut(author_mm_text, cut_all=True)
autho_mm_split = ' '.join(author_mm_wordlist)
coloring_mm = np.array(PIL.Image.open("E:/python/qidian/mm.png"))
mm_wordcloud = WordCloud(background_color="white",
max_words=2000,
mask=coloring_mm,
max_font_size=250,
random_state=42,
scale=2,
font_path=font).generate(autho_mm_split)
ax1 = plt.figure(figsize=(16, 16)).add_subplot(121)
# plt.imshow(my_wordcloud)
ax1.imshow(gg_wordcloud)
ax1.axis("off")
ax2 = plt.subplot(122)
ax2.imshow(mm_wordcloud)
ax2.axis("off")
plt.show()
左边为男生网,右边为女生网,从词云图看,男生网偏男性,女生网偏女性。没有爬取作者的信息,不能确定,但可以猜测男作者主要是男作者比较多,女生网的作者主要是女作者比较多。
2.3.10 小说名
小说有百万数量,想要取一个好的书名还是挺看作者功力的。
title_gg = df_novels[df_novels['sex'] == 'gg']['title']
title_gg_text = ' '.join([title_ for title_ in title_gg])
filename_title_gg = 'E:/python/qidian/title_gg.txt'
with open(filename_title_gg,'w',encoding='utf-8') as f:
f.write(title_gg_text)
f.close()
title_mm = df_novels[df_novels['sex'] == 'mm']['title']
title_mm_text = ' '.join([title_ for title_ in title_mm])
filename_title_mm = 'E:/python/qidian/title_mm.txt'
with open(filename_title_mm,'w',encoding='utf-8') as f:
f.write(title_mm_text)
f.close()
font = r'C:\Windows\Fonts\simli.ttf'
title_gg_wordlist = jieba.cut(title_gg_text, cut_all=True)
title_gg_split = ' '.join(title_gg_wordlist)
coloring_gg = np.array(PIL.Image.open("E:/python/qidian/gg.png"))
gg_wordcloud = WordCloud(background_color="white",
max_words=2000,
mask=coloring_gg,
max_font_size=250,
random_state=42,
scale=2,
font_path=font).generate(title_gg_split)
title_mm_wordlist = jieba.cut(title_mm_text, cut_all=True)
autho_mm_split = ' '.join(title_mm_wordlist)
coloring_mm = np.array(PIL.Image.open("E:/python/qidian/mm.png"))
mm_wordcloud = WordCloud(background_color="white",
max_words=2000,
mask=coloring_mm,
max_font_size=250,
random_state=42,
scale=2,
font_path=font).generate(autho_mm_split)
ax1 = plt.figure(figsize=(16, 16)).add_subplot(121)
# plt.imshow(my_wordcloud)
ax1.imshow(gg_wordcloud)
ax1.axis("off")
ax2 = plt.subplot(122)
ax2.imshow(mm_wordcloud)
ax2.axis("off")
plt.show()
从男女词云图看,小说书名重生和穿越还是挺吃香的。男生网偏男性,女生网偏女性,这个在小说名词云图还是一样。
2.3.11 起点小说数量的增长趋势
date = datetime.date(datetime(2017, 8, 1))
novels = df_novels[date-df_novels['first_update_date'] > timedelta(0)].copy()
novels.index = pd.DatetimeIndex(novels.first_update_date)
count_novels = novels.id
grouped = count_novels.groupby(level=0)
group_month = grouped.count().resample('M', kind='period').sum()
ax = plt.figure(figsize=(16, 6)).add_subplot(111)
ax.get_xaxis().get_major_formatter().set_useOffset(False)
group_month.plot()
ax.set_xlabel('年份', size=20)
ax.set_title('2004-2017年起点小说月份新增数量的变化趋势', size=26)
plt.show()
从图中可以看出,每个月新增小说的数量是呈上升趋势的,虽然比较曲折。只是在2017年开始提升幅度很大,不知道不是起点本身数据的问题。
- 每月新增完本小说的的增长
novels_wb = novels[novels['novel_status'] == '完本']
count_novels_wb = novels_wb.id
grouped_wb = count_novels_wb.groupby(level=0)
group_month_wb = grouped_wb.count().resample('M', kind='period').sum()
ax = plt.figure(figsize=(16, 6)).add_subplot(111)
ax.get_xaxis().get_major_formatter().set_useOffset(False)
group_month_wb.plot()
ax.set_xlabel('年份', size=20)
ax.set_title('2004-2017年起点完本小说月份新增数量的变化趋势', size=26)
plt.show()
趋势也是曲折上升的,而且起伏较为明显。新增小说的数量影响到下一个时间段新增完本小说增加的数量,完本小说由新增小说转化而来,其实还可以看看转化率是多少,转化率的变化趋势。新小说数量增加越多,再下一个时间点完本小说的数量才能看出,毕竟小说从开始写到创作需要时间,可以看出大概是影响1年后的完本小说数量,小说创作时间集中在1年,与这是是相吻合的。