news 2026/9/14 21:29:48

基于Python Flask的新冠疫情可视化系统与ECharts大屏开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Python Flask的新冠疫情可视化系统与ECharts大屏开发

简介:一份基于Python+Flask框架实现的新冠疫情可视化项目源码,目标用户是Python初学者、期末大作业及课程设计人群,覆盖数据抓取、接口封装、ECharts动态图表和前端页面交互的完整实现链路,代码内附详细注释,下载后简单配置即可运行。压缩包共含2022个文件,大小约28.89MB;其中1061个csv数据文件支撑疫情历史数据,780个json文件管理配置与数据接口,110个js文件实现图表交互,11个Python源文件承载Flask后端逻辑,另有CSS样式、字体图标和PNG图片等前端资源,目录结构清晰,便于按模块阅读和二次开发。项目提供全国与省份维度的实时统计、趋势曲线、地图分布等可视化页面,功能完整、界面简洁,能直接作为课程设计或期末大作业的高分参考。目前已有130人学习下载,对于想快速完成同类可视化项目的读者有较高实用价值。

1. 为什么课程设计选 Python + Flask 做新冠疫情可视化,以及"高分"到底高在哪

新冠疫情可视化是课程设计里最典型的「一条链」项目:有真实数据源、有地域维度、有时间维度、有动态变化,可视化出来的效果天然能打。选 Python + Flask 而不是前后端分离,是因为课程设计的验收场景通常是一台学生电脑、一个 Flask 进程同时扛住模板渲染和数据接口,不需要另起前端服务,也不需要上数据库,CSV 足够。所谓「高分项目源码」,我建议把它当成需求清单而不是下载目标——名字里带「源码」「高分」的打包资源,往往下载回来还要花大量时间改成自己讲得清的样子。真正拉开分数差距的只有三件事:数据能否现场拉通、图表能否交互、被追问某个函数时能否讲明白每一行。下面按数据采集、Flask 接口、ECharts 对接、加分排错四段把这套方案讲透,跟着做一天能出活。

2. 疫情数据采集:requests 拉公开接口、pandas 清洗与离线 CSV 兜底

2.1 数据源选型:公开接口、页面爬虫和离线 CSV 怎么搭配

做疫情可视化第一步不是画图,是把数据拿稳。常见的有三条路:写爬虫抓疫情专题页面、调公开 JSON 接口、直接用离线 CSV 文件。页面爬虫最不建议用于课程设计——页面结构改一次你的解析就崩,答辩现场没有任何容错空间,而且不少专题页带了基础的反爬校验,调试时间会远超你的预期。

公开 JSON 接口字段全、有现成的每日时间序列和省份维度,缺点是接口随时可能关闭或改字段,半年前写的解析代码今天跑不通是常态。离线 CSV 最稳,但数据是死的,展示不出「实时拉取」这个答辩加分点。我一般用「离线 CSV 打底 + 接口定时刷新」的组合:项目里放一份已经清洗好的历史 CSV,Flask 启动时先加载它保证页面秒开,同时用 requests 去请求公开接口,成功就覆盖更新 CSV,失败就继续用旧文件并打一条日志。演示时不会因为数据源抖动白屏,被问到更新机制时又能讲清楚增量刷新的设计。

2.2 requests 拉取疫情公开接口的最小代码与响应结构

下面这段代码是接入公开接口的最小实现,核心是requests.get加参数拼接,以及把 json 响应里的ret业务码和 HTTP 状态码分开判断。接口字段以实际返回为准,接入前先打印一次结构再写解析。

import requests def fetch_covid_data(): """从公开疫情接口拉取原始 JSON,返回 dict。 注意:接口字段会随数据源调整,接入前先 print(resp.text) 看一次结构。 """ url = "https://api.inews.qq.com/newsqa/v1/query/inner/publish/modules/list" params = { "modules": "chinaDayList,chinaDayAddList,nowConfirmStatis,provinceCompare" } headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36" } resp = requests.get(url, params=params, headers=headers, timeout=10) resp.raise_for_status() # HTTP 层:4xx/5xx 直接抛异常 payload = resp.json() if payload.get("ret") != 0: # 业务层:接口自身返回错误码 raise RuntimeError(f"接口返回异常: {payload.get('msg')}") return payload["data"]

