Python数据可视化大屏实战:BeautifulSoup采集、MySQL存储与pyecharts渲染全链路
2026/9/20 19:34:46 网站建设 项目流程

简介:面向 Python 初学者、数据分析师及有志从事数据分析工作的人群,这份资料围绕 Python 数据可视化大屏展开,提供从数据采集、数据库管理到图表展示与看板集成的完整案例。资源内含 Pyecharts 图表示例、连接 MySQL 数据库的 Python 脚本、以及大屏看板监控中心的实现代码,综合运用 pyecharts、pymysql、BeautifulSoup、operator 等库,方便读者对照学习爬虫入库、数据查询与可视化联动的开发思路。压缩包共 1653 个文件,约 9.48MB,以 Python 源码、编译缓存、头文件、文本说明、可执行程序、网页模板及配置类文件为主,整体目录结构清晰,适合直接参考改造;较多数量的源码与脚本也便于按模块挑选复用,快速定位图表配置、数据库连接或页面布局等关键片段。目前已有 9802 人学习下载,配套代码和模板能够帮助读者快速搭建自己的可视化大屏项目,尤其适合用于毕业设计、项目演示或企业监控中心原型验证。

1. 数据可视化大屏最容易翻车的不是图表,是数据链路

把数据渲染成网页图表只是数据可视化大屏最直观的一环。真正会翻车的地方,是数据从哪来、怎么存、怎么在刷新时不卡、图表之间怎么对齐。标题里的三个库——pyecharts、pymysql、BeautifulSoup——恰好分别回答三件事:网页数据采集、MySQL落库、ECharts图表渲染。这三层各管一段,串联起来才是一个能回答「昨天数据是多少」「今天为什么变了」「这个数字的口径是什么」的完整方案。本文面向已经写过 Python 脚本、想自己搭一套数据可视化大屏的工程师,默认你熟悉 Python 基础语法,但不要求你懂前端和 ECharts 配置。文章会按采集、存库、可视化、整合部署、排障的顺序走,每步都给可执行的代码和参数说明。

2. 用 requests + BeautifulSoup 做网页数据采集,先搞清楚页面是「真静态」还是「假静态」

2.1 判断目标页面渲染方式的 3 个快速方法

BeautifulSoup 只能解析 HTML 字符串,它不执行 JavaScript。拿到一个目标页面,我一般先花 30 秒确认页面是否服务端渲染。方法有三个:

  1. 浏览器右键查看网页源代码,搜索正文关键词是否直接出现在 HTML 里。如果出现,说明是服务端渲染,requests 直接抓就能拿到数据。
  2. 如果源代码里没有,但页面显示出来了,说明是 JavaScript 异步加载,BeautifulSoup 抓不到。这时需要找接口地址,或者改用 selenium/playwright。
  3. 打开浏览器开发者工具 Network 面板,刷新页面后找 XHR 和 Fetch 请求,看返回的是不是 JSON。很多所谓「爬不到」的站点只是数据藏在接口里。

对于第三种情况,常见做法是直接在 Python 里请求那个接口,拿到 JSON 再用 json 模块解析,而不是硬用 BeautifulSoup。但标题既然给了 BeautifulSoup,说明目标场景是服务端渲染页面或者混合渲染中的静态部分。所以上面三个方法里的前两个是主要路径。

2.2 最小爬虫模板:requests 拿 HTML,BeautifulSoup 解析表格

用 pyecharts 做大屏,最常见的数据源是报表类页面,数据嵌在 table 标签里。以下代码适合抓取这类表格数据,并落到 DataFrame 里,方便下一步写库:

import requests from bs4 import BeautifulSoup import pandas as pd import time from tqdm import tqdm HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } def fetch_table(url): resp = requests.get(url, headers=HEADERS, timeout=10) resp.raise_for_status() resp.encoding = resp.apparent_encoding # 避免中文乱码 soup = BeautifulSoup(resp.text, "html.parser") table = soup.find("table") # 找第一个表格,可按 id/class 精确指定 rows = table.find_all("tr") data = [] for row in rows: cells = row.find_all(["th", "td"]) data.append([cell.get_text(strip=True) for cell in cells]) return data data = fetch_table("https://example.com/report") df = pd.DataFrame(data[1:], columns=data[0]) # 第一行做表头 print(df.head())

