文章目录

  • 1. 前言
  • 2. 数据抓取——分享一个调度服务框架
  • 3. 数据处理——基于Numpy的预处理
    • 3.1 读取指定编号博主的投票数据
    • 3.2 取得指定时刻得票数量前n名的博主序号
  • 4. 数据分析——基于matplotlib绘图
    • 4.1 绘制指定编号博主的投票曲线
    • 4.2 绘制指定编号博主的10分钟投票增量曲线
    • 4.3 将多位博主得票数据绘制在一张图上
    • 4.4 绘制日增量柱状图,打印markdown格式的位次变化表
  • 5. 趋势预测——最小二乘法拟合多项式

1. 前言

万众瞩目的CSDN博客之星总评选投票活动渐入佳境,竞争趋于白热化。入选前200名的博主们火力全开,使出了浑身解数,通过各种渠道拉票。一时间,CSDN刷爆了各大自媒体。无论是吃瓜群众,还是摇旗呐喊、擂鼓助威的亲友团,无不赞叹:CSDN这一波广告创意,真高!

俗话说,外行看热闹,内行看门道。姑且让亲友团、粉丝团飞一会儿,我来给吃瓜群众介绍一下,如何用数据分析的手段,剖析今年的CSDN博客之星总评选,并用机器学习的方式预测后续的竞争结果。本文纯粹出于技术交流之目的,绝无恶意揣测诽谤他人之意图;所谓预测,亦出于游戏之心态,只为博同学们一笑耳。

从17日开始,关心CSDN博客之星总评选的朋友们,可以点击“2019年CSDN博客之星总评选投票走势”查看实时的数据统计结果了。该站点数据来源于本次活动的官方网站,未经任何修改,亦无任何观点倾向,仅供参考。

2. 数据抓取——分享一个调度服务框架

做数据分析,首先得有数据。看2019年CSDN博客之星总评选,就要从活动网站上抓取数据。抓数据不难,同学们都会,但要抓历史数据,就需要费功夫了。因为活动网站上没有提供历史数据下载服务,我们只能不断地以固定时间间隔访问网站,并将数据记录下来。这就需要一个长期工作的服务程序。通常,执行定时任务的服务程序,都是基于调度服务的框架。

APScheduler 是我最喜欢的一个用于调度服务的模块,其全称是 Advanced Python Scheduler。这是一个轻量级的 Python 定时任务调度框架,功能非常强大。单说这个模块的话,洋洋洒洒可以写万字以上,但本文的重点不是 APScheduler,所以这里直接分享一个APScheduler 的应用实例。虽然只有区区70余行代码,依然非常实用、健壮。APScheduler 的安装很简单:

python -m pip install apscheduler

下面的程序启动之后,每10分钟从2019年CSDN博客之星总评选活动网站抓取一次数据,以博主编号为文件名,保存在和服务程序同级的 data 文件夹下,文件格式如下:

天元浪子
2020-01-15 11:10:00,1,10512
2020-01-15 11:20:00,1,10517
2020-01-15 11:30:00,1,10527
… …

scheduler_catch.py