params里传的modules决定这次请求拿回哪几组数据,chinaDayList是全国每日累计序列,provinceCompare是各省最新快照,这两个是地图和折线图的核心来源。headers里的User-Agent必须有,很多接口会拒绝默认的python-requests标识。timeout=10是为了防止数据源卡住时把 Flask 启动流程阻塞住,课程设计演示现场最怕的就是页面长时间转圈。

返回的data是个 dict,key 和 modules 一一对应,字段含义大致如下表。不同时间段的接口字段名略有出入,写代码时以实际打印为准:

返回模块内容常见字段
chinaDayList全国每日时间序列date, confirm, dead, heal, nowConfirm
chinaDayAddList全国每日新增date, confirm_add
nowConfirmStatis现存确诊汇总各类统计数值
provinceCompare各省最新快照province, confirm, nowConfirm, dead, heal

2.3 pandas 清洗:拍平嵌套 JSON 并补算每日新增

接口拿到的数组通常直接用不成图表——日期是字符串、累计值和新增值混在一起、字段名也不是你前端想要的。清洗这一步我固定用 pandas,因为向量化操作比在循环里逐行 diff 快得多,更重要的是代码短,答辩时一句话能讲清。

import pandas as pd def clean_china_daily(raw_list) -> pd.DataFrame: """把接口返回的每日数组清洗成时序表。 raw_list 对应 data['chinaDayList']。 """ df = pd.DataFrame(raw_list) keep = ["date", "confirm", "suspect", "dead", "heal", "nowConfirm"] df = df[keep].copy() df["date"] = pd.to_datetime(df["date"], format="%Y.%m.%d") df = df.sort_values("date").reset_index(drop=True) df["confirm_add"] = df["confirm"].diff().fillna(0).astype(int) df["heal_add"] = df["heal"].diff().fillna(0).astype(int) return df # 清洗后落盘,utf-8-sig 保证 Excel 打开中文不乱码 # clean_china_daily(raw).to_csv("data/covid_history.csv", index=False, encoding="utf-8-sig")

接口里的 date 类似"2020.01.13",必须转成datetime类型,否则 pandas 的排序会按字符串字典序排,导致 10 月排在 2 月前面,这个错非常隐蔽。diff()计算相邻两行的差值得到每日新增,fillna(0)处理第一行没有前值的情况,astype(int)把浮点结果转回整数,因为累计字段在接口里可能是字符串或浮点。

这里有个常见的错误做法:有些人拿到chinaDayAddList就直接当新增用,但该模块在不同时期字段变化大,统一「先算累计、再 diff 出新增」的清洗策略最保险,后面前端无论要累计线还是新增柱状图都只需要同一份 DataFrame。清洗完的数据落盘成 CSV,就是整条链路的离线兜底。

3. Flask 后端路由设计:统一响应格式、缓存策略与项目目录

3.1 项目目录结构与路由规划表

Flask 项目最怕写成单文件堆砌,路由、数据逻辑、页面全部挤在 app.py 里。课程设计虽然规模小,但目录划分直接体现工程意识。下面是这套方案我常用的结构:

covid_vis/ ├── app.py # Flask 应用入口,只注册路由 ├── service/ │ └── data_service.py # 数据加载、清洗、查询封装 ├── data/ │ ├── covid_history.csv # 全国历史数据(离线兜底) │ └── province_latest.csv ├── templates/ │ ├── index.html # 可视化大屏主页 │ └── province.html # 省份详情页 └── static/ ├── js/ ├── css/ └── vendor/echarts.min.js

路由层和数据层分开之后,接口路径一目了然,也方便答辩时对着路由表讲设计。路由规划我固定用下面这一套:

路由方法返回内容数据来源
/GET渲染 index.html 大屏
/api/overviewGET全国累计确诊、治愈、死亡、现存CSV
/api/trendGET全国每日时间序列CSV
/api/provinceGET各省最新数据,用于地图CSV
/api/province/<name>GET指定省份历史趋势CSV

命名上统一用复数名词,/api/province/广东省这种路径语义清楚,不跟/api/province冲突。所有接口都走/api前缀,页面路由和接口路由分开,后面加鉴权或者换前端框架时不用大改。

3.2 核心接口实现:code/msg/data 响应与 DataService 封装

接口响应格式从前端消费角度定,统一成{code, msg, data}三段式。前端fetch之后只需要判断code === 0,不需要为每个接口单独写异常分支。

