前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >十分钟用 Python 绘制动态排行图 —— 以 A 股历年市值前十股票排行榜为例

十分钟用 Python 绘制动态排行图 —— 以 A 股历年市值前十股票排行榜为例

原创
作者头像
davidac
修改2021-07-12 10:12:14
1.2K0
修改2021-07-12 10:12:14
举报
文章被收录于专栏:DataminC

开局一张图

图片
图片

相信大家都曾在 YouTube 和 B 站看到过类似的视频,这种图在国外叫做 Bar Chart Race,配上一段气势磅礴的 BGM,就会营造出一种「浮沉跌宕」的沉浸感,这类型的视频很多都获得了相当可观的播放量。

由于这类视频的大火,网络上已经有专门的制作工具,并且都以 NO-CODING 为营销卖点,也进一步导致了该类视频的「泛滥」。不过作为一个喜欢折腾的数据分析工程师,还是习惯通过手打代码的方式来实现。

一、数据源

可获取的数据有很多,这次也蹭把热点,以近期打工人都想入场大干一番的股市为主题,将历年 TOP 10 的 A 股股票通过动态排行图将其展示出来。

既然是关于股市的数据,那可以直接在证券交易所的官网查询到相关的数据。果不其然,上海证券交易所官网数据板块中,有向广大投资者提供「市值排名」的查询入口(http://www.sse.com.cn/market/stockdata/marketvalue/main/),点击进去会看到,我们「股票市价总值排名前十名」的报表,并可以通过日期筛选框进行查询。

图片
图片

数据源确定了,需要对接下来的工作流进行梳理。

二、数据流分析

图片
图片

三、网站分析

在网页上更改日期查询后,网址没有改变,页面也没有刷新,初步判断通过 Ajax 进行异步更新。在 Chrome 浏览器上,右键点击 inspect,查看 Network 模块下的 JS 标签,

图片
图片

这时再次切换查询日期,便会在 JS 标签左侧面板里找到真正的请求 URL(如 http://query.sse.com.cn/marketdata/tradedata/queryTopMktValByPage.do?&jsonCallBack=jsonpCallback12925&isPagination=true&searchDate=2021-01-01&_=1610296018800),可见请求 URL 需要我们配置以下的参数:

  • jsonCallBack:测试后不传入也不影响
  • isPagination:true
  • searchDate:查询日期
  • _:时间戳,不传入也不影响

点击请求 URL 后可以通过右侧面板的 Preview、Response 标签帮助我们查看该条请求是不是有爬虫想要的数据返回结果中。

图片
图片

四、数据抓取

Requests 库对其进行抓取,Requests 库是 Python 最简单易用的 HTTP 库,我们可以通过它来构建 URL 的请求,并获取其 response 结果。

一般来说,要构建一个 HTTP 请求,需要传入请求头(header),请求地址,请求方法(GET 或 POST 等)和 HTTP 协议版本。另外,根据前面的网站分析,我们还需要给 URL 传入参数,Requests 库提供了 params 关键字参数,允许我们以一个字典来配置 URL 所需的参数。

代码语言:javascript
复制
import requests
params = params = {
    "isPagination": "true",
    "searchDate": "2021-01-11"
}

headers = {
    "Referer": "http://www.sse.com.cn/market/stockdata/marketvalue/",
    "Accept-Encoding": "gzip, deflate",
    "Connection": "keep-alive",
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"
}

url="http://query.sse.com.cn/marketdata/tradedata/queryTopMktValByPage.do"

response = requests.get(url, headers = headers, params = params)

print(response.text)

最后 response.text 的输出结果是一个嵌套式的 JSON 串,我们想要的市值、排名等数据便藏在 result 那里

图片
图片

接着,配合正则表达式对 response.text 的输出结果截取出目标数据

代码语言:javascript
复制
# 接上
import re
text = response.text
result = re.search('"result":\[(.*?)\]', text).group(1)
temp = {}
stock_info = re.findall('"market":"(.*?)",.*?"productA":"(.*?)",.*?"productName":"(.*?)",.*?"rank":(.*?)\}', result, re.DOTALL)


f = open(file_path + '/stock_history_market_value.csv', 'a+', newline = '')
print('正在写入:', trade_date)
writer = csv.DictWriter(f, ['year', 'trade_date', 'code', 'stock_name', 'market_value', 'rank'])

for info in stock_info:
    temp = {
        "year": 2021,
        "trade_date": "2021-01-11",
        "code": info[1],
        "stock_name": info[2],
        "market_value": info[0],
        "rank": info[3]
    }
    print(temp)
    writer.writerow(temp)
print('已完成', trade_date)

执行完成后就会发现程序目录多了一个文件 stock_history_market_value.csv

由于动态排行图需要用到历年的数据,需要有必要将上面写入的 csv 的步骤封装到 spider_market_value函数中,以便复用。考虑到数据量的问题,这里只对历年(2000 年起)每个月的最后一天的数据进行抓取,另外,同样对该执行命令封装到函数中,方便传参执行。

代码语言:javascript
复制
def get_monthly_market_value(year):
# 如果参数是本年,则取本月前每个月取最后一天的市值排名,本月则取脚本时间的前一天的市值排名
    if year == datetime.date.today().year:
        this_month = datetime.date.today().month
        for month in range(1, this_month+1):
            if month == datetime.date.today().month:
                trade_date = (datetime.date.today() - timedelta(days=1)).strftime('%Y-%m-%d')
                spider_market_value(year, trade_date)
            else:
                trade_date = str(year) + '-' + str(month) + '-' + str(calendar.monthrange(year, month)[1])
                spider_market_value(year, trade_date)
    # 如果参数为历年,则取每个月最后一天的市值排名
    else:
    for month in range(1, 13):        trade_date = str(year) + '-' + str(month) + '-' + str(calendar.monthrange(year, month)[1])
        spider_market_value(year, trade_date)

给 get_monthly_market_value(year) 传入年份,便可抓取到对应年份每个月的数据,并汇总写入到 stock_history_market_value.csv 文件中。

图片
图片

这样,数据部分就准备好了。

五、绘图可视化

在生成动态图之前,先查阅下所用的库与函数的用法,本文将以经典可视化库 matplotlib 里的 animation.FuncAnimation 为例,调用前需了解该方法的参数,以便确认下一步的准备工作。

从官网文档可以查看到 animation.FuncAnimation 主要参数说明:

  1. fig - 传入画布对象,可以通过 fig, ax = plt.subplots() 创建;
  2. func - 每一帧更新时所调用的(绘图)函数(如下方要新建的 draw_barchart() 函数)
  3. frames - func 函数的参数,作为帧序列,靠它图例才会动态变化
代码语言:javascript
复制
# 给每一个股票随机一种颜色
random.seed(444)
get_colors = lambda n: list(map(lambda i:"#" +"%06x" % random.randint(0x111111, 0xffffff),range(n)))
colors = get_colors(df['code'].nunique())

codecolors = dict()
uni_code = set(df['code'])
for code, color in zip(uni_code, colors):
codecolors[code] = color


def draw_barchart(trade_date):
    plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
    plt.rcParams['animation.embed_limit'] = 2**128
    
    # 读取当天的数据
    df_date = df[df['trade_date'] == trade_date]
    df_date = df_date.sort_values(by = ['market_value'], ascending = True)
    
    # 每次绘制前必须先清空画布,不然图像会重叠的
    ax.clear()
    
    # 绘制水平柱状图
   ax.barh(df_date['stock_name'].astype(str), df_date['market_value'], color = [codecolors[c] for c in df_date['code']])
    
    # 标记文案
    dx = df_date['market_value'].max()/200
    for i, (value, code) in enumerate(zip(df_date['market_value'], df_date['stock_name'].astype(str))):
       ax.text(value-dx, i, code, size = 14, weight = 600, ha = 'right', va = 'bottom')
        ax.text(value+dx, i, f'{value:,.0f}', size = 14, ha = 'left', va = 'bottom')
    
    # 标记帧日期
   ax.text(1, 0.45, trade_date.split('-')[0] + '-' + trade_date.split('-')[1], transform = ax.transAxes, color = '#777777', size = 46, ha = 'right')
    
    # 标记轴标签
    ax.text(0, 1.06, "市值(万元)", transform = ax.transAxes, size = 12, color = '#777777')
    
    # 设置 X 轴坐标的位置为顶部
    ax.xaxis.set_ticks_position('top')
    
    #设置 X 轴坐标的颜色和字体大小
    ax.tick_params(axis = 'x', color = '#777777')
    ax.xaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
    
    # 设置图形与边框的距离
    ax.margins(0, 0.01)
    ax.grid(which = 'major', axis = 'x', linestyle = '-')
    ax.set_axisbelow(True)
    
    # 设置标题
    ax.text(0.3, 1.05, '历年市值前10股票', transform = ax.transAxes, size = 48, weight = 600, ha = 'left')
    
    # 去掉边框
    plt.box(False)

fig, ax = plt.subplots(figsize=(22, 10))
animator = animation.FuncAnimation(fig, draw_barchart, frames = trade_date_list, interval = 125)
HTML(animator.to_jshtml())

将 draw_barchart() 作为数据更新函数,月份作为 frames 帧序列,执行上面的语句,稍等片刻,文章开头的动态排行图便出来了:

图片
图片

动画的流畅程度除取决于 FuncAnimation 的 iterval 参数(用于设置换帧的时间间隔),也取决于每帧数据的差距,差距越小,按帧播放时就越顺滑,原理跟皮影戏一样,因此,如果要想获得更顺滑的动画,可以考虑下按日或按周抓取目标数据,当然到时要处理的数据量也就越大,运行时间和性能问题也是需要考虑的点,大家不妨多调试测试下。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、数据源
  • 二、数据流分析
  • 三、网站分析
  • 四、数据抓取
  • 五、绘图可视化
相关产品与服务
灰盒安全测试
腾讯知识图谱(Tencent Knowledge Graph,TKG)是一个集成图数据库、图计算引擎和图可视化分析的一站式平台。支持抽取和融合异构数据,支持千亿级节点关系的存储和计算,支持规则匹配、机器学习、图嵌入等图数据挖掘算法,拥有丰富的图数据渲染和展现的可视化方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档