逻辑说明:resp.apparent_encoding是从响应内容推断编码,很多国产站点页面声明的是 utf-8 但实际是 gbk,这行能减少乱码。find_all(["th", "td"])是同时匹配表头和单元格标签,避免漏列。get_text(strip=True)去掉单元格里的空白字符和换行。

参数说明:timeout=10是连接和读取超时,单位秒,建议根据目标站点响应速度调整到 5~15 秒。HEADERS里的 User-Agent 必须设置,很多站点对无 UA 的请求直接返回 403。tqdm不是必须的,但如果抓的是多页列表页,建议配合循环使用进度条,方便观察是否卡住。

2.3 多页翻页采集时,URL 规律和 sleep 间隔是两件必做的事

如果大屏数据来自列表页翻页,比如每页 20 条、共 50 页,常规写法是构造页码 URL 并循环请求:

def crawl_pages(base_url, total_pages): all_rows = [] for page in tqdm(range(1, total_pages + 1)): url = f"{base_url}?page={page}" # 根据实际站点的 URL 规律调整 try: page_data = fetch_table(url) all_rows.extend(page_data[1:]) # 去掉每个子页的表头 except Exception as exc: print(f"第{page}页失败: {exc}") continue time.sleep(1) # 控制请求频率,避免被封 IP return all_rows

这里最容易踩的坑有两个。第一个是 URL 规律不是简单的?page=,有的站点用/list/2.html,有的用 POST 请求带页号参数。先手动翻两页比较 URL 差异再写代码,不要凭空猜。第二个是 sleep 间隔,time.sleep(1)对中小站点够用;如果目标站点有反爬策略,用随机间隔time.sleep(random.uniform(0.5, 1.5))更稳妥。如果返回 403,先检查 UA 和 Cookie 是否缺失,而不是盲目加大 sleep 时间。

3. pymysql 写库:表结构设计决定了大屏查询快还是慢

3.1 建表时就把去重、索引、时间分区想清楚

pymysql 是纯 Python 实现的 MySQL 客户端驱动,相比 mysql-connector-python,它更轻量,性能在中小数据量下没有明显差异。选了 pymysql 后,第一件事不是写插入语句,而是设计表结构。大屏页面上的每个指标,最终都是select sum/count/avg where 时间范围之类的聚合查询。如果表设计不合理,图表加载会从秒级拖到分钟级。

一个通用的数据明细表设计如下:

