尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

数据大屏实战:BeautifulSoup抓取、pymysql存储、pyecharts可视化

数据大屏实战:BeautifulSoup抓取、pymysql存储、pyecharts可视化 简介一套面向Python初学者与数据分析师的数据可视化大屏实战资料聚焦pyecharts图表、数据库连接与大屏监控中心三个核心模块。资源以完整项目源码为主线包含Pyecharts图表制作、pymysql连接示例如bartest.py、BeautifulSoup数据采集辅助代码以及监控中心大屏看板实现可直接参考改造用于实际业务数据展示。压缩包共1653个文件以py脚本为主附带pyc编译文件、txt配置、html页面及少量exe工具等整体仅9.48MB目录层次清晰便于检索。内容从环境准备到库使用均有覆盖适合希望系统学习可视化大屏搭建、快速上手Python数据展示的入门及进阶者。目前已有9802人学习下载是兼顾案例完整性与上手效率的实用合集。1. 数据大屏最容易被低估的环节是数据从哪来一块数据可视化大屏的交付效果八成不取决于图表特效而取决于数据链路是否经得起每天跑。我在项目里常用的一套组合是 pyecharts 管展示、pymysql 管存储、BeautifulSoup 管采集正好覆盖从网页抓数、落库清洗到图表渲染的完整闭环。这套组合最典型的落地场景是拉取没有开放 API 的内部系统页面或外部公开页面把页面里的指标字段抽出来按天存入 MySQL再通过 pyecharts 的图表和 grid 布局拼成一块能直接投到办公室大屏上的看板。个人开发者、运维和数据分析师都适合照这个思路搭不用引入消息队列和离线数仓一台普通服务器就能扛住日更的数据量。2. 用 BeautifulSoup 把网页数据抽出来先分清渲染方式再写解析2.1 先判断目标页面是静态渲染还是异步加载BeautifulSoup 的本质是解析 HTML 字符串它不执行页面里的 JavaScript。所以拿到一个待抓页面第一件事不是写解析代码而是确认目标数据在不在初始 HTML 里。常见做法是打开浏览器开发者工具在 Network 面板里搜某个数值如果 Response 里直接能搜到说明是服务端渲染requests就够用如果搜不到说明数据是异步加载的BeautifulSoup 就够不着了。对于异步页面还有两条路可以走。一是直接找背后的 XHR 接口在 Network 里按 Fetch/XHR 过滤找到返回 JSON 的那个请求这种接口抓下来数据更干净反而不用 BeautifulSoup 做解析。二是用 selenium 配合浏览器驱动做真实渲染但维护成本明显上了一个台阶。我给个人做大屏抓取时的建议是优先找接口接口找不到再考虑渲染方案BeautifulSoup 在静态页面里已经能覆盖大部分场景不必强行解决所有问题。提示抓取公开页面时控制请求频率间隔 1 到 3 秒不要在对业务有影响的时段密集并发。稳定的数据源比一次抓全更重要。2.2 解析 HTML 的三种手法find、select、get_text以商品列表页为例假设每个商品在一个div classitem里内部有.price和.title节点。抓取代码可以这样写import requests from bs4 import BeautifulSoup def fetch_items(url): resp requests.get(url, headers{ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) items [] for node in soup.select(div.item): title_node node.select_one(.title) price_node node.select_one(.price) if title_node is None or price_node is None: continue items.append({ title: title_node.get_text(stripTrue), price: float(price_node.get_text(stripTrue).lstrip(¥)), }) return items这段代码里有两个关键点。第一个是select和select_one它们接收 CSS 选择器比find_all按属性过滤的写法更接近前端直觉嵌套结构一多可读性差距就体现出来了。第二个是判空处理页面上偶尔会出现结构不完整的节点这一行能把脏数据挡在门外。get_text(stripTrue)值得单独说。很多人解析时直接取.text拿到的是带大量换行和空格的文本还得再写正则去清。stripTrue会把首尾空白自动去掉再配合split()就能加工销量 1.2万这类带单位的文本。常用的解析方法可以按场景选择方法适用场景示例find/find_all按标签名和属性精确匹配soup.find_all(div, class_item)select/select_one嵌套层级多用 CSS 路径定位soup.select(div.item span.price)get_text(stripTrue)提取节点文本并清理空白node.get_text(stripTrue)2.3 清洗是独立步骤不是解析的附属品抓下来得到的是一个字典列表接下来要做的类型转换和缺失值处理是我不建议和解析代码揉在一起的部分。单独一个clean()函数页面结构调整时只需要动解析层清洗逻辑完全不受影响。def clean(items): cleaned [] for item in items: price item.get(price) if price is None or price 0: continue item[title] item[title][:100] # 截断超长标题 cleaned.append(item) return cleaned把清洗拆出来本质上是让每一步出错时可定位。大屏数据源最常见的问题不是抓不到而是抓到脏数据后图表上出现明显离谱的值排查链路时一层一层看比一个大函数容易得多。3. 用 pymysql 把清洗好的数据写进 MySQL搭起增量更新的地基3.1 为什么放着 CSV 不用非要上 MySQLCSV 确实能顶一段时间pandas 读进来直接就能画图。但大屏场景藏着两个隐式需求增量更新和并发查询。CSV 每次全量覆盖历史趋势留不下来MySQL 则可以用INSERT ... ON DUPLICATE KEY UPDATE做幂等写入任务重跑不会产生垃圾行。另一层是查询灵活性pyecharts 接收的往往不是原始数据而是按天聚合的结果SQL 里一条GROUP BY就处理完了用 Python 来做要写不少循环。这里有一个常见的认知偏差认为数据量小就不需要数据库。实际上大屏的瓶颈不在数据量而在按时产出的数据和可追溯的更新记录。MySQL 的日期字段和唯一键能让每一天的数据都留痕哪天图表数值异常一条 SQL 就能查出来源。3.2 建表和连接参数要避开的坑先建一张用于存储抓取结果的表。通常的设计会带一个联合唯一键保证同一时间点的同一条记录不会被重复写入CREATE TABLE product_daily ( id INT AUTO_INCREMENT PRIMARY KEY, item_title VARCHAR(255) NOT NULL, item_price DECIMAL(10, 2) NOT NULL, crawl_date DATE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uniq_item_date (item_title, crawl_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里的uniq_item_date联合唯一键是增量更新的核心。同一天同一商品被抓两遍后面写入时要么被忽略要么走更新分支不会造成数据翻倍。varchar(255)在utf8mb4字符集下最大长度需要留意中文按 3 到 4 字节计算250 字以内的标题都没问题。pymysql 连接里有一个容易被忽略的参数cursorclass。默认返回元组取字段全靠下标代码一长根本分不清哪个值对应哪个字段。推荐改成字典游标import pymysql conn pymysql.connect( host127.0.0.1, port3306, userdashboard_user, passwordyour_password, databasedashboard, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor, autocommitFalse, connect_timeout5 )参数很多我把实际项目里用得到的信息整理成一张速查表参数推荐值说明charsetutf8mb4能存表情符号和生僻字比utf8更稳cursorclassDictCursor查询结果返回字典按字段名取值autocommitFalse显式控制事务批量写时方便回滚connect_timeout5数据库连接失败时快速报错不傻等3.3 批量写入与幂等更新一条 SQL 同时处理插入和覆盖清洗后的数据写入 MySQL用executemany一次性提交配合ON DUPLICATE KEY UPDATE实现插入与更新合并def save_items(items, cursor, crawl_date): sql INSERT INTO product_daily (item_title, item_price, crawl_date) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE item_price VALUES(item_price) cursor.executemany( sql, [(it[title], it[price], crawl_date) for it in items] )executemany批量执行避免在循环里反复execute带来的网络往返。参数通过%s占位符传给 MySQL天然规避了拼接 SQL 带来的注入风险。ON DUPLICATE KEY UPDATE在唯一键冲突时执行更新这里更新的是价格字段意味着同一天的重复抓取以最后一次价格为准而不是额外插一行。写入完成后记得显式执行conn.commit()。pymysql 默认autocommit是 False不提交的话数据只存在于连接的内存里连接关闭就丢了。这个坑我见过不止一次数据明明打印在屏幕上刷新数据库却什么都没有。到此MySQL 里已经有了按天累计的结构化数据。下一步是把这些数据从库里捞出来用 pyecharts 变成图表。需要注意查询设计依然在 SQL 层面完成——比如最近 7 天价格变化趋势最好是后端接口直接返回聚合结果而不是把全表数据拉到浏览器前端再聚合。4. pyecharts 出图从 MySQL 查询到图表配置4.1 把聚合查询写成可复用的数据接口pyecharts 的输入是 Python 列表所以第一步自然是从 MySQL 里查出能直接映射成 x 轴和 y 轴的数据。以商品均价的 7 日趋势为例def fetch_trend(conn, days7): sql SELECT crawl_date, AVG(item_price) AS avg_price, COUNT(*) AS cnt FROM product_daily WHERE crawl_date DATE_SUB(CURDATE(), INTERVAL %s DAY) GROUP BY crawl_date ORDER BY crawl_date with conn.cursor() as cursor: cursor.execute(sql, (days,)) return cursor.fetchall()返回的每一行是字典crawl_date、avg_price、cnt三个键分别对应日期、均价和抓取到的商品数。接下来把这三个字段拆成列表传给 pyecharts 的 Line 图。4.2 链式 API 与图表配置拆解pyecharts 的调用方式是链式结构add_xaxis和add_yaxis负责数据部分set_global_opts负责所有坐标轴、标题和图例配置。一个完整的最小折线图长这样from pyecharts.charts import Line from pyecharts import options as opts def make_line(records): dates [r[crawl_date] for r in records] prices [r[avg_price] for r in records] line ( Line() .add_xaxis(dates) .add_yaxis( 均价, prices, is_smoothTrue, is_symbol_showFalse ) .set_global_opts( title_optsopts.TitleOpts(title商品价格趋势), yaxis_optsopts.AxisOpts(name元), xaxis_optsopts.AxisOpts(name日期), ) ) return lineis_smooth决定折线是否使用平滑曲线is_symbol_showFalse可以隐藏数据点标记大屏上多个图表共存时密集的数据点标记会让画面显得杂乱。标题、坐标轴名称都属于全局配置set_global_opts里还可以继续加tooltip_opts和legend_opts分别控制悬浮提示框和图例的位置。4.3 grid 多图合并与双 y 轴场景大屏上几乎不会只放一张图常见布局是左侧折线、中间柱状、右侧饼图。pyecharts 用Grid把多个图表拼在同一个画布里from pyecharts.charts import Bar, Line, Grid def make_grid(line_chart, bar_chart): grid Grid() grid.add(line_chart, grid_optsopts.GridOpts(pos_left8%, pos_right55%)) grid.add(bar_chart, grid_optsopts.GridOpts(pos_left55%, pos_right8%)) return gridGrid.add里的grid_opts控制每个子图的绘制区域pos_left、pos_right决定子图占据画布的比例。常见误区是把两张图叠在一起结果坐标轴相互遮挡。正确的做法是显式错开左右边距让两个图表并排铺开。如果是同一个 x 轴下需要展示量级差异巨大的两个指标比如价格和销量可以用双 y 轴。方案是实例化两个yAxis第二个通过yaxis_optsopts.AxisOpts(name销量, positionright)指定到右侧line.set_global_opts( yaxis_optsopts.AxisOpts(name价格), ) bar.add_yaxis(销量, sales) bar.extend_axis( yaxisopts.AxisOpts(name销量, positionright) )extend_axis为图表增加一条额外的 y 轴两个指标各自绑定一条轴不会因为单位不同导致某个指标被压成一条直线。4.4 大屏图表调参的几个细节第一个是图例位置。大屏通常投在宽屏上图例放在底部会压缩图表高度legend_optsopts.LegendOpts(pos_top5%)可以把它移到顶部。第二个是坐标轴标签的密度日期数据一多横轴就会重叠大屏上尤其明显xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate30))让标签倾斜 30 度显示视觉效果比隐藏标签好得多。第三个是颜色默认主题在大屏投影上偏淡可以在add_yaxis里用itemstyle_opts覆盖颜色也可以在初始化时指定一个深色背景主题。5. 把多张图拼成整屏并加上自动刷新才算真正交付5.1 用 grid 把饼图、柱状图和折线图拼成一块大屏前面的 grid 示例只有两个子图实际大屏往往要容纳四到六张。用Page组件可以把多个Grid合并成一张完整的 HTML 页面每个 Grid 充当大屏上的一个区块。代码结构是这样的from pyecharts.components import Page page Page(layoutPage.SimplePageLayout) page.add(make_line(trend_data)) page.add(make_bar(category_data)) page.add(make_pie(share_data)) page.render(big_screen.html)Page.SimplePageLayout会按顺序纵向排列图表适合快速交付。想要左右分栏效果需要在前端用 CSS 控制每个 iframe 的宽度或者在 pyecharts 内部用多个 Grid 手动指定坐标区域。常见做法是后一种它能精确控制每张图的位置刷屏出来效果最接近商用大屏。5.2 自动刷新的实现与内存控制数据每日更新大屏却开着不刷新等于第一次交付后就不再变化。最稳妥的刷新方式不是刷新整个浏览器页面而是让图表的数据接口定时重新拉取并调用setOption更新。pyecharts 渲染出的 HTML 里已经带了图表实例可以在这个基础上增加一个 setInterval 调用async function refreshData() { const resp await fetch(/api/dashboard_data); const data await resp.json(); chart.setOption(data); } setInterval(refreshData, 300000);每 5 分钟拉一次数据接口setOption会做增量合并动画过渡平滑比整页刷新更省资源。这里有一个值得验证的点长时间运行的大屏页面如果一直在往全局变量里塞数据内存会缓慢上涨。把setOption第二个参数设为true能清掉旧数据再合并或者定期看一下浏览器的 Performance 面板观察内存曲线是否持续走高。验证整个链路是否健康可以用一个简单的检查脚本执行一次fetch_items再执行save_items最后跑fetch_trend三步连通说明从采集到展示没有断点。把这三步串成一条 Python 命令放进 crontab每天早上 9 点自动跑一遍大屏就真正变成了每天更新的业务看板而不是一次性的演示页。本文还有配套的精品资源点击获取
返回列表