# coding:utf-8"""定时数据抓取服务"""import os, re, json, time
from datetime import datetime
import urllib.request
from apscheduler.schedulers.blocking import BlockingSchedulerdef start_service():"""启动数据抓取调度服务"""scheduler = BlockingScheduler()scheduler.add_job(TimeJobHandler,args = (),trigger = "cron",second = "0",minute = "0/10",hour = "*",day = "*",month = "*",day_of_week = "*",year = "*",misfire_grace_time = 60)scheduler.start()def TimeJobHandler():url = "http://m234140.nofollow.ax.mvote.cn/action/viewvotewxorderlist.html?voteguid=43ced329-3a4b-0a5d-a13c-f088cf8eafef"res = urllib.request.urlopen(url)now = datetime.now()html = res.read().decode("utf-8")itemMatch(html, now)def itemMatch(content, now):cwd = os.getcwd()path = os.path.join(cwd, "data")if not os.path.isdir(path):os.makedirs(path)content = content.replace("\r\n", "").replace("\n", "").replace("\r", "").replace("\"", "'")p = re.compile("第(\d+)名")col0 = p.findall(content) # 第N名p = re.compile("<span class='optt'><a href='.*?'>(.*?)</a></span>", )col1 = p.findall(content) # 当前名次p = re.compile("<td class='' style='width:80px'>(\d+)票</td>")col2 = p.findall(content) # 当前票数newName = []for i, name in enumerate(col1):if "(点此进入个人页)" in name:ns = name.split(u"(")name = ns[0]name = name.strip()ns = name.split(".")num = ns[0]truename = "".join(ns[1:]).strip()filename = os.path.join(path, num + ".txt")if os.path.isfile(filename):f = open(filename, 'a')else:f = open(filename, 'w')f.write(truename + "\n")f.write(now.strftime("%Y-%m-%d %X") + "," + col0[i] + "," + col2[i] + "\n")f.close()if __name__ == '__main__':start_service()

3. 数据处理——基于Numpy的预处理

数据处理过程中导入用到两个模块,先统一写在这里,后面讲解时就不再处处导入了:

import numpy as np
from datetime import datetime, timedelta

3.1 读取指定编号博主的投票数据

def read_by_no(no, start_dt, end_dt):"""读取指定编号博主的投票数据"""with open('data/%d.txt'%no, 'r', encoding='utf-8') as fp:lines = fp.readlines()name = lines[0].strip()dt_list = list()rank_list = list()votes_list = list()for line in lines[1:]:dt, rank, votes = line.strip().split(',')if (not start_dt or start_dt and dt >= start_dt) and (not end_dt or end_dt and dt <= end_dt):dt_list.append(datetime.strptime(dt, '%Y-%m-%d %H:%M:%S'))rank_list.append(int(rank))votes_list.append(int(votes))return name, dt_list, rank_list, votes_list

3.2 取得指定时刻得票数量前n名的博主序号

def get_top(n, end_dt):"""取得end_dt时刻前n名序号"""ranks = list()for i in range(1, 202):with open('data/%d.txt'%i, 'r', encoding='utf-8') as fp:lines = fp.readlines()for j in range(1, len(lines)):dt, rank, votes = lines[j].strip().split(',')if dt > end_dt:breakdt, rank, votes = lines[j-1].strip().split(',')ranks.append((i, int(rank)))ranks.sort(key=lambda x:x[1])return [item[0] for item in ranks][:n]

4. 数据分析——基于matplotlib绘图

绘图函数需要导入matplotlib模块,并设置中文字体。先统一写在这里,后面讲解时就不再处处导入了:

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.ticker as tickerplt.rcParams['font.sans-serif'] = ['FangSong']  # 设置默认字体
plt.rcParams['axes.unicode_minus'] = False  # 解决保存图像时'-'显示为方块的问题

4.1 绘制指定编号博主的投票曲线

def plot_votes(no, start_dt=None, end_dt=None):"""绘制指定编号博主的投票曲线"""name, dt_list, rank_list, votes_list = read_by_no(no, start_dt, end_dt)plt.figure('2019年CSDN博客之星总评选统计图表', facecolor='#f4f4f4', figsize=(15, 8))plt.title('2019年CSDN博客之星总评选%s10分钟得票数量统计曲线'%name, fontsize=20)plt.grid(linestyle=':') # 辅助网格plt.plot(dt_list, votes_list) # 绘制数据plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M')) # 格式化时间轴标注plt.gcf().autofmt_xdate() # 优化标注(自动倾斜)#plt.savefig('image/得票数量统计曲线.png') # 保存为文件plt.show()plot_votes(168)

绘制效果如下:

4.2 绘制指定编号博主的10分钟投票增量曲线