CREATE TABLE IF NOT EXISTS report_daily ( id INT AUTO_INCREMENT PRIMARY KEY, stat_date DATE NOT NULL COMMENT '统计日期', category VARCHAR(64) NOT NULL COMMENT '分类维度', metric_name VARCHAR(64) NOT NULL COMMENT '指标名', metric_value DECIMAL(12, 2) NOT NULL COMMENT '指标值', data_source VARCHAR(32) DEFAULT 'portal' COMMENT '来源标识', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_date_cat (stat_date, category, metric_name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='大屏日报数据表';

逻辑说明:唯一索引uk_date_cat是去重的核心。爬虫跑重了,同一日期、同一分类、同一指标名的数据不会插入两次。这样在可视化端做聚合时,不需要额外的distinct操作,索引也有利于按日期范围过滤。

参数说明:DECIMAL(12, 2)适合金额、百分比等需要精确计算的数值,不要用 FLOAT,二进制浮点会在求和时产生精度误差。utf8mb4是必须的,utf8在 MySQL 里最多存 3 字节字符,遇到 emoji 或生僻字会报错。ENGINE=InnoDB保证事务和行级锁,虽然爬虫写入场景并发不高,但 InnoDB 的崩溃恢复能力比 MyISAM 可靠得多。

3.2 使用 INSERT IGNORE 实现幂等写入,避免主键冲突报错

写库时最常用的去重写法是INSERT IGNOREON DUPLICATE KEY UPDATE。两者的区别在于:INSERT IGNORE遇到唯一键冲突时直接跳过该行,适合只追加不改历史数据的场景;ON DUPLICATE KEY UPDATE会在冲突时更新指定字段,适合需要修正历史数据的场景。大屏数据通常只追加,用前者更安全:

import pymysql conn = pymysql.connect( host="127.0.0.1", port=3306, user="root", password="your_password", database="dashboard_db", charset="utf8mb4", autocommit=False ) insert_sql = """ INSERT IGNORE INTO report_daily (stat_date, category, metric_name, metric_value, data_source) VALUES (%s, %s, %s, %s, %s) """ data = [ ("2025-01-10", "华东", "订单量", 1234.00, "portal"), ("2025-01-10", "华北", "订单量", 987.00, "portal"), ] cursor = conn.cursor() cursor.executemany(insert_sql, data) conn.commit() print(f"影响行数: {cursor.rowcount}") cursor.close() conn.close()

逻辑说明:executemany是批量插入,比循环执行execute快一个数量级。影响行数rowcount打印的是实际插入的行数,被IGNORE跳过的行不会算进去,所以可以用来核对本次新写入的数据量是否和预期一致。

参数说明:autocommit=False是刻意关闭自动提交。批量插入时手动commit,如果中途出异常可以rollback保证数据完整。charset="utf8mb4"必须和表定义保持一致,否则中文写入可能乱码。cursor.rowcount在 MySQL 的 Python 驱动里,executemany返回的是受影响行数总和,不要用它判断是否重复。判断重复应该先执行select count(*)或直接看INSERT IGNORE的跳过行为。

3.3 连接池和超时:写给每天定时跑的任务

爬虫任务通常是定时执行,比如每天凌晨跑一次。脚本每跑一次就新建连接的写法在小数据量下没问题,但如果采集量大、写入频繁,反复建立 MySQL 连接会占用服务端线程资源。一个稳妥的做法是使用dbutils.PooledDB做连接池:

from dbutils.pooled_db import PooledDB pool = PooledDB( creator=pymysql, maxconnections=10, mincached=2, blocking=True, host="127.0.0.1", port=3306, user="root", password="your_password", database="dashboard_db", charset="utf8mb4" ) conn = pool.connection() cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM report_daily") print(cursor.fetchone()) cursor.close() conn.close() # 归还连接而不是关闭

逻辑说明:PooledDB维护一组连接,conn.close()在连接池模式下是归还连接,不是真的断开。blocking=True表示连接池满时,请求会排队等待而不是报错,适合定时任务这种并发不高的场景。

参数说明:maxconnections=10表示池中最多 10 个连接,mincached=2表示启动时预创建 2 个空闲连接。这两个值要根据 MySQL 的max_connections配置调整,一般脚本场景 5~10 就够了,设置太大会浪费数据库连接资源。注意dbutils库在新版 Python 里的包名是dbutils.pooled_db,旧写法DBUtils.PooledDB只兼容旧版本,安装时用pip install DBUtils即可。

4. pyecharts 生成大屏图表:从单图到多图 Grid 布局

4.1 先用面积图、柱状图、饼图把指标映射到图表类型

数据落到 MySQL 后,接下来就是用 pyecharts 把聚合结果渲染成图表。pyecharts 的底层是 ECharts,它的优势是生成 HTML 文件或嵌入 Web 框架,不需要前端工程师参与。选图时有一条经验规则:趋势看折线或面积图、排名看柱状图、占比看饼图、地理分布看地图。大屏上最常见的布局是左上排名柱状图、中间趋势线图、右侧占比饼图。

一个从 MySQL 读数据然后渲染柱状图的例子:

from pyecharts.charts import Bar from pyecharts import options as opts import pymysql conn = pymysql.connect(host="127.0.0.1", user="root", password="your_password", database="dashboard_db", charset="utf8mb4") cursor = conn.cursor() cursor.execute("SELECT category, SUM(metric_value) FROM report_daily WHERE stat_date = '2025-01-10' GROUP BY category ORDER BY SUM(metric_value) DESC LIMIT 10") rows = cursor.fetchall() bar = ( Bar() .add_xaxis([row[0] for row in rows]) .add_yaxis("指标值", [float(row[1]) for row in rows]) .set_global_opts( title_opts=opts.TitleOpts(title="各省份指标排名"), toolbox_opts=opts.ToolboxOpts(), ) ) bar.render("bar_chart.html")

逻辑说明:set_global_opts控制标题、图例、提示框等全局配置。toolbox_opts默认带有导出图片、数据视图、刷新等按钮,预览时有用,但上生产大屏前建议删掉,避免用户点开看到底层数据。

参数说明:LIMIT 10配合ORDER BY取前 10 名,这里在 SQL 层做排序而不是在 Python 里排序,更高效。float(row[1])转成 float,因为 DECIMAL 字段在 pymysql 中返回的是 Decimal 对象,直接传给 pyecharts 可能序列化报错。

4.2 Grid 拼接多个图表,解决「一屏多个图」的经典需求

热词里反复出现「pyecharts grid 多个」,说明一屏展示多个图表是刚需。pyecharts 的Grid组件用于在同一个 HTML 页面里放置多个独立图表,每个图表有独立的坐标轴和布局区域。和Page组件的区别在于:Page是简单地上下排列多个图表,每个图表保持独立高度;Grid则是真正的网格布局,适合大屏拼接。以下代码演示用 Grid 把折线图和柱状图放在同一屏:

from pyecharts.charts import Bar, Line, Grid from pyecharts import options as opts bar = ( Bar() .add_xaxis(["周一", "周二", "周三", "周四", "周五", "周六", "周日"]) .add_yaxis("销售额", [120, 200, 150, 80, 70, 110, 130]) .set_global_opts(title_opts=opts.TitleOpts(title="销售额趋势")) ) line = ( Line() .add_xaxis(["周一", "周二", "周三", "周四", "周五", "周六", "周日"]) .add_yaxis("订单量", [12, 20, 15, 8, 7, 11, 13]) .set_global_opts(title_opts=opts.TitleOpts(title="订单量趋势")) ) grid = ( Grid(init_opts=opts.InitOpts(width="1200px", height="600px")) .add(bar, grid_opts=opts.GridOpts(pos_top="10%", pos_left="5%", pos_right="55%")) .add(line, grid_opts=opts.GridOpts(pos_top="50%", pos_left="5%", pos_right="55%")) ) grid.render("grid_chart.html")

逻辑说明:Grid.add()里的GridOpts决定了每个子图在画布中的位置。pos_toppos_leftpos_right是相对画布的百分比坐标。上面代码把两个图表分别放在上方和下方,各占一半区域。init_opts里的 width 和 height 控制整个画布的大小,不能省略,否则多个图表会互相挤压变形。

参数说明:pos_top="10%"表示子图上边缘距画布顶部 10% 的位置,pos_right="55%"表示子图右边缘距画布左侧 55% 的位置,相当于在横向上留出空间给其他图表。百分比是相对画布的,不是相对父容器,需要反复调试。调试技巧是先把pos_*都设成整数百分比如 "10%"、"30%"、"50%",看效果再微调,不要一上来就填 "12.5%" 这种精度值。

4.3 set_global_opts 里的 6 个必调参数

大屏的视觉表达依赖全局配置,常见做法是把以下 6 个参数都显式设置一遍,避免用默认样式:

  1. title_opts控制标题位置和内容;大屏多图时建议副标题写数据更新时间。
  2. tooltip_optstrigger="axis"适用于柱状图、折线图,鼠标滑过时显示横轴分组数据;trigger="item"适用于饼图、地图。
  3. legend_opts控制图例的位置,大屏右侧或底部。
  4. datazoom_opts对长时序数据很关键,type_="slider"会在图表底部加一个可拖动的缩略轴,不需要时坚决删除。
  5. xaxis_optsyaxis_opts里设axislabel_opts=opts.LabelOpts(rotate=30),横坐标文本太长时可以旋转避免重叠,这个细节对应热词中「python画图横坐标太密集」的场景。
  6. toolbox_opts在开发阶段开启,上线前关闭。
.set_global_opts( title_opts=opts.TitleOpts(title="经营日报", subtitle="更新时间: 2025-01-11 08:00"), tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="shadow"), legend_opts=opts.LegendOpts(pos_top="5%", pos_right="10%"), datazoom_opts=[opts.DataZoomOpts(type_="inside")], xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=30)), yaxis_opts=opts.AxisOpts(name="单位: 万元"), )

