尧图精选

胡润富豪榜可视化大屏:Python爬虫+Flask+ECharts全链路实战

🕒 发布时间:2026/10/2 20:15:44 📁 来源:尧图网络
做数据可视化大屏这几年我接过不少五花八门的项目但“胡润富豪榜数据可视化系统”这种题目几乎是每年高校课设、毕业设计里稳定出现的经典款。原因很简单胡润榜单本身自带话题度数据公开且结构化程度高又有年份、地域、行业、财富值等多个天然维度非常适合用来展示Python数据分析能力。你拿来练手可以串起爬虫、数据清洗、Flask后端和ECharts大屏这整条链路拿来交作业标题里“源码文档调试可视化大屏”四个关键词也足够撑起一个完整课题。这个系统的核心思路就是抓取历年胡润富豪榜的数据存进数据库然后用Python写一层接口服务最终在浏览器里以可视化大屏的形式把榜单、地域分布、行业结构、历年趋势这些信息一次性呈现出来。听起来不复杂但真正做完你会发现从数据落库到图表渲染之间坑远比你想象的多尤其是数据清洗和地图坐标这两块处理不好整个大屏看起来就是一堆失败案例。这篇文章我就基于这个项目的完整实现过程从设计拆解、数据采集、后端接口、大屏配置到调试排错把能说的细节全部过一遍。1. 项目整体设计与思路拆解1.1 为什么是“爬虫FlaskECharts”这个组合做数据可视化市面上可选路线很多用Excel做静态图表、用Power BI做动态报表、用Jupyter Notebook做分析展示都可以。但如果你要做的是一个能投屏展示、带交互效果、同时还要展示完整代码和源码结构的系统这几个方案都不太合适。我的选择是Python全家桶爬虫用requestsBeautifulSoup数据处理用pandas后端服务用Flask前端图表用ECharts。这个搭配几乎覆盖了从数据获取到页面展示的全部环节有几个明显的优势Python在数据采集和清洗环节的工具链最成熟遇到脏数据可以快速处理。Flask足够轻量单文件就能启动不需要像Django那样做一堆初始化配置方便课设和中小项目快速跑通。ECharts是浏览器端图表的事实标准文档全面图表种类多大屏效果上限很高而且不像商业图表库那样收费。整个链路只用一种编程语言调试时心智负担小出了问题也好定位。我见过有的同学为了“炫技”用DjangoDRFVuePostgreSQL那一套来做同样的东西结果光环境配置就折腾了一周。不是说那样不行只是在这个项目规模下选型应该优先考虑“跑得动、改得快、看得懂”。如果你不是要积累重量级Web开发经验老老实实用FlaskECharts就够了。1.2 系统分层与工作流设计这个系统虽然规模不大但我强烈建议按分层的思路来做而不是所有代码堆在一个文件里。分层的最大好处是每一层出问题都可以独立排查替换某一层的实现也不影响其他层。我实际落地时把系统拆成了四层数据采集层负责从公开页面抓取胡润富豪榜数据针对不同的榜单页面写解析器输出规范化后的数据结构。数据存储层把清洗好的数据写进数据库。开发环境我用SQLite部署环境换成MySQL也只需要改一行连接配置。服务接口层Flask提供若干JSON接口返回大屏需要的聚合数据比如按行业统计、按省份统计、历年总财富趋势等。大屏展示层前端一个HTML页面通过Ajax请求接口拿到数据再用ECharts渲染成图表最后排版成一个大屏页面。数据流的方向是单向的爬虫把榜单页面转换成结构化记录脚本把记录清洗并入库Flask从库里读出明细数据后聚合成接口返回前端拿到聚合好的JSON直接喂给ECharts。这个链条里最容易忽略的就是“明细”和“聚合”这两个概念的分离。接口层返回的数据应该尽量是“已经聚合好的图表数据”而不是把一堆明细数据丢给前端去自己算。让前端花大量时间去遍历明细做求和、分组一方面导致页面加载变慢另一方面也会让前端代码变得混乱难维护。正确的做法是确定大屏需要哪些图表然后照着图表需求设计接口让后端把该算的都算好前端只负责“取数据、填Option、渲染”。1.3 可视化大屏的信息架构设计大屏不是把信息越多越好地堆在一起。一张合格的大屏核心指标应该在3秒内被观看者捕捉到。我的布局通常按“核心指标总值地理分布结构占比趋势变化”四块来切分顶部区域标题、当前榜单年份、总上榜人数、总财富规模。中央偏左行业分布图通常用饼图或环形图展示互联网、房地产、新能源这些行业的上榜比例。中央主区域地图展示各省份、各城市的上榜富豪数量这是大屏的视觉重心。右侧区域TOP10富豪榜单用横向柱状图或卡片列表排列配合头像和财富值。底部区域历年上榜人数和财富总值的变化趋势用折线图或面积图。还要考虑大屏的实际展示形态。如果最后要投到电视或者LED屏幕上分辨率基准一般按1920×1080设计同时要做好缩放适配。我这里用的是ECharts自带的响应式能力加CSS的transform比例缩放把设计稿等比缩放到实际窗口尺寸简单粗暴但非常有效能保证在不同分辨率的屏幕上不出现布局错乱。2. 核心细节解析与实操要点2.1 数据采集榜单数据的获取与清洗规则胡润富豪榜的数据源一般是各类公开财经频道转载的榜单页面结构通常是标准HTML表格。数据字段要提前定清楚我最终保留的字段是年份、排名、姓名、财富值亿元、公司、行业、居住城市、居住省份、国家。有一个非常关键的坑胡润榜单在不同年份、不同页面上展示的单位和格式并不统一。有的页面单位是美元有的是人民币有的财富值写成“3450”有的写成“3,450”有的带货币符号有的不带。如果不做单位统一和格式清理后面的图表就全是错的。我的清洗流程固定为三步第一步用pandas读取HTML表格或用BeautifulSoup定位table节点按行解析把原始数据先变成DataFrame。第二步做字段清理去空格、去除货币符号和千分位逗号、把字符串类型的数字转为浮点型、统一统一货币单位为人民币亿元。第三步做逻辑校验过滤掉财富值为空、姓名为空的行同时按年份去重避免同一人同一年重复出现。这里我提前做过样本检查——先把页面下载到本地打印前20行看一眼再写解析逻辑。这个习惯帮我省了很多反复试错的时间。不要一上来就对着网页结构写选择器页面结构经常会变先拿到静态HTML再处理是最稳妥的。2.2 数据库设计与业务指标定义数据库表结构我用得最多的是这样一张表字段刻意做了冗余设计CREATE TABLE wealth_ranking ( id INTEGER PRIMARY KEY AUTOINCREMENT, year INTEGER NOT NULL, rank INTEGER NOT NULL, name TEXT NOT NULL, wealth REAL NOT NULL, company TEXT, industry TEXT, city TEXT, province TEXT, country TEXT DEFAULT 中国, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );为什么要同时存city和province因为地图展示可能需要按省份聚合行业分析需要按行业聚合如果只存城市每次按省份统计都要先做一次城市到省份的映射既慢又容易出错。直接在入库时就把省份和城市两个字段都填好聚合查询可以一条SQL搞定前端取数也方便。还有一个细节是财富值的单位。既然设计为“亿元”建议所有年份都统一成人民币亿元并在表注释中写清楚单位。因为后续做历年趋势对比时如果某一年是美元单位曲线就会出现一个断崖看起来像经济崩了其实是单位换算问题。业务指标定义上我重点做这几个维度总上榜人数、总财富规模、平均财富值、行业TOP数量、省份上榜人数排名、TOP10富豪的财富占比。这些指标都通过接口层动态计算不在前端写死方便后续年份数据更新后自动刷新。2.3 可视化方案ECharts组件选型与联动设计图表选型有一个底层逻辑不要为了“酷炫”去乱用图表类型不要在一个大屏里塞超过7个图表。我自己的经验是地图用散点图地图注册来展示城市分布行业占比用环形饼图TOP10富豪用横向柱状图因为名字本身是长文本横排比竖排更容易阅读历年趋势用平滑面积图视觉上比较稳。地图是这个项目里最容易出效果的组件也是最容易踩坑的组件。ECharts从5.x开始不再内置中国地图数据需要自己准备GeoJSON然后调用echarts.registerMap(china, geoJson)完成注册。我在网上推荐用阿里云DataV的GeoJSON或者直接使用echarts-map等开源仓库提供的json文件文件不大但非常关键。自己手写经纬度坐标这种事千万不要干误差会把你的地图变成艺术品。联动设计方面我实现了一个简单的筛选联动点击饼图里的某个行业右侧的TOP10榜单和地图高亮立即切换为该行业的数据。实现原理是给饼图加click事件监听拿到行业名称后重新发起接口请求更新榜单和地图的option。这里不用做太复杂的双向联动一个方向的联动就足够体现交互价值。3. 实操过程与核心环节实现3.1 环境准备与工程结构我的建议是Python版本直接用3.10或3.11不要用太老的版本。项目依赖控制在很小的范围里能用标准库就尽量用标准库减少环境问题。实际使用的依赖如下flask3.0.0 pandas2.1.4 requests2.31.0 beautifulsoup44.12.2 flask-cors4.0.0工程目录我按这样的结构组织hurun-dashboard/ ├── app.py # Flask主服务 ├── requirements.txt # 依赖清单 ├── data/ │ ├── crawler.py # 爬虫脚本 │ ├── clean.py # 数据清洗脚本 │ └── hurun.db # SQLite数据库文件 ├── static/ │ ├── js/ │ │ ├── dashboard.js # 大屏逻辑 │ │ └── china.json # 中国地图GeoJSON │ ├── css/ │ │ └── style.css │ └── index.html # 可视化大屏页面 └── templates/ └── index.html # Flask模板也可以直接放static数据库文件放在data目录下大屏页面和JavaScript放在static目录下Flask默认从static目录加载静态资源这个路径不需要额外配置但要注意模板渲染时引用路径别写错。3.2 核心代码从爬虫到大屏接口爬虫部分我用requests抓取页面后先用BeautifulSoup定位表格的行节点。以某个常见的榜单页结构为例核心循环大概长这样import requests from bs4 import BeautifulSoup import pandas as pd url https://example.com/hurun-list headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)} resp requests.get(url, headersheaders, timeout10) soup BeautifulSoup(resp.text, html.parser) rows [] for tr in soup.select(table tr): tds tr.find_all(td) if len(tds) 6: continue row { rank: tds[0].text.strip(), name: tds[1].text.strip(), wealth: tds[2].text.strip(), company: tds[3].text.strip(), industry: tds[4].text.strip(), city: tds[5].text.strip(), } rows.append(row) df pd.DataFrame(rows) df.to_csv(raw_data.csv, indexFalse)注意这里我用len(tds) 6跳过无意义行这是解析表格时很实用的小技巧避免把表头行、空行当作数据混进来。实际页面结构千差万别你要先打印一下tds的数量和文本内容再调整下标不要照抄。清洗脚本里我把“脏处理”集中在一起一段一轮import pandas as pd df pd.read_csv(raw_data.csv) df[wealth] df[wealth].str.replace(,, ).str.replace(亿元, ) df[wealth] pd.to_numeric(df[wealth], errorscoerce) df df.dropna(subset[wealth, name]) df[province] df[city].map(city_to_province_mapping) df df.drop_duplicates(subset[year, name], keeplast) df.to_csv(cleaned_data.csv, indexFalse)单位统一这里用的是字符串替换加pd.to_numeric强制转换如果发现某个值转换后为空说明原始文本里还有隐藏字符需要回头检查。省份映射这里我用了一个字典表把常见城市映射到省份只做一级映射就够用了因为大屏地图以省份为聚合单位。Flask接口部分我把大屏需要的四个接口一次性设计好from flask import Flask, jsonify import sqlite3 app Flask(__name__) def query_db(sql, params()): conn sqlite3.connect(data/hurun.db) cur conn.cursor() cur.execute(sql, params) rows cur.fetchall() cols [desc[0] for desc in cur.description] conn.close() return [dict(zip(cols, row)) for row in rows] app.route(/api/summary) def summary(): sql SELECT COUNT(*) AS total_people, SUM(wealth) AS total_wealth FROM wealth_ranking return jsonify(query_db(sql)[0]) app.route(/api/industry) def industry(): sql SELECT industry, COUNT(*) AS num, SUM(wealth) AS total_wealth FROM wealth_ranking GROUP BY industry ORDER BY num DESC LIMIT 12 return jsonify(query_db(sql)) app.route(/api/trend) def trend(): sql SELECT year, COUNT(*) AS num, SUM(wealth) AS total_wealth FROM wealth_ranking GROUP BY year ORDER BY year return jsonify(query_db(sql)) app.route(/api/rank) def rank(): sql SELECT name, wealth, company FROM wealth_ranking WHERE year(SELECT MAX(year) FROM wealth_ranking) ORDER BY wealth DESC LIMIT 10 return jsonify(query_db(sql)) if __name__ __main__: app.run(debugTrue, host0.0.0.0, port5000)这几个接口的返回结构直接对应前端图表的data字段前端拿到数据以后几乎不需要再加工。这也是我在前面强调的“接口为图表服务”后端做聚合既能让接口响应时间稳定也方便在浏览器的Network面板直接检查每个接口返回的数据是否合理。大屏前端我用一个dashboard.js统一管理所有ECharts实例加载方式用Ajax并行向后端要数据然后逐个初始化图表。核心的ECharts配置举一个横向柱状图的例子fetch(/api/rank) .then(res res.json()) .then(data { const names data.map(item item.name); const values data.map(item item.wealth); rankChart.setOption({ color: [#f6c343], tooltip: { trigger: axis }, grid: { left: 90, right: 30, top: 20, bottom: 20 }, xAxis: { type: value, name: 财富(亿元) }, yAxis: { type: category, data: names.reverse() }, series: [{ type: bar, data: values.reverse(), label: { show: true, position: right } }] }); });这里有一个细节ECharts的category轴默认从下往上排如果想让第一名显示在最上方需要把数据reverse。这个我不止一次见过有人写完之后图表顺序反了然后一脸懵地到处找问题。3.3 调试与运行大屏系统从启动到展示的完整流程本地运行的时候先启动Flask服务python app.py看到Running on http://0.0.0.0:5000输出后浏览器访问http://127.0.0.1:5000/进入大屏页面。我调试时始终开着Flask的debugTrue模式这样改完Python代码能自动重载省掉频繁手动重启的麻烦。大屏页面的调试重心在浏览器的开发者工具里。F12打开Network面板刷新页面后能看到前端发出的几个接口请求逐个点开重点看返回JSON的字段名和类型是否与前端代码里预期的完全一致。字段名大小写、数组嵌套层级、数字类型这三样是最容易出问题的地方。前端ECharts不显示或显示空白时先看Console有没有报错再看Network里的接口有没有报错这两步能过滤掉八成的问题。最后如果要部署到服务器或给老师现场演示不要用debugTrue改成app.run(host0.0.0.0, port80, debugFalse)同时在云服务器的安全组里放行80端口。如果使用Nginx反向代理要注意把/api路径转发给Flask端口静态资源由Nginx直接指向static目录。4. 常见问题与排查技巧实录4.1 数据抓取阶段最容易翻车的几个点爬虫这一层最大的坑是页面结构变化和反爬。页面结构变化无解只能说解析逻辑写得尽量“鲁棒”比如优先按表格行而不是按绝对XPath定位至少能在小改动时存活。反爬方面设置User-Agent和Referer是基本操作请求频率控制在每秒1次以内不要并发地去打一个普通榜单页面这是对目标网站的尊重。还有一个很隐蔽的坑字符编码。部分历史榜单页面用的是GBK或GB2312编码直接用resp.text会得到一堆乱码。处理方法是在requests响应后显式指定编码resp.encoding gbk或者用resp.apparent_encoding自动判断。如果页面里混着多个编码优先尝试gbk解码再回退到utf-8。4.2 可视化大屏显示效果异常的处理清单大屏图表最常见的显示问题我用排查顺序列一下遇到问题从第一项往下查现象第一排查点处理方式图表完全空白容器高度是否设了ECharts容器必须有明确的height值如600pxbody高度无效图表出来了但数据为0接口是否正常返回直接在浏览器访问接口地址看返回JSON是否有数据地图空白或只显示中国名GeoJSON是否注册成功echarts.registerMap必须在setOption之前执行且路径不能错数据太多导致卡顿是否有密集散点/柱子开启dataZoom或后端接口做limit限制图表字体太小/布局错乱分辨率适配问题用transform: scale做整体缩放而不是逐个调图表尺寸接口跨域报错CORS配置安装flask-cors并初始化:CORS(app)地图这块我再多说一句GeoJSON文件要确保是真的中国地图边界数据不要弄到省级行政区划缺失或坐标翻转的版本。加载后先在页面里单独测试一下china-maps的注册是否生效再接入数据。4.3 调试综合技巧思路比工具更重要调试这个项目我发现最有用的调试工具其实不是断点而是“打印一切”和“拆解单步验证”。爬虫阶段每抓一页就打印前3行确认结构没问题再全量跑清洗阶段每处理一个字段就输出一批样本值确认转换结果符合预期接口阶段直接用浏览器访问URL看JSON前端阶段在setOption之前console.log一下数据源。如果确实要用断点PyCharm的Flask调试和VSCode的launch.json配置都够用。启动Flask调试时要确保debug模式关闭否则断点可能不会命中。另外日常跑日志写到文件里是个好习惯比如爬虫抓取失败时记录URL和失败原因清洗时记录过滤掉的行数这样出了问题复盘很快。我在实际调试中碰到过一次很典型的问题某年榜单里有大量富豪的“居住城市”字段是空值导致省份聚合结果比实际少了近三分之一地图颜色分布看起来完全不对。排查过程就是打印清洗后的数据发现有几百行province字段为None。后来我在清洗阶段加了一条兜底规则如果city为空且country不是中国就标记为“海外”如果是中国城市但没有映射到省份则归入“未知”。这样至少大屏上不会出现数据“神秘消失”的情况。最后再分享一点项目心得这个项目从爬虫到上线我最大的感受是做数据可视化系统七成精力其实花在了数据和接口上真正写ECharts配置只占了一小部分。很多人一开始就奔着图表去结果数据没洗干净接口设计不合理最后大屏怎么调都像“假的”。建议你把顺序颠倒过来先花足够时间看数据、清洗数据再设计好接口字段最后一口气做前端会顺畅很多。有一个实用小技巧开发时预留一个“数据更新”按钮点击后重新触发爬虫并且刷新缓存。虽然当前系统只是静态数据展示但加上这个按钮后后续每年榜单更新时你只需要点一下就能生成新的大屏数据不用手动改数据库这个细节在答辩或演示时非常加分。至于扩展方向可以在这个基础上加入财富变化趋势动画——通过年份滑块动态展示富豪排序变化或者引入多个年份对比一个企业的排名沉浮。核心逻辑都是在现有接口基础上增加维度和参数并不需要推翻整体架构。这个项目做完对数据可视化这条链路的理解会比单纯调ECharts深得多。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →