去年我接到一个很有意思的课程设计题目:基于Python的携程网数据可视化分析。说白了就是从携程网上抓酒店、景点或者机票的公开数据,用Python做清洗和分析,最后用图表把结论直观地展示出来。这个项目做完之后,我觉得它特别适合用来练手——爬虫、数据处理、可视化三个Python最常用的方向全占了,而且数据源真实、分析结果能落地,不像很多教程里用的假数据那样脱离实际。
这篇博文就把我完整做下来的过程、踩过的坑、以及各环节的取舍逻辑都写清楚。不管你是刚学Python想找项目练手,还是做毕业设计需要参考,或者是想了解一套完整的数据分析流程,这篇内容应该都能帮到你。
1. 项目整体设计与技术选型
1.1 核心需求解析:这个项目到底在做什么
拿到这个题目,第一个要弄明白的是:数据分析可视化这四个字,拆开来看其实是三件事。
第一件事是数据获取。携程网有海量的公开页面,包含酒店价格、评分、点评数量、位置区域、景点热度等信息。这些数据散落在网页的HTML结构里,需要通过爬虫程序去抓取。第二件事是数据加工。抓下来的原始数据往往是脏的——价格带货币符号、评分是字符串、有的字段直接缺失,必须清洗成规整的结构化数据才能用。第三件事是可视化呈现。清洗完的数据要回答几个问题,比如价格分布呈现什么规律、评分和价格有没有相关性、哪个区域的酒店最集中,这些结论需要用图表直观地呈现出来。
目标明确之后,整个项目的技术路线也就清晰了:requests做请求、lxml或BeautifulSoup做解析、pandas做清洗、pyecharts或matplotlib做可视化。这套组合是目前最主流的数据分析流程,社区生态成熟,出了问题几乎都能搜到解决方案。
1.2 技术选型:为什么用这套组合而不选别的
项目里最核心的选型决策有三个,这里展开讲讲我当时的考虑。
爬虫框架到底要不要用Scrapy。 很多教程一上来就推荐Scrapy,但我最后选择了requests + BeautifulSoup的组合。原因很简单:这个项目的目标是数据分析和可视化,爬虫只是前置环节。Scrapy是完整的爬虫框架,功能强大但也更重,需要写middleware、配置settings、跑spider项目,学习成本高。而携程酒店列表页和详情页的结构相对规整,requests加解析库完全能搞定,代码量少、调试直观。如果你想系统学爬虫,Scrapy是后面的事,但在这个项目里,别为了用框架而用框架。
requests库的生命周期问题。 我记得这个项目做完时requests库已经出了好几个大版本,后来又有人问我为什么不用httpx或者aiohttp做异步并发。这个我在后面会专门讲,这里先给出结论:项目初始设计用同步requests完全够用,数据量在千条级别时,同步抓取的耗时是可以接受的。异步并发会引入复杂性,对新手不友好,而且容易触发反爬。
可视化工具选pyecharts还是matplotlib。 这是一个两难的问题。matplotlib是Python可视化的老牌库,功能全面、文档最多、生成的图表是静态图片,适合论文和报告。pyecharts是百度ECharts的Python封装,生成的图表是交互式HTML页面,鼠标悬停能看到数值,可以缩放、拖拽,观感明显更现代。考虑到这个项目带“可视化分析”这四个字,最终呈现效果的直观性很重要,我选择了pyecharts作为主可视化工具,个别场景(比如评分分布直方图)配合matplotlib使用。
这里有个经验供参考:如果项目面向学术论文,优先matplotlib,因为是矢量图,印刷清晰;如果面向展示演示或个人博客,pyecharts的交互效果更出彩。两个都装也不冲突,必要时可以混用。
1.3 项目目录结构规划
动手写代码之前,我先把项目目录规划好。很多新手拿到项目就写一个main.py写到底,爬到一半发现代码混乱到改不下去了。合理的目录结构应该按职责拆分:
text复制ctrip-analysis/
├── spider/
│ ├── __init__.py
│ ├── hotel_spider.py # 爬虫主逻辑
│ ├── parser.py # 页面解析函数
│ └── user_agents.py # 请求头池
├── data/
│ ├── raw/ # 原始HTML或JSON文件
│ └── cleaned/ # 清洗后的CSV数据
├── analysis/
│ ├── __init__.py
│ ├── clean.py # 数据清洗模块
│ └── stats.py # 统计分析模块
├── charts/
│ └── generate.py # 可视化生成脚本
├── output/ # 图表输出目录
├── requirements.txt
└── config.py # 全局配置(URL、参数等)
这个结构的好处是每个文件职责单一,爬虫挂了不影响清洗和可视化环节,数据可以缓存下来反复使用,不用每次都重新爬。我实际开发中体会很深的一点是:数据一旦爬到本地,后续的所有调试都不需要再发请求,既避免了对目标网站的频繁访问,也能在离线状态下安心调图表样式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据采集:携程网爬虫从入门到实战
2.1 确定采集目标与分析字段
动手爬之前,先想清楚爬什么。我这次选择的是携程酒店数据,采集字段包括:
- 酒店名称
- 所属区域(行政区/商圈)
- 最低价格
- 综合评分
- 点评数量
- 酒店地址
- 星级/档次标签
为什么选酒店而不选机票或景点?因为酒店数据的采集和清洗难度适中,字段多为文本和数字,适合做多维分析。机票数据价格波动大、字段依赖出发地和日期组合,对爬虫和清洗逻辑要求更高,容易打击新手信心。
2.2 请求头的完整配置与反爬应对
携程的页面有基础的反爬措施,直接裸requests访问大概率拿到的是验证页面。实战中,最关键的是把请求头伪装成真实浏览器。我的请求头配置如下:
python复制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",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Referer": "https://hotels.ctrip.com/"
}
随手写一个User-Agent不是不行,但你如果你仔细观察浏览器开发者工具里发出的请求,会发现还有很多细节值得关注。我建议打开浏览器的开发者工具(F12),切到Network面板,刷新页面,然后找到第一个HTML请求,把Request Headers里的字段原样复制下来,做成Python字典。
这里有个关键点:Accept-Encoding字段不要照搬。浏览器请求会自动带上gzip压缩头,但如果你忘了让requests自动解压,返回的内容会是一堆乱码。requests其实会自动处理gzip解压,所以这个字段可以直接删掉,或者保留但放心交给库去处理。
2.3 两种数据提取方式的对比与取舍
拿到页面HTML之后,下一步就是从HTML里抽数据。我在项目中尝试了BeautifulSoup和XPath两种方式,都跑通了。
BeautifulSoup的写法规整且对新手友好,定位元素的方式非常直观:
python复制from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "lxml")
hotel_names = soup.select("div.hotel-item div.name a")
prices = soup.select("div.hotel-price span.price-num")
用起来确实舒服,但遇到层级嵌套很深的页面,一连串div的CSS选择器看着有点乱。后来我换成了XPath,用lxml库处理:
python复制from lxml import etree
tree = etree.HTML(html)
names = tree.xpath('//div[contains(@class, "hotel-item")]//a[contains(@class, "name")]/text()')
prices = tree.xpath('//div[contains(@class, "hotel-price")]//span[contains(@class, "price-num")]/text()')
XPath的好处是可以通过文本内容和属性特征来模糊定位,写起来更灵活,而且执行速度快。当然它对语法熟练度有要求,XPath写错一个小标点就容易提取空列表。
我的建议是:两个库都装上都学,实际项目中挑自己顺手的用。如果页面是标准的JSON数据嵌在script标签里,那直接正则在script里抽取比HTML解析快得多。
2.4 数据存储:CSV和JSON怎么选
数据抓到之后需要落盘,我同时用了CSV和JSON两种格式,分别应对不同场景。
python复制import csv
import json
# 保存为CSV,适合表格化数据分析
with open("data/raw/hotels_raw.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=["name", "district", "price", "score", "comment_count", "address", "star"])
writer.writeheader()
writer.writerows(all_hotels)
# 保存为JSON,适合保留嵌套结构
with open("data/raw/hotels_raw.json", "w", encoding="utf-8") as f:
json.dump(all_hotels, f, ensure_ascii=False, indent=2)
这里有个关键的编码坑:写入CSV用utf-8-sig而不是utf-8。因为Excel打开UTF-8编码的CSV时会出现中文乱码,utf-8-sig会在文件开头加一个BOM标记,Excel能正确识别编码。这是我实际踩过的坑,只用utf-8保存后发给别人,对方打开一片乱码,尴尬得不行。
2.5 多页数据抓取与频率控制
列表页一般有几十页,翻页机制通常是URL里的参数控制,比如/hotels/list?city=2&page=1这种。我写了个简单的分页循环:
python复制for page in range(1, 31):
url = f"https://hotels.ctrip.com/hotels/list?city=2&page={page}"
resp = requests.get(url, headers=headers, timeout=10)
if resp.status_code != 200:
break
hotel_data = parse_page(resp.text)
all_hotels.extend(hotel_data)
# 关键:每页之间停顿,避免请求频率过高
time.sleep(random.uniform(2, 5))
注意这个random.uniform(2, 5),随机延时2到5秒。为什么要随机?因为固定间隔的请求模式更容易被识别为爬虫程序。随机延时加上随机User-Agent切换,能有效降低被反爬的概率。我当时还准备了一个User-Agent池:
python复制user_agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1",
]
每次请求前随机选一个:
python复制import random
headers["User-Agent"] = random.choice(user_agents)
爬虫的核心道德和效率原则是:抓取频率要让人觉得你是一个正常用户在浏览。一味追求快反而容易触发验证码,最后什么都拿不到。
3. 数据清洗与预处理实战
3.1 脏数据从哪来:爬虫数据常见质量问题
数据爬下来不代表能直接用,我在清洗阶段发现的问题有这些:
- 价格字段是"¥538起"这种格式,数字和单位混在一起
- 评分字段偶尔缺失,有些酒店没有用户点评,显示"暂无"
- 同一个商圈在不同页面的写法不一致,比如"外滩"和"外滩/黄浦江"其实是一个地方
- 点评数量有时是"2356条"有时是"1.2万条",单位不统一
- 地址字段里有大量无意义的换行符和空格
这些问题如果不处理,后续统计结果会完全失真。比如1.2万条点评的字条串没法直接计算平均值,缺失的评分如果不处理,均值会被拉低。
3.2 数据清洗完整代码与原理
清洗的核心处理流程分成四步:缺失值处理、格式化转换、文本规范化、去重。我封装成一个模块:
python复制import pandas as pd
import re
import numpy as np
def clean_hotel_data(df):
# 1. 去掉完全重复的行
df = df.drop_duplicates(subset=["name", "address"])
# 2. 价格清洗:提取数字部分,过滤掉异常低价
def clean_price(val):
match = re.search(r"\d+", str(val))
if match:
price = int(match.group())
return price if price >= 50 else np.nan
return np.nan
df["price"] = df["price"].apply(clean_price)
# 3. 评分清洗:把字符串转浮点,缺失值用中位数填充
def clean_score(val):
try:
score = float(val)
return score if 0 < score <= 5 else np.nan
except (ValueError, TypeError):
return np.nan
df["score"] = df["score"].apply(clean_score)
median_score = df["score"].median()
df["score"] = df["score"].fillna(median_score)
# 4. 点评数量规范:'1.2万' → 12000
def clean_comment_count(val):
text = str(val).replace("条", "")
if "万" in text:
return int(float(text.replace("万", "")) * 10000)
match = re.search(r"\d+", text)
return int(match.group()) if match else 0
df["comment_count"] = df["comment_count"].apply(clean_comment_count)
return df
清洗逻辑背后的原理值得细说。
价格为什么过滤掉小于50的值?因为携程有些活动价或者展示图的价格非常低,比如9块9这种,这类异常值会严重扭曲价格均值和分布,不像是正常酒店价格,我直接置为缺失再剔除。
评分为什么用中位数填充而不是平均值?因为评分分布是偏态的,大部分集中在4分以上,少数低分会拉低均值,中位数对异常值不敏感,填出来的数值更接近整体水平。
3.3 乱码问题的系统处理
中文乱码是数据处理里绕不开的坎。爬虫拿到的网页如果没指定编码,requests会去猜,猜错了就是乱码。
python复制resp = requests.get(url, headers=headers)
resp.encoding = resp.apparent_encoding
apparent_encoding是requests根据页面内容自动检测的编码,大多数情况下能解决问题。但检测不一定百分百准确,更稳妥的方式是直接在headers里声明:
python复制resp.encoding = "utf-8"
因为携程的页面是UTF-8编码的,显式指定后就不会乱。实际项目中还可以观察HTML源码里的<meta charset="...">标签来确定正确的编码。
写CSV时的乱码问题前面已经提过,用utf-8-sig。读CSV的时候也有讲究:
python复制df = pd.read_csv("data/cleaned/hotels_clean.csv", encoding="utf-8-sig")
如果你收到的CSV文件已经乱码了,可以试试用gbk或gb18030编码去读,这是Windows环境常见的编码方式。
3.4 结构化数据统计分析
清洗完成之后,数据长这样:
| 字段 | 示例 | 类型 |
|---|---|---|
| name | 上海外滩XX酒店 | str |
| district | 外滩 | str |
| price | 688 | int |
| score | 4.6 | float |
| comment_count | 2300 | int |
| address | 黄浦区中山东一路XX号 | str |
| star | 五星级 | str |
接下来做基本的描述性统计,先对整个数据集有个全局认识:
python复制print("总酒店数量:", len(df))
print("价格分布:")
print(df["price"].describe())
print("评分分布:")
print(df["score"].describe())
print("商圈分布Top10:")
print(df["district"].value_counts().head(10))
对,就是这简单的几行,能让你快速发现数据里的问题。比如如果价格最大值是10万,那说明有极端异常值需要排查;如果某个商圈的酒店数量特别少,那做区域对比时就要小心结论的可靠性。
4. 数据可视化:从图表到洞察
4.1 价格分布分析:读懂数据的分布形态
可视化不是把图表画出来就完了,重点是能从图里读出结论。我第一个做的是酒店价格分布直方图,用matplotlib:
python复制import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"] # 解决中文乱码
plt.rcParams["axes.unicode_minus"] = False # 解决负号显示异常
fig, ax = plt.subplots(figsize=(10, 6))
ax.hist(df["price"], bins=50, color="#4C72B0", alpha=0.8, edgecolor="white")
ax.set_xlabel("价格(元)")
ax.set_ylabel("酒店数量")
ax.set_title("酒店价格分布直方图")
plt.tight_layout()
plt.savefig("output/price_distribution.png", dpi=200)
plt.show()
图形画出来后,能直观看到价格分布是明显右偏的——大量酒店集中在200-800元区间,高价酒店数量急剧减少。这说明该城市的酒店供给以中端商务型为主,豪华酒店虽然单价高但数量有限。
4.2 评分与价格关系分析:两个维度的关联
这个分析回答的是一个酒店行业里常被讨论的问题:价格高的酒店评分一定高吗?
散点图是最直观的呈现方式:
python复制from pyecharts.charts import Scatter
from pyecharts import options as opts
scatter = (
Scatter()
.add_xaxis(df["price"].tolist())
.add_yaxis("酒店评分", df["score"].tolist(), label_opts=opts.LabelOpts(is_show=False))
.set_global_opts(
title_opts=opts.TitleOpts(title="酒店价格与评分关系"),
xaxis_opts=opts.AxisOpts(name="价格(元)", type_="value"),
yaxis_opts=opts.AxisOpts(name="评分", type_="value", min_=3.5, max_=5),
)
)
scatter.render("output/price_score_scatter.html")
实际结果挺有意思:绝大多数酒店的评分落在4.0到4.8之间,和价格没有明显的线性关系——800元的酒店评分不一定比300元的高。这说明在携程体系下,评分更多反映的是“性价比”而非“绝对品质”,价格贵的酒店往往因为用户预期高,评分反而容易被拉低。
如果你追求更严谨,可以做一个分组统计:把价格分成几个区间,计算每个区间的平均评分,用柱状图展示。这种分析方式能把数据里的隐藏关系挖得更深。
4.3 区域分布分析:画热力图找聚落
分析酒店的区域分布时,pyecharts的地理图表表达能力远超matplotlib。我用pyecharts的Map或者Bar展示各区域的酒店数量和平均价格。
python复制from pyecharts.charts import Bar
district_data = df.groupby("district").agg(
酒店数量=("name", "count"),
平均价格=("price", "mean")
).sort_values("酒店数量", ascending=False).head(15)
bar = (
Bar()
.add_xaxis(district_data.index.tolist())
.add_yaxis("酒店数量", district_data["酒店数量"].tolist())
.set_global_opts(
title_opts=opts.TitleOpts(title="各商圈酒店数量Top15"),
xaxis_opts=opts.AxisOpts(name="商圈", axislabel_opts=opts.LabelOpts(rotate=30)),
yaxis_opts=opts.AxisOpts(name="数量"),
)
)
bar.render("output/district_hotel_count.html")
柱状图已经很直观,但如果你要把酒店落在地图上,pyecharts有Map类型。不过使用Map需要先准备好区域名称和数值的映射,地图里的行政区名称必须和携程页面的叫法完全一致,否则数据挂不上去。我当时不得不用一个映射字典做翻译。
4.4 交互式仪表盘:把多个图表集成到一个页面
项目做到后面,我发现每次改完数据都要重新打开几个HTML,不太方便。于是用pyecharts的Page类把所有图表拼合成一个仪表盘页面:
python复制from pyecharts.charts import Page, Bar, Line, Scatter
from pyecharts.components import Table
page = Page(layout=Page.SimplePageLayout)
page.add(price_hist)
page.add(price_score_scatter)
page.add(district_bar)
page.add(comment_table)
page.render("output/dashboard.html")
打开这个HTML文件,所有分析结果在一个页面里分块展示,滚动就能浏览完整报告。这个设计在答辩和汇报时特别加分——不用切换窗口,一份文件讲完全部内容。
5. 常见问题与排查技巧实录
5.1 请求被拒绝或返回验证页面
这是爬虫项目最常遇到的问题,表现是status_code 200但返回的HTML里没有酒店数据,取而代之的是滑块验证或验证码页面。
排查思路和对应解法如下:
- 检查User-Agent是否真实,不要用常见的Python默认UA
- 补全Referer和Accept字段,模拟从携程首页点击进入的访问路径
- 降低请求频率,把延时从2秒提高到5秒以上
- 使用Session会话保持Cookie:
python复制session = requests.Session() session.headers.update(headers) # 先访问首页获取默认Cookie session.get("https://hotels.ctrip.com/", timeout=10) # 再请求列表页 resp = session.get(list_url, timeout=10) - 如果仍然被拦截,换一个时段或者换一个IP网络再试
我实际测试下来,Session配合随机UA的效果最好,多数情况下能绕开反爬。
5.2 pyecharts图表中文乱码
pyecharts生成的HTML里中文是正常的,因为它用的是JavaScript字体渲染,不依赖系统字体。乱码问题主要出现在matplotlib里。
matplotlib的中文乱码有两类:一类是图中文字变成方块,说明系统没有SimHei字体或者matplotlib找不到;另一类是负号显示不出来,变成小方块。
解决方法是对症下药:
python复制import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "KaiTi"]
plt.rcParams["axes.unicode_minus"] = False
如果你在Linux服务器上运行,还需要先安装中文字体,比如:
bash复制apt-get install -y fonts-wqy-microhei
然后删掉matplotlib的字体缓存重新生成:
bash复制rm -rf ~/.cache/matplotlib
5.3 数据处理时的内存和性能问题
如果抓取的数据量很大,比如几万条酒店数据加十几万条评论,pandas处理时可能会有卡顿。几个优化经验:
- 读取CSV时指定只用到的列:
df = pd.read_csv("hotels.csv", usecols=["name", "price", "score"]) - 做groupby聚合时避免对字符串列操作,先过滤再聚合
- 数据量超过内存时用
chunksize分块读取
python复制chunk_iter = pd.read_csv("hotels.csv", chunksize=10000)
result = pd.concat([chunk[chunk["price"] > 100] for chunk in chunk_iter])
至于要不要用多进程爬取,我的教训是:先确认同步抓取够不够用。我之前心血来潮用了multiprocessing池子开8个进程去并发抓取,结果抓了没多久IP就被限制了,反而得不偿失。数据量不大的情况下,老老实实带延时抓完,比什么都快。
5.4 XPath提取结果为空
这是新手最容易卡住的地方。XPath表达式在Chrome开发者工具里测试能取到值,放Python里却返回空列表。原因通常是text()匹配的位置不对。
比如一个元素的结构是:
html复制<div class="price">
<span>¥</span>
538
<span>起</span>
</div>
如果用//div[@class="price"]/text(),确实取到了538,但如果HTML源码里价格外还有一层嵌套,或者标签写法不同,表达式就要调整。稳妥的做法是取整个div的字符串内容再做正则提取:
python复制text = tree.xpath('//div[contains(@class, "hotel-price")]//text()')
price_text = "".join(text).strip()
price = re.search(r"\d+", price_text).group()
这里我建议:不要过度依赖XPath取精确文本,直接取元素区域内的纯文本再正则清洗,往往更稳定。HTML结构一改,精确的XPath链就容易失效,区域文本提取则要健壮得多。
5.5 数据可视化表达不清
最后说一个很容易被忽略的问题:数据画出来了,但图画得让人看不懂。我有几个原则分享:
- 柱状图的分类轴如果类别名太长,一定旋转角度,不然标签会互相遮挡
- 饼图的分类超过6个就不要用饼图了,扇形太碎看不清,改用横向条形图
- 做对比分析时保证纵轴范围一致,否则会误导读者
- 颜色用同一色系的渐变,不要五颜六色堆在一起
图表是给读者看的,不是展示你用了多少种颜色。
6. 项目总结与后续扩展思路
6.1 项目整体回顾
整套流程走下来,我用Python完整实现了从网页数据采集、清洗整理到多维可视化展示的闭环。核心代码量在800行左右,不算多,但每个环节都踩到了典型的坑,做完之后对Python数据生态的掌握确实上了一个台阶。
这个项目给我最大的收获不是技术本身,而是建立了一种“数据闭环”的思维方式:先想清楚要回答什么问题,再设计需要什么数据,然后才有爬虫和清洗的方向。很多人做数据项目容易本末倒置,先爬了一堆数据,最后却不知道拿这些数据干什么。我是先确定了分析目标——酒店价格分布、评分与价格关系、区域分布特征,然后反推需要哪些字段,这样每一步都有目标感。
6.2 项目后续可以扩展的方向
做完整套项目后,其实还有不少可以继续深挖的空间。
第一个方向是扩大数据范围。酒店只是携程数据的一个维度,景点门票、机票价格、餐饮评价都可以用同样的爬虫框架去抓取,最后做一个跨维度的综合分析。
第二个方向是加入时间维度。酒店价格会随节假日、旺季淡季波动,如果定期采集数据(比如每天抓一次),就能做时序分析和趋势预测。这块可以结合schedule库做定时任务,用time series相关模型做预测。
第三个方向是机器学习建模。基于酒店的价格、评分、评论数量、位置、星级这些特征,可以训练一个预测模型,预测一个酒店的评分区间或者合理价格区间。携程的海量真实数据天然适合这种练习。
第四个方向是产品化。把分析结果做成一个网页应用,用Flask或Streamlit搭一个简单的交互界面,用户可以选城市、选区域,动态生成对应的分析图表。Streamlit尤其适合这个场景,几行代码就能把一个数据分析脚本变成网页应用,我之前试过体验很好。
这些扩展方向不用全做,挑一个感兴趣的去深入就够了。数据分析的项目不怕小,怕的是没有完整的闭环。能把一个环节扎扎实实做透,比浮光掠影做十个半成品要强得多。