参数说明:datazoom_opts=[opts.DataZoomOpts(type_="inside")]type_="inside"是鼠标滚轮缩放,type_="slider"是底部滑块。两者可以并存,但大屏上用 slider 更直观,因为不是所有看大屏的用户都会用鼠标滚轮。axis_pointer_type="shadow"让提示框的十字光标变成阴影柱状块,视觉上更清晰。

5. Flask + pyecharts 整合成大屏页面,用 AJAX 轮询代替整页刷新

5.1 Flask 路由如何把 pyecharts 生成的 HTML 嵌进页面

数据可视化大屏的部署形态通常是一个 Web 页面。pyecharts 的render()会生成完整的 HTML 文件,但这不是大屏的最终形态——因为数据不会每天手动重新生成页面。常规做法是写一个 Flask 应用,路由里动态获取 MySQL 数据、生成图表 JSON 或 HTML 片段,再交给前端模板渲染。

一个最基本的结构如下:

from flask import Flask, render_template from pyecharts.charts import Bar from pyecharts import options as opts import pymysql app = Flask(__name__) def fetch_data(): conn = pymysql.connect(host="127.0.0.1", user="root", password="your_password", database="dashboard_db", charset="utf8mb4") cursor = conn.cursor() cursor.execute("SELECT category, SUM(metric_value) FROM report_daily WHERE stat_date = CURDATE() GROUP BY category") rows = cursor.fetchall() conn.close() return rows @app.route("/") def dashboard(): rows = fetch_data() bar = ( Bar() .add_xaxis([r[0] for r in rows]) .add_yaxis("指标值", [float(r[1]) for r in rows]) ) chart_js = bar.dump_options_with_quotes() # 生成 ECharts 可用的 JSON 配置 return render_template("dashboard.html", chart_js=chart_js) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)