def plot_delta(no, start_dt=None, end_dt=None):"""绘制指定编号博主的10分钟投票增量曲线"""name, dt_list, rank_list, votes_list = read_by_no(no, start_dt, end_dt)plt.figure('2019年CSDN博客之星总评选统计图表', facecolor='#f4f4f4', figsize=(15, 8))plt.title('2019年CSDN博客之星总评选%s10分钟投票增量统计曲线'%name, fontsize=20)plt.grid(linestyle=':') # 辅助网格votes_list.insert(0, votes_list[0])votes_list = np.diff(np.array(votes_list))plt.plot(dt_list, votes_list, color='g') # 绘制数据plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M')) # 格式化时间轴标注plt.gcf().autofmt_xdate() # 优化标注(自动倾斜)#plt.savefig('image/投票增量曲线.png') # 保存为文件plt.show()sdt = '2020-01-13 00:00:00'
edt = '2020-01-15 00:00:00'
plot_delta(168, start_dt=sdt, end_dt=edt)

绘制效果如下:

4.3 将多位博主得票数据绘制在一张图上

函数 get_top(n, end_dt) 返回 end_dt 时刻得票数量排名前 n 位的博主的序号列表。遍历这个列表,我们可以把多位博主的投票曲线或者投票增量曲线,画在同一张图上。这里就不一一给出代码了,直接贴出效果图:

4.4 绘制日增量柱状图,打印markdown格式的位次变化表

接下来,我们再学习画柱状图,用以分析TOP20的博主们每天的投票增量。

def plot_votes_delta(n, dt):"""绘制日增量柱状图,打印markdown格式的位次变化表"""last_dt = datetime.strptime(dt, '%Y-%m-%d %H:%M:%S') - timedelta(days=1)last_dt = last_dt.strftime('%Y-%m-%d %X')no_list = get_top(n, dt)names, dv = list(), list()table_str = '|编号|博主|位次|升降|总得票数|日增票数|\n|:---:|:---:|:---:|:---:|:---:|:---:|\n'for i in range(n):no = no_list[i]name, dt_list, rank_list, votes_list = read_by_no(no, last_dt, dt)dvotes = votes_list[-1]-votes_list[0]drank = rank_list[0]-rank_list[-1]if drank > 0:drank_str = '<font color="red">↑%d</font>'%drankelif drank < 0:drank_str = '<font color="green">↓%d</font>'%abs(drank)else:drank_str = '-'table_str += '|%d|%s|%d|%s|%d|%d|\n'%(no, name, i+1, drank_str, votes_list[-1], dvotes)names.append(name)dv.append(dvotes)print(table_str)color=[(np.random.random(),np.random.random(),np.random.random()) for i in range(n)]plt.figure('2019年CSDN博客之星总评选统计图表', facecolor='#f4f4f4', figsize=(15, 12))plt.title('2019年CSDN博客之星总评选TOP%d(截至%s)日增投票数量柱状图'%(n, dt), fontsize=20)plt.barh(names[::-1], dv[::-1], align="center", height=0.5, alpha=1.0, color=color)plt.gcf().autofmt_xdate()plt.savefig('image/日增投票数量柱状图%s.png'%dt[:10])plt.show()sdt = '2020-01-13 00:00:00'
edt = '2020-01-15 00:00:00'
plot_votes_delta(20, edt)

绘制效果如下:

markdown格式的位次变化表:

编号 博主 位次 升降 总得票数 日增票数
168 天元浪子 1 - 9770 1795
22 Eastmount 2 - 8366 1062
25 Programer Cat 3 - 7597 1357
23 _YourBatman 4 ↑3 7037 1806
176 小傅哥 5 ↓1 6994 1513
200 DrogoZhang 6 ↓1 6803 1436
127 Mike__Jiang 7 ↓1 6741 1503
57 lilongsy 8 ↑3 6166 1577
201 刘望舒 9 - 6158 1197
95 敖丶丙 10 - 6033 1234
21 程序猿DD 11 ↑1 5910 1883
79 沉默王二 12 ↓4 5882 683
76 唯有坚持不懈 13 ↑2 5037 1258
82 人工智能博士 14 - 4597 776
68 十步杀一人_千里不留行 15 ↓2 4349 387
20 段智华 16 - 4094 915
69 不脱发的程序猿 17 - 3748 1016
66 lynnlovemin 18 - 3528 862
103 Vam的金豆之路 19 - 3120 578
70 狂野小青年 20 ↑2 3072 741