from flask import Flask, jsonify, render_template from service.data_service import DataService app = Flask(__name__) ds = DataService() # 启动时加载 CSV,进程内只初始化一次 @app.route("/") def index(): return render_template("index.html") @app.route("/api/overview") def overview(): return jsonify(code=0, msg="ok", data=ds.get_overview()) @app.route("/api/trend") def trend(): return jsonify(code=0, msg="ok", data=ds.get_trend()) @app.route("/api/province") def province(): return jsonify(code=0, msg="ok", data=ds.get_province_latest()) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=False)

DataService在模块级实例化,意味着所有请求共享同一份内存中的 DataFrame,不会每次请求都重新读 CSV。host="0.0.0.0"是为了答辩时让老师用自己手机连同一个局域网直接访问,不用挤在你的电脑前面。debug=False一定要关,Werkzeug 调试器在演示现场刷新一次终端就刷一次屏,而且开着 debug 等于暴露了远程执行入口。

DataService 里有个关键的坑:pandas 的int64类型不能直接被jsonify序列化,必须在返回前转成 Python 原生int

import pandas as pd class DataService: """统一封装 CSV 的加载和查询,所有路由只调方法不碰文件。""" def __init__(self, path="data/covid_history.csv"): try: self.df = pd.read_csv(path) except FileNotFoundError: self.df = self._fetch_and_clean_from_api() # 离线文件缺失时兜底 self.df["date"] = pd.to_datetime(self.df["date"]) def get_overview(self): row = self.df.iloc[-1] # 最后一行就是最新一天 return { "confirm": int(row["confirm"]), "heal": int(row["heal"]), "dead": int(row["dead"]), "nowConfirm": int(row["nowConfirm"]), } def get_trend(self): sub = self.df[["date", "confirm", "heal", "dead"]] return sub.to_dict(orient="records")

iloc[-1]依赖前面清洗时已经按日期排过序,to_dict(orient="records")把 DataFrame 转成[{列名: 值}]的列表结构,正好是前端 ECharts 需要的格式。遇到TypeError: Object of type int64 is not JSON serializable就检查是不是有字段忘了int()转换,这是 pandas 和 Flask 搭配时出现频率最高的报错。

3.3 flask_caching 缓存:避免每次刷新都重新打数据源

数据量小的时候不做缓存也能跑,但地图页面要拖动、要缩放,每次交互都触发后端全量重算,答辩时如果数据源接口不稳定,体验会很难看。用 flask_caching 在接口层做缓存是成本最低的优化。