逻辑说明:dump_options_with_quotes()是 pyecharts 提供的方法,把图表配置导出为标准 JSON 字符串。前端拿到这个 JSON 直接赋给echarts.init().setOption(),比在后端把整个 HTML 传过去灵活得多,也是前后端解耦的常见做法。

参数说明:CURDATE()是 MySQL 的当前日期函数,直接替代 Python 侧传参。如果大屏展示的是 T-1 数据,用DATE_SUB(CURDATE(), INTERVAL 1 DAY)host="0.0.0.0"是让 Flask 监听所有网卡地址,方便局域网内其他设备访问大屏页面。

5.2 前端模板:setInterval 30 秒刷新一次,而不是刷新整个页面

大屏页面需要定时更新数据。最常见的错误做法是setInterval(() => location.reload(), 30000),整页刷新会导致画面闪烁、JavaScript 状态丢失,体验非常差。正确做法是前端定时请求后端接口拿最新数据,然后用setOption更新图表。

前端模板templates/dashboard.html如下:

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>数据大屏</title> <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> </head> <body> <div id="barChart" style="width: 600px; height: 400px;"></div> <script> let chart = echarts.init(document.getElementById("barChart")); function loadData() { fetch("/api/chart-data") .then(res => res.json()) .then(data => { chart.setOption({ xAxis: { type: "category", data: data.categories }, yAxis: { type: "value" }, series: [{ type: "bar", data: data.values }] }); }) .catch(err => console.error("数据加载失败:", err)); } loadData(); setInterval(loadData, 30000); </script> </body> </html>

对应的 Flask 接口:

from flask import jsonify @app.route("/api/chart-data") def chart_data(): rows = fetch_data() return jsonify({ "categories": [r[0] for r in rows], "values": [float(r[1]) for r in rows] })

逻辑说明:setOption在第二次调用时会合并配置,而不是覆盖整个图表,所以折线图和柱状图的数据更新不需要重新初始化。fetch返回的 Promise 里先res.json()解析响应体,再更新series.datasetInterval的 30000 毫秒意味着接口每 30 秒被调用一次,如果 MySQL 查询本身耗时超过 30 秒,浏览器会积累大量未完成的请求,后端评估不过来的话,可以用setTimeout在请求完成后重新计时。

参数说明:jsonify是 Flask 提供的 JSON 响应函数,比直接json.dumps安全,它会自动设置 Content-Type 为application/json。前端 CDN 地址用的 echarts@5,与 pyecharts 1.x 的内部版本匹配。如果部署环境离线,需要下载 echarts.min.js 到本地 static 目录,不要生产环境依赖 CDN。

5.3 nginx 反向代理大屏时的 3 个超时参数

大屏服务跑在 Flask 开发服务器上只能用于内部测试,正式部署通常用 gunicorn 启动 Flask,再用 nginx 做反向代理。如果页面图表多、单次查询慢,nginx 默认 60 秒的超时时间可能不够。以下配置中,3 个超时参数需要特别关注:

server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:5000; proxy_connect_timeout 10s; proxy_read_timeout 120s; proxy_send_timeout 120s; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

逻辑说明:proxy_connect_timeout是 nginx 和后端建立 TCP 连接的超时,一般 5~10 秒足够;proxy_read_timeout是等待后端返回响应的超时,大屏场景下后端可能要查多个聚合查询,如果超过 120 秒建议优化 SQL 而不是调大超时。proxy_set_header Host $host保证后端拿到的是原始域名,而不是 127.0.0.1:5000。

参数说明:gunicorn 启动命令建议gunicorn -w 4 -b 127.0.0.1:5000 app:app-w 4是 4 个 worker 进程。大屏场景下 worker 数不是越多越好,MySQL 连接数是瓶颈,建议 worker 数乘以每个 worker 的线程数不超过 MySQL 的最大连接数。可以使用gunicorn --threads 2让每个 worker 处理更多并发请求,而不是盲目加-w

6. 定时刷新任务与 6 个实用排错检查点

6.1 用 APScheduler 启动定时采集,避免 cron 和 Python 环境纠缠

爬虫采集任务如果写在脚本里然后用 crontab 定时执行,会有一个麻烦:crontab 里找不到 Python 路径或虚拟环境变量。一个更可维护的做法是直接在 Flask 应用或独立脚本里用 APScheduler 调度,将采集任务挂进来。基础用法如下:

from apscheduler.schedulers.blocking import BlockingScheduler scheduler = BlockingScheduler() @scheduler.scheduled_job("cron", hour=2, minute=30) def daily_crawl_job(): print("开始执行数据采集任务") crawl_and_store() # 内部调用 requests + BeautifulSoup + pymysql scheduler.start()

这里用 BlockingScheduler 表示调度器独占当前进程,适合单独跑采集服务。如果你不想单独部署,也可以把采集任务放到 Flask 启动时用后台线程跑。注意 macOS 和 Linux 上 APScheduler 的 cron 语法与系统 crontab 一致,hour=2, minute=30表示每天凌晨 2 点 30 分执行。

6.2 大屏打不开或数据不对时的排查顺序

采集、存储、可视化三层都有独立的问题特征,按链路排查效率最高:

现象优先检查工具/命令
页面打不开Flask 进程是否存活、端口是否监听`ps aux
图表空白前端是否拿到数据、接口返回是否 200浏览器 F12 Network 面板,看/api/chart-data响应
数据量对不上SQL 聚合条件是否正确,去重是否生效MySQLSELECT COUNT(*), COUNT(DISTINCT stat_date, category) FROM report_daily
中文乱码MySQL 表字符集是 utf8mb4,pymysql 连接 charset 一致SHOW CREATE TABLE report_daily
定时任务没跑调度器所在进程是否在运行,日志有没有输出检查 APScheduler 日志,或加print到文件
图表闪烁是否用了location.reload(),改用 setOption 更新前端代码setInterval内部是否调chart.setOption

这里要特别提醒一点:如果INSERT IGNORE跳过了重复数据,但你看得到的记录数还是变多,先检查stat_date字段是不是带时间戳的 DATETIME 类型。DATE 和 DATETIME 的去重表现不同,DATETIME 会因为时分秒不同而被当成不同记录,导致唯一索引失效。爬虫任务里如果stat_date传的是datetime.now(),本质上每一天的每一次执行都会产生新记录。

6.3 用一行 curl 验证接口响应时间

大屏卡顿的时候,先分清瓶颈在 MySQL 还是前端渲染。一个快速的验证方法是直接 curl 接口看耗时:

curl -o /dev/null -s -w "耗时: %{time_total}s\n" http://127.0.0.1:5000/api/chart-data

如果这个接口耗时超过 3 秒,问题大概率在 MySQL 查询或 Python 聚合逻辑,而不是 ECharts 渲染。此时打开 MySQL 慢查询日志:

SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1;

之后查询mysqld-slow.log,针对慢查询用EXPLAIN检查索引是否命中。大屏常用的是按日期范围聚合,务必确认stat_date上有索引。如果report_daily表的数据量超过几千万行,考虑按月分表或使用 ClickHouse 这类列式存储,那时 pymysql 就不再是合适的查询驱动了——但那是数据量到了一定规模才要考虑的问题。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询