☰
胡润富豪榜可视化大屏:Python爬虫+Flask+ECharts全链路实战
2026/10/2 20:15:43 网站建设 项目流程

做数据可视化大屏这几年,我接过不少五花八门的项目,但“胡润富豪榜数据可视化系统”这种题目,几乎是每年高校课设、毕业设计里稳定出现的经典款。原因很简单:胡润榜单本身自带话题度,数据公开且结构化程度高,又有年份、地域、行业、财富值等多个天然维度,非常适合用来展示Python数据分析能力。你拿来练手,可以串起爬虫、数据清洗、Flask后端和ECharts大屏这整条链路;拿来交作业,标题里“源码+文档+调试+可视化大屏”四个关键词也足够撑起一个完整课题。

这个系统的核心思路,就是抓取历年胡润富豪榜的数据,存进数据库,然后用Python写一层接口服务,最终在浏览器里以可视化大屏的形式把榜单、地域分布、行业结构、历年趋势这些信息一次性呈现出来。听起来不复杂,但真正做完你会发现,从数据落库到图表渲染之间,坑远比你想象的多,尤其是数据清洗和地图坐标这两块,处理不好,整个大屏看起来就是一堆失败案例。这篇文章我就基于这个项目的完整实现过程,从设计拆解、数据采集、后端接口、大屏配置到调试排错,把能说的细节全部过一遍。

1. 项目整体设计与思路拆解

1.1 为什么是“爬虫+Flask+ECharts”这个组合

做数据可视化,市面上可选路线很多:用Excel做静态图表、用Power BI做动态报表、用Jupyter Notebook做分析展示,都可以。但如果你要做的是一个能投屏展示、带交互效果、同时还要展示完整代码和源码结构的系统,这几个方案都不太合适。

我的选择是Python全家桶:爬虫用requests+BeautifulSoup,数据处理用pandas,后端服务用Flask,前端图表用ECharts。这个搭配几乎覆盖了从数据获取到页面展示的全部环节,有几个明显的优势:

  • Python在数据采集和清洗环节的工具链最成熟,遇到脏数据可以快速处理。
  • Flask足够轻量,单文件就能启动,不需要像Django那样做一堆初始化配置,方便课设和中小项目快速跑通。
  • ECharts是浏览器端图表的事实标准,文档全面,图表种类多,大屏效果上限很高,而且不像商业图表库那样收费。
  • 整个链路只用一种编程语言,调试时心智负担小,出了问题也好定位。

我见过有的同学为了“炫技”用Django+DRF+Vue+PostgreSQL那一套来做同样的东西,结果光环境配置就折腾了一周。不是说那样不行,只是在这个项目规模下,选型应该优先考虑“跑得动、改得快、看得懂”。如果你不是要积累重量级Web开发经验,老老实实用Flask+ECharts就够了。

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,不要用太老的版本。项目依赖控制在很小的范围里,能用标准库就尽量用标准库,减少环境问题。实际使用的依赖如下:

flask==3.0.0 pandas==2.1.4 requests==2.31.0 beautifulsoup4==4.12.2 flask-cors==4.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, headers=headers, timeout=10) 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", index=False)

注意这里我用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"], errors="coerce") df = df.dropna(subset=["wealth", "name"]) df["province"] = df["city"].map(city_to_province_mapping) df = df.drop_duplicates(subset=["year", "name"], keep="last") df.to_csv("cleaned_data.csv", index=False)

单位统一这里用的是字符串替换加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(debug=True, host="0.0.0.0", port=5000)

这几个接口的返回结构直接对应前端图表的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的debug=True模式,这样改完Python代码能自动重载,省掉频繁手动重启的麻烦。

大屏页面的调试重心在浏览器的开发者工具里。F12打开Network面板,刷新页面后能看到前端发出的几个接口请求,逐个点开,重点看返回JSON的字段名和类型是否与前端代码里预期的完全一致。字段名大小写、数组嵌套层级、数字类型,这三样是最容易出问题的地方。前端ECharts不显示或显示空白时,先看Console有没有报错,再看Network里的接口有没有报错,这两步能过滤掉八成的问题。

最后如果要部署到服务器或给老师现场演示,不要用debug=True,改成:

app.run(host="0.0.0.0", port=80, debug=False)

同时在云服务器的安全组里放行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值(如600px),body高度无效
图表出来了但数据为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深得多。

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

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

立即咨询