from flask_caching import Cache cache = Cache(app, config={ "CACHE_TYPE": "SimpleCache", # 进程内缓存,零依赖,够用 "CACHE_DEFAULT_TIMEOUT": 300 }) @app.route("/api/province") @cache.cached(timeout=120) def province(): return jsonify(code=0, msg="ok", data=ds.get_province_latest())

@cache.cached(timeout=120)按请求路径做缓存,两分钟内重复访问同一个接口直接返回缓存结果,不重新读文件和计算。SimpleCache是进程内字典实现,单进程跑没有任何问题;如果以后拆成多进程或多实例部署,就要换成RedisCache并配置REDIS_URL。换上去之后想看缓存有没有生效,可以用 redis 可视化管理工具连上去看键列表,flask_cache_前缀的键出现就说明命中缓存了。

缓存粒度上要注意:/api/overview这种汇总数据缓存时间可以设长一点,因为一天之内累计值不会频繁变;/api/trend设 5 分钟也够。但如果你的项目做了「定时从接口拉取更新」,缓存时间必须小于更新周期,否则页面刷出来的永远是旧值,这个细节答辩时被问到会很扣分。

4. ECharts 可视化对接:地图注册、折线趋势与可视化大屏适配

4.1 图表选型:地图、折线、柱状图各放什么数据

疫情可视化最忌讳图表堆砌,每一类图都要对应一种业务问题。地图回答「哪里严重」,折线回答「趋势如何」,柱状图回答「谁排前列」,饼图回答「结构占比」。选型定下来之后,后端接口的设计也跟着定,不会出现前端想要的数据后端没给的情况。

数据视图图表类型对应接口关键配置
全国各省累计地图/api/provincemap + visualMap
全国每日趋势折线图/api/trendxAxis category + smooth
省份确诊 Top10柱状图/api/province前端排序横向柱状图
治愈/死亡占比环形饼图/api/overviewradius 百分比

我一般把地图放在大屏中央最显眼的位置,折线图放左侧,Top10 柱状图放右侧,饼图右下角。布局不用框架,CSS Grid 一行就够,关键是图表容器要用百分比宽度,为后面的适配做准备。

4.2 前后端对接:fetch 拉 /api 接口再 setOption

模板渲染和 AJAX 两种方式都可以,课程设计我推荐用fetch拉接口再setOption。模板渲染只有首次打开页面时有一次数据,图表做不了刷新和联动;AJAX 方式下后端接口可以独立测试,前端逻辑也清晰。

<div id="trendChart" style="width:100%;height:420px;"></div> <script src="/static/vendor/echarts.min.js"></script> <script> const trendChart = echarts.init(document.getElementById('trendChart')); async function renderTrend() { const res = await fetch('/api/trend'); const payload = await res.json(); if (payload.code !== 0) return; // 统一判断业务码 const rows = payload.data; trendChart.setOption({ tooltip: { trigger: 'axis' }, legend: { data: ['累计确诊', '累计治愈'] }, xAxis: { type: 'category', data: rows.map(r => r.date.slice(0, 10)) }, yAxis: { type: 'value' }, series: [ { name: '累计确诊', type: 'line', smooth: true, data: rows.map(r => r.confirm) }, { name: '累计治愈', type: 'line', smooth: true, data: rows.map(r => r.heal) } ] }); } renderTrend(); </script>

rows.map(r => r.date.slice(0, 10))是因为 CSV 里落盘的日期带时分秒,图表 x 轴只需要到天。tooltiptrigger: 'axis'保证鼠标悬停时同时显示两条线的数值,而不是单点弹框。smooth: true让折线带弧度,视觉上更像新闻报道里的趋势图。接口路径用相对路径/api/trend,这样不管 Flask 跑在 5000 还是 8080 端口,前端都不用改。

4.3 ECharts 5 中国地图注册与两个适配参数

ECharts 5 不再内置中国地图,必须手动registerMap。最省事的做法是把china.json放到static/vendor/下,页面里用 fetch 加载后注册。不带构建工具时 import 语法用不了,所以走 fetch 注册是课程设计里最不折腾的方案。

let mapChart; fetch('/static/vendor/china.json') .then(r => r.json()) .then(geo => { echarts.registerMap('china', geo); initMap(); }); function initMap() { mapChart = echarts.init(document.getElementById('mapChart')); mapChart.setOption({ visualMap: { min: 0, max: 2000, left: 20, bottom: 20, inRange: { color: ['#e8f4fd', '#d94e5d'] } // 浅到深的连续色带 }, series: [{ type: 'map', map: 'china', roam: true, label: { show: false }, data: [ { name: '湖北省', value: 67800 }, { name: '广东省', value: 2200 } ] }] }); }

registerMap必须在setOption之前执行,否则地图区域会空白,这是每次都有人踩的坑。visualMapminmax决定了颜色映射范围,max 设小了颜色全变深红,设大了看不出差异,我一般先取数据最大值再向上取整到千位。series.data里每一项的name必须是官方行政区划全名,写「湖北」不写「湖北省」就匹配不上地图区域。roam: true开启拖拽缩放,大屏展示时方便聚焦局部省份。

大屏适配两个参数必调:一是图表容器用百分比宽高,二是监听 resize 事件。很多项目图表只显示左上角一块,就是因为容器像素宽高写死,或者忘了注册 resize 回调。

function resizeAllCharts() { if (mapChart) mapChart.resize(); if (trendChart) trendChart.resize(); } window.addEventListener('resize', resizeAllCharts); // 首屏布局稳定后再执行一次,避免容器高度还没撑开就渲染 setTimeout(resizeAllCharts, 200);

resize()是 ECharts 实例方法,容器尺寸变了必须重新计算画布。setTimeout延迟 200ms 是因为页面刚加载时 CSS 布局可能还没稳定,提前 resize 会把图表初始尺寸算错。如果觉得延迟不够稳,可以把图表初始化也放到window.onload之后。

5. 三个加分功能与高频报错定位顺序

5.1 时间轴播放与图表导出:答辩现场最能抓住眼球的两个功能

基础图表做完只算及格,两个功能能明显拉开差距。第一个是时间轴播放,让地图按日期动态变化。ECharts 的timeline组件把每一天的完整option放进数组,自动播放时逐帧切换。

mapChart.setOption({ timeline: { data: dates, // 日期数组,如 ['2020-01-20', '2020-01-21', ...] autoPlay: true, playInterval: 800, // 每帧间隔毫秒 bottom: 10 }, options: dates.map(date => ({ series: [{ type: 'map', map: 'china', data: provinceDataByDate[date] // 当天各省数据数组 }] })) });

timelineoptions数组长度必须和data数组长度一致,每个元素是一个完整的图表配置项,ECharts 会自动把相邻两帧做过渡动画。provinceDataByDate需要后端额外给一个按日期索引的接口,实现就是在 DataService 里按日期筛一遍 DataFrame,成本很低但视觉效果非常抢眼。

第二个加分功能是导出图片。大屏展示时老师想看某个省份细节,直接拖拽缩放再导出,比口头描述直观得多。

function exportMap() { const url = mapChart.getDataURL({ pixelRatio: 2, // 2 倍像素,导出的图在大屏上不糊 backgroundColor: '#fff' }); const a = document.createElement('a'); a.href = url; a.download = '疫情地图.png'; a.click(); }

pixelRatio是导出清晰度的关键参数,默认 1 导出的图片在投影上会有锯齿,设成 2 就是双倍分辨率。最后再加一个隐蔽的加分点:把 CSV 缺失时自动去拉接口的兜底逻辑写进 DataService 的__init__,然后在启动日志里打印一行数据源加载方式:离线文件/在线接口,这个细节能直接证明你考虑过数据可用性。

5.2 高频报错定位顺序表

课程设计最常见的报错其实就那几类,按「前端先看控制台、后端先看终端」的顺序排查。

现象第一排查动作再往下看什么
启动报 ModuleNotFoundError确认虚拟环境已激活pip list看 flask、requests、pandas 是否安装
页面能开但图表空白F12 打开 ConsoleJS 报错、registerMap 是否在 setOption 之前
地图区域空白打印/api/province返回data 的 name 是否为行政区划全名
接口转圈超时看 Flask 终端日志数据源接口是否下线,确认走离线 CSV 兜底
图表只占页面一角检查容器 CSS父容器是否百分比宽高、resize 是否注册
jsonify 报 int64 序列化错检查 DataService 返回值是否忘了转 int

最后留半小时做一次「断网验收」:拔掉网线重启 Flask,所有图表应该基于 CSV 兜底数据正常渲染;再把 CSV 最后一行的confirm手动改掉,刷新页面确认变化生效。这一步同时验证了数据加载链路、缓存失效逻辑和前端渲染三处正确性,比答辩前临时改代码可靠得多。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 21:29:46

Cursor AI 连上 TaoToken 后能写完微信小程序页面

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 21:28:33

搞半天Python数据分析,原来numpy.array才是Pandas这尊大神的祖宗

是针对借助 NumPy 而产生的一种工具载体范畴, 此工具载体范畴是因致力于解决数据分析任务从而被创建的。它收纳了大量的库以及一些标准的数据模型, 为高效操作大型数据集提供了所需工具。它还搭建起大量能助力我们迅捷便利处理数据的函数以及方法。简洁来讲, 你能够将其视作是 …

作者头像 李华
网站建设 2026/9/14 21:28:02

AI智能体平台选型的5个硬标准:可解释性、状态持久化与私有化水位

1. 这不是选工具&#xff0c;是选“数字分身”的底层逻辑“AI智能体到底怎么选&#xff1f;”——这句话最近在技术群、产品会、甚至咖啡馆里被反复抛出来&#xff0c;像一块试金石&#xff0c;照出不同角色的真实处境&#xff1a;创业者在纠结要不要把客服流程全交给一个智能体…

作者头像 李华
网站建设 2026/9/14 21:27:57

EMC核心原理篇(五):一根导线为什么会变成天线?从电容、电感、E/H场一直讲到真正的电磁辐射

🔥 EMC核心原理篇(五):一根导线为什么会变成天线?从电容、电感、E/H场一直讲到真正的电磁辐射 做汽车电子经常会碰到一个看似很奇怪的现象: 明明只是一根12V电源线,为什么它会辐射? CAN线、USB线、屏线为什么也会变成天线? PCB上一小段走线,什么时候还是“电路”,什…

作者头像 李华
网站建设 2026/9/14 21:27:44

Axure字母分类选择器原型设计与实现

1. 字母分类选择器原型设计概述字母分类选择器是一种常见于移动端应用的交互控件&#xff0c;主要用于快速定位和筛选大量按字母排序的数据项。这种设计模式最早出现在iOS通讯录中&#xff0c;后来被广泛应用于各类需要字母索引的场景&#xff0c;如商品分类、城市选择、音乐列…

作者头像 李华