5. 趋势预测——最小二乘法拟合多项式

关于最小二乘法实现多项式拟合,请参考我的另一篇博文:《从寻找谷神星的过程,谈最小二乘法实现多项式拟合》。下面我们以168号博主最近4天的投票数据为例,用最小二乘法拟合多项式,对天元浪子未来一天的投票结果做出趋势预测(纯属搞笑,切勿当真)。

def predict(no, start_dt, end_dt):"""趋势预测"""name, dt_list, rank_list, votes_list = read_by_no(168, start_dt, end_dt)_y = votes_list = np.array(votes_list)_x = np.arange(_y.shape[0])x = np.arange(_y.shape[0]+144) # 预测24小时后得票数量g3 = np.poly1d(np.polyfit(_x, _y, 3))g4 = np.poly1d(np.polyfit(_x, _y, 4))loss3 = np.sum(np.square(g3(_x)-_y))/_y.shape[0]loss4 = np.sum(np.square(g4(_x)-_y))/_y.shape[0]plt.figure('2019年CSDN博客之星总评选统计图表', facecolor='#f4f4f4', figsize=(15, 8))plt.title('2019年CSDN博客之星总评选%s24小时趋势预测'%name, fontsize=20)plt.plot(_x, _y, label='原始数据')plt.plot(x, g3(x), label='3次多项式,预测24小时后得票数量:%d'%int(g3(x)[-1]))plt.plot(x, g4(x), label='4次多项式,预测24小时后得票数量:%d'%int(g4(x)[-1]))plt.plot(_x, g3(_x), label='3次多项式,误差%0.4f'%loss3)plt.plot(_x, g4(_x), label='4次多项式,误差%0.4f'%loss4)plt.legend()plt.show()

经过比较,4次多项式拟合结果误差最小。预测结果如下:


既然都读到这里了,就给168号博主天元浪子投上5票吧,谢谢!

用数据分析的手段,看2019年CSDN博客之星总评选相关推荐

  1. 2019年度CSDN博客之星TOP10榜单揭晓,你上榜了吗?

    培根说,『读书造成充实的人,会议造成未能觉悟的人,写作造成正确的人』. 在短信短视频快速迭代的快时代,更深度的思考.更正确的实践,更成体系的写作与分享,尤显可贵.这里,每一篇博文都是开发者实战的经验解 ...

  2. 大屏实时监控-2019年CSDN博客之星年度总评选(2019-02-07 13:47)

    监控地址:http://anymk.com:8888/ 2019-02-07 13:47 2019-02-01 20:09 2019-01-28 15:17 2019-01-25 15:01 大屏实时 ...

  3. 大屏实时监控-2019年CSDN博客之星年度总评选(2019-01-20 23:30)

    近期的2019年CSDN博客之星年度总评选战况空前激烈,前面看了第一名天元浪子每日发布的投票统计博客,觉得很有意思,于是我就换个角度来分析一下.目前大屏监控比较流行,我也就跟风制作了一个供大家鉴赏. ...

  4. CSDN:2019年度CSDN博客之星评选竞赛——105号【一个处女座的程序猿】,感谢您,投上的宝贵一票,感谢!感恩!

    CSDN:2019年度CSDN博客之星评选竞赛--105号[一个处女座的程序猿],感谢您,投上的宝贵一票,感谢!感恩! 导读:新的一年,改革春风吹满地,新的一年要争气!  博主在此,祝所有朋友们,20 ...

  5. 数风流人物,俱在今朝!|2019年度CSDN博客之星TOP10榜单揭晓-智华非常荣幸入选中国博客之星TOP9,非常感谢大家的支持,愿和读者朋友们共同努力,坚持学习,让每个人都能实现自己的梦想!

    培根说,『读书造成充实的人,会议造成未能觉悟的人,写作造成正确的人』. 在短信短视频快速迭代的快时代,更深度的思考.更正确的实践,更成体系的写作与分享,尤显可贵.这里,每一篇博文都是开发者实战的经验解 ...

  6. 2019年度CSDN博客之星TOP10榜单揭晓 智华入选TOP9!

    培根说,『读书造成充实的人,会议造成未能觉悟的人,写作造成正确的人』.在短信短视频快速迭代的快时代,更深度的思考.更正确的实践,更成体系的写作与分享,尤显可贵.这里,每一篇博文都是开发者实战的经验解读 ...

  7. 跟踪了下CSDN博客之星的竞选数据,我就看看不评论,你怎么看?

    实时统计:http://csdn.didispace.com/ 我就看看,不评论,你怎么看? 我也在竞选CSDN博客之星,支持我点下面 集合!随我一起搞台 Macbook Pro 如何? 朕已阅 

  8. 写在末日来临之前的2012 CSDN 博客之星评选

    写在末日来临之前的2012 CSDN 博客之星评选 最近收到邮件通知参加2012 CSDN 博客之星评选,惊喜入围88名候选人,感谢CSDN给IT人员这个平台,分享技术问题,2012年即将过去,201 ...

  9. 【2013年度CSDN博客之星】评奖:全文五言句,俺也拉个票

    收到通知信,知推(2013年度CSDN博客之星)候选人. 急忙看排名,形势还不妙.票数才个位,绝对不科学!宣传不到位,这个有问题. 再来看名单,不少老面孔.不惜我之力,各送一助攻. 谁来助攻我,需要想 ...

  10. 荣幸获得CSDN博客之星第三名,感激不尽!Python

    荣幸获得CSDN博客之星第三名,感激不尽!Python 最近在学习Python的过程中,发现了一个非常有趣的功能--使用Python来生成美丽的图形.这个功能不仅可以在科学.工程方面得到广泛应用,同时 ...

最新文章

  1. phpmyadmin执行mysql语句_如何在phpMyAdmin中执行sql语句
  2. DB2定时清理归档日志脚本
  3. Oracle分析函数FIRST_VALUE、LAST_VALUE
  4. 创建一个html网页,创建第一个HTML网页(未完待续)
  5. ASP.NET Core中HTTP管道和中间件的二三事
  6. 你准备好了在云中工作吗?
  7. 【转】aspx,ascx和ashx使用小结
  8. LockSupport的park和unpark
  9. 王道 —— 操作系统的概念(定义)、功能和目标
  10. Java 学习总结(189)—— Java 8 功能接口使用总结
  11. linux oracle显示乱码,Linux下oracle显示乱码解决
  12. hello ,test livewriter
  13. html代码怎么出歌词,html中的歌词翻译
  14. mysql中间件研究(tddl atlas cobar sharding-jdbc)
  15. 利用Rsyslog向ElasticSearch推送nginx access日志
  16. (自动)运动控制系统学习笔记
  17. 问卷调查页面的设计与实现
  18. VB.net 播放 WAV音乐
  19. 家庭多房间网线连接---小白快速上手
  20. SpringBoot中@Pattern注解对时间格式校验

热门文章

  1. PHP 使用rabbitmq 入门教程
  2. TCPIP协议详解内容总结
  3. 枪林弹雨显示服务器正在维修,枪林弹雨Patcher 已停止工作解决方法教程
  4. python时频图_怎样用python画wav文件的时频分析图
  5. 蝉妈妈对话曹颖:5月带货1.1亿,踩过的坑都变成了卖货的筹码
  6. 计算机软件系统由程序和相应的文档组成,ab计算机软件
  7. 认识ManualResetEvent
  8. 暴力破解QQ空间设置的问题
  9. 如何给linux添加新硬盘(转)
  10. 自己的电脑怎么测网速