
简介一份基于Python的小红书武汉旅游数据可视化分析实战项目面向Python入门者、数据分析爱好者及旅游餐饮研究者演示如何从小红书抓取武汉热门景点与美食信息并完成数据清洗、统计分析、词云、情感分析和可视化展示。项目涵盖requests与BeautifulSoup网络爬虫、pandas数据整理、matplotlib与seaborn图表绘制适合课程设计、毕业项目或自媒体趋势分析。资源共21个文件大小约489KB包含6个Python功能脚本、2个CSV数据文件、2个HTML可视化页面、2张PNG结果图及项目配置文件等覆盖数据获取、预处理、分析和可视化全流程。目前已有554人学习下载。压缩包内提供可直接运行的代码脚本与示例数据读者可跟随分析思路快速复现图表并能迁移到其他城市或平台掌握从网络爬虫到数据可视化报告的实际技能。1. 用 Python 拆小红书的武汉旅游数据从爬虫到可视化的完整闭环用 Python 对小红书上的武汉旅行内容做数据可视化分析很多人第一反应是“把搜索结果做成几个柱状图”但真的跑完这套项目你会发现难点根本不在画图而在数据怎么拿、文本怎么洗、中文词云怎么不出方块。这套实战资源把整条链路拆好了爬虫脚本 xhs.py 负责采集武汉旅游.csv 和武汉美食.csv 是清洗后的结构化数据food_wordcloud.py、emotion.py 负责词云和情感分析最终落到 wh.png、wordcloud.html、map.png 这些可直接用于汇报的图片和页面。如果你正在做旅游餐饮方向的数据分析入门、毕业设计或者想看看小红书上武汉哪些景点和美食真正被用户反复提及这份资源能让你少走很多弯路。2. 数据获取与预处理爬虫脚本与 CSV 清洗的实操细节2.1 爬虫脚本 xhs.py 的核心逻辑与接口认知xhs.py 是这个项目的起点它的作用是抓取小红书里与武汉相关的笔记内容。常见做法是带着搜索关键词去请求小红书的搜索结果页然后解析返回的 HTML。我先给一段与项目思路一致的裁剪版代码方便你理解字段是怎么落地的import requests from bs4 import BeautifulSoup import pandas as pd import time import random def fetch_xhs_notes(keyword, pages5): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://www.xiaohongshu.com/, Cookie: 替换成你自己的有效cookie } rows [] for page in range(1, pages 1): url fhttps://www.xiaohongshu.com/search_result?keyword{keyword}page{page} resp requests.get(url, headersheaders, timeout10) soup BeautifulSoup(resp.text, lxml) # 页面中笔记卡片的 CSS 选择器以实际页面为准 for item in soup.select(.note-item): title item.select_one(.title).get_text().strip() if item.select_one(.title) else tags [a.get_text() for a in item.select(.tag a)] like_text item.select_one(.like-wrapper .count) like_count int(like_text.get_text()) if like_text else 0 link item.select_one(a).get(href) if item.select_one(a) else rows.append({ title: title, tags: |.join(tags), like_count: like_count, link: link, keyword: keyword }) time.sleep(random.uniform(1, 3)) return pd.DataFrame(rows) # 调用示例 df_wuhan fetch_xhs_notes(武汉旅游, pages3) df_wuhan.to_csv(武汉旅游.csv, indexFalse, encodingutf-8-sig)这段代码的逻辑不复杂每翻一页组装一个带 keyword 的 URL用 requests 发起 GET 请求BeautifulSoup 按 CSS 选择器提取标题、话题标签、点赞数和链接。这里有几个参数值得说明pages控制抓取深度页面太多会触发频率限制我一般控制在 3~5 页time.sleep(random.uniform(1, 3))是每个页面之间的随机等待目的是把请求频率拉低避免被服务端识别为批量访问。encodingutf-8-sig非常重要它会让 Excel 直接打开 CSV 时不乱码省掉后面一个常见的坑。但要提醒你小红书首页和搜索页是典型的动态渲染页面直接 requests 拿到的 HTML 里经常没有笔记数据。项目里这份 xhs.py 更接近教学版演示的是“请求-解析-存储”的完整思路。实际线上跑的时候我一般会改为分析它的 XHR 接口直接抓取返回的 JSON再从中提取笔记列表。这个后面避坑章节会具体说这里先建立认知。2.2 武汉旅游.csv 与武汉美食.csv 的清洗与规整抓下来的原始数据不能直接拿去做可视化。项目里的两个 CSV 就是清洗后的成果字段大致包含标题、话题标签、点赞数、评论数、链接、关键词、发布时间等。以武汉美食.csv 为例我一般会用 pandas 做下面这四步清洗import pandas as pd def clean_csv(path): df pd.read_csv(path, encodingutf-8-sig) # 1. 去掉完全重复的行 df df.drop_duplicates(subset[title, link]) # 2. 数值列统一转成数字解析失败补 0 for col in [like_count, comment_count]: df[col] pd.to_numeric(df[col], errorscoerce).fillna(0).astype(int) # 3. 过滤掉标题过短或无意义的内容 df df[df[title].str.len() 4] # 4. 将时间字段统一格式方便后续做趋势分析 if publish_time in df.columns: df[publish_time] pd.to_datetime(df[publish_time], errorscoerce) return df df_food clean_csv(武汉美食.csv) print(df_food.shape) print(df_food[title].head())drop_duplicates我用了“标题链接”双字段因为光靠标题去重会误删不同博主发的同文案内容errorscoerce能把“1.2k”这类文本转成 NaN再统一补 0避免后面画图时报错str.len() 4是为了过滤掉“好吃”“打卡”这类两字短标题它们在词频统计里不具备区分度。时间字段转 datetime 是给后续的月度趋势分析留口子这一步现在不做第 6 章就会后悔。清洗完成后武汉旅游.csv 和武汉美食.csv 就是干净的分析底稿。一张表存景点相关笔记一张表存美食相关笔记两个文件结构一致方便复用同一套可视化代码。3. 美食数据可视化词云脚本、字体参数与交互式 HTML3.1 food_wordcloud.py 与中文词云的生成流程项目里有两个词云脚本food_wordcloud.py 是基础版xhsfoodwordcloud.py 是小红书风格加强版。两者核心思路一致先用 jieba 分词再用 WordCloud 生成图片。我把常见做法抽出来给你看import jieba from wordcloud import WordCloud import matplotlib.pyplot as plt import pandas as pd # 1. 加载停用词表 with open(words.txt, encodingutf-8) as f: stopwords set(line.strip() for line in f if line.strip()) # 2. 读取 CSV拼接所有标题作为语料 df pd.read_csv(武汉美食.csv, encodingutf-8-sig) text .join(df[title].tolist()) # 3. jieba 分词并过滤 words [] for w in jieba.cut(text): w w.strip() if len(w) 2 or w in stopwords or not w.isalpha() and not any(\u4e00 ch \u9fff for ch in w): continue words.append(w) freq pd.Series(words).value_counts().head(200).to_dict() # 4. 生成词云 wc WordCloud( font_pathC:/Windows/Fonts/msyh.ttc, # 微软雅黑必须显式指定 width800, height600, background_colorwhite, max_words200, colormapRdYlGn ) wc.generate_from_frequencies(freq) plt.figure(figsize(10, 8)) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.savefig(wh.png, dpi200)font_path是中文词云绕不开的参数Windows 下我习惯用C:/Windows/Fonts/msyh.ttcMac 用/System/Library/Fonts/PingFang.ttc。如果不指定Figure 里的中文会全部变成方框这是新手最容易踩的坑。max_words200控制词云里最多显示 200 个词词频越高的词字号越大。generate_from_frequencies比generate的好处是你已经自己算好了词频可以更灵活地筛选词。words.txt是停用词表里面存了“的”“了”“就是”“真的”这类高频无意义词过滤后词云才有信息量。3.2 用 xhsfoodwordcloud.py 和 wordcloud.html 做可交互展示静态图片适合放进报告但如果你想在网页上展示让读者鼠标悬停看到词频项目里的 wordcloud.html 就是干这个的。这类 HTML 通常用 pyecharts 生成我在类似项目里的写法是from pyecharts import options as opts from pyecharts.charts import WordCloud # freq 来自上一步的分词统计格式为 {词: 次数} data_pair [(k, v) for k, v in freq.items()] wc_html WordCloud() wc_html.add( series_name武汉美食高频词, data_pairdata_pair, word_size_range[14, 80], shapediamond, tooltip_optsopts.TooltipOpts(is_showTrue) ) wc_html.set_global_opts(title_optsopts.TitleOpts(title小红书武汉美食关键词)) wc_html.render(wordcloud.html)shapediamond决定词云整体轮廓常用取值有circle、diamond、triangle你可以根据自己的审美换。word_size_range控制最小和最大字号词频差比较大的时候把最大值调小一些比如 80视觉上不会一个词占半屏。生成之后用浏览器打开 wordcloud.html 就能看交互效果不需要启动本地服务所有数据都内嵌在 HTML 里方便直接发给别人。对比一下两个脚本food_wordcloud.py 出的是静态 wh.png适合论文和 PPTxhsfoodwordcloud.py 生成 HTML适合放在网页或者作为交付物里的附件。如果数据量不大我建议两个都跑一遍因为静态图在 Word 里排版更稳定HTML 单独用浏览器看更直观。4. 景点热度与情感分析从地图到用户态度判断4.1 景点热度地图把提及次数映射到地理空间武汉旅游.csv 里最常用的分析角度是按景点名称汇总提及次数再把次数映射到地图上。项目中的 map.png 和 m1.html 就是这类产物。注意武汉景点多集中在一个城市尺度上用街道级别的地图效果并不好常见做法是用 pyecharts 的 Map 组件做“武汉”市级地图。代码大致是from pyecharts.charts import Map from pyecharts import options as opts # 手动构造示例数据实际应该从武汉旅游.csv 按景点聚合 spot_data [(黄鹤楼, 128), (东湖, 96), (户部巷, 85), (江汉路, 72), (粮道街, 65)] m Map() m.add( series_name武汉景点提及次数, data_pairspot_data, maptype武汉, is_roamTrue ) m.set_global_opts( title_optsopts.TitleOpts(title小红书武汉热门景点分析), visualmap_optsopts.VisualMapOpts(max_130, is_piecewiseTrue) ) m.render(m1.html)maptype武汉会加载武汉市级地图数据指标如果超过几百用is_piecewiseTrue做分段色带比连续渐变更直观。is_roamTrue允许鼠标拖拽和缩放方便看景点之间的相对位置。如果你想把地图输出成图片可以用snapshot_selenium或直接截图浏览器的 m1.html项目里的 map.png 更像是把地图渲染后用 PIL 拼接生成的成果图。这里的关键是地图不是画得越复杂越好而是要把“哪个区域笔记密度高”讲清楚。江城文旅热度通常集中在长江两岸的武昌和汉口你从词云里看到的景点名一旦落到地图上就能发现热力是连片还是孤点。这个信息比单纯看数字排名更有说服力。4.2 情感分析emotion.py 如何判断用户对景点和美食的态度只看提及次数会忽略态度。比如一个景点被提到 200 次但全是“踩雷”“别去”它就不是真正的口碑热点。项目里的 emotion.py 就是做这件事的。常见实现方式是用 SnowNLP 对标题或评论做情感打分from snownlp import SnowNLP import pandas as pd def get_sentiment(text): if not text or len(str(text)) 2: return 0.0 return SnowNLP(str(text)).sentiments # 0~1越接近1越积极 df pd.read_csv(武汉旅游.csv, encodingutf-8-sig) df[sentiment_score] df[title].apply(get_sentiment) # 分成三档0-0.4消极0.4-0.6中性0.6-1积极 df[sentiment_label] pd.cut( df[sentiment_score], bins[0, 0.4, 0.6, 1.0], labels[负面, 中性, 正面] ) # 按景点聚合平均情感分 result df.groupby(spot)[sentiment_score].agg([count, mean]).round(2) result result[result[count] 5].sort_values(mean, ascendingFalse) print(result.head(10))SnowNLP返回的是 0 到 1 的浮点数不需要训练模型开箱即用对短文本标题的粗粒度判断足够。pd.cut的三档阈值需要根据你的语料微调我之前跑美食数据时发现武汉话的“绝了”“蛮好”分数很高但“还行吧”只有 0.5 左右所以会把中性阈值放宽到 0.45~0.65。聚合时用count 5过滤掉只被提到一两次的冷门景点避免少数几条评论拉高均值。这步做完你就会得到两份关键结果一个是“哪些景点被反复提及”另一个是“这些提及里正面占比多高”。两者交叉就能找出“高热度高情感”的优质目的地以及“高热度低情感”的营销型但口碑不佳的景点。5. 避坑指南爬虫、中文编码与词云字体的典型翻车点5.1 爬虫返回空 HTML数据一条都解析不到现象requests 请求小红书搜索页打印 resp.text 能看到页面框架但用 BeautifulSoup 查找.note-item时结果为空后面所有字段全是 NaN。原因小红书是前后端分离的动态页面笔记数据由浏览器发起 XHR 请求获取返回 JSON然后渲染到 DOM。你直接用 requests 拿到的是没有数据的骨架 HTML而且这类接口通常带签名参数缺少签名或过期 Cookie 时服务端直接拒绝返回。解决我在实战里通常会放弃直接解析 HTML改为用浏览器的开发者工具找到搜索页的search/notes接口复制它的完整 URL 和请求头在代码里模拟这个 JSON 请求。同时登录自己的小红书账号把 Cookie 里的关键字段如 web_session填进 headers。如果签名校验过不去就退回 Selenium 或 Playwright 控制真实浏览器抓取。项目里的 xhs.py 保留的是教学式写法线上用时这段必须更新。5.2 Excel 打开 CSV 中文全乱码现象用 pandas 保存的武汉美食.csv放到 Excel 里中文变成“烤干”“æ¦æ±‰”一类字符。原因Excel 默认以 GBK 编码打开 CSV而 Python 写入时用的是 UTF-8两边编码不匹配就会乱码。解决保存 CSV 时明确指定encodingutf-8-sig。这个编码会在文件开头写入 BOM 标记Excel 读到 BOM 后会自动识别为 UTF-8。同时读取时也要统一用encodingutf-8-sig避免数据里残留 BOM 导致第一列列名多一个看不见的字符。5.3 词云生成后中文全是小方块现象food_wordcloud.py 运行没有报错但 wh.png 里中文全部显示为空心方块或提示字符。原因WordCloud 的默认字体是英文 DroidSansMono不支持中文。你没带font_path参数或者给了一个系统中不存在的字体路径它就直接沿用默认字体。解决在 Windows 上使用系统绝对路径C:/Windows/Fonts/msyh.ttc在 macOS 上用/System/Library/Fonts/PingFang.ttc。注意文件名大小写和扩展名.ttc和.ttf都可用但路径必须真实存在。生成前加一行print(os.path.exists(font_path))确认路径有效这是我每次都会做的检查。5.4 词频统计被“了”“的”“就是”霸屏现象词云里最大最显眼的全是“真的很不错”“真的很好吃”里的“真的”景点名和菜品名反而小得可怜。原因jieba 分词后没有做停用词过滤高频虚词和口语化副词占据了词频榜前几名。另外默认分词会把“热干面”切成“热干”和“面”导致意义被拆散。解决维护一个 words.txt 停用词表把“真的”“非常”“一个”“好吃”这类无区分度的词放进去。一次写入多个项目复用。在分词后增加过滤条件单字词直接丢弃纯数字丢弃。如果想保留“热干面”这类整体词可以给 jieba 加上自定义词典jieba.add_word(热干面)。我在处理武汉美食数据时至少加了二十个地名加食物名的复合词词云的可读性才上来。5.5 数据重复导致景点排名虚高现象统计“黄鹤楼”提及次数时发现有 200 条但人工检查发现有 60 条是同一篇爆文被多个账号复制转发甚至标题字符完全一样。原因小红书上的爆款内容会被大量二次搬运。drop_duplicates(subset[title])只能过滤同一账号内的重复行但不同账号发布相同标题时不会触发去重。解决清洗时使用“标题链接”联合去重并在人工抽检时检查link或note_id字段是否相同。更严格的做法是针对同一标题如果作者不同但内容相似度超过 90%也视为重复用fuzzywuzzy做模糊匹配过滤。这一步直接影响后续所有统计的可信度值得多花时间。6. 结果解读与进阶把可视化变成能写进报告的观点词云、地图、情感分都跑出来后真正难的是怎么把它们凝练成一句有依据的判断。我一般从三个角度来组织结果时间趋势、口碑交叉、异常点核对。先看时间趋势。如果武汉旅游.csv 里保留了发布时间的字段我建议做一个月度提及量曲线。比方说樱花季的 3 月关于武汉大学的笔记会有一个明显的尖峰这个尖峰和“武汉旅游”整体曲线的比值能说明季节主题的带动效应。代码上将publish_time用dt.to_period(M)聚合即可。我在跑的时候发现东湖的提及量在 4 月显著上升而粮道街的美食笔记全年平稳说明“城建美食”流量型内容和“季节景点”脉冲型内容形成了时间互补。再看口碑交叉。把景点和美食各自的情感均值与提及量做散点图横轴是提及数纵轴是情感均分右上角的就是口碑双高的推荐内容。比如黄鹤楼提及量高但情感分 0.52 居中东湖樱园提及量中等但情感分 0.78那么给旅游从业者的建议就不是“黄鹤楼不值得推”而是“黄鹤楼是打卡型地标东湖更适合做深度体验内容”。这个结论比单纯列榜单更有参考价值。最后做异常点核对。所有可视化结果里一旦出现违背常识的数字先不要急着解释回到原始 CSV 抽几条看看。我这里有个血泪教训以前跑某个城市分析发现“某景区”词频异常高抽数据后发现那月底正好有大量营销号集中发同一条抽奖笔记标题里带景区名字导致词频暴涨。从那以后我每次改完清洗规则都要跑一遍df[df[title].str.contains(抽奖)].shape[0]这样的检查把营销噪音单独标记出来而不是直接删掉。项目里两个 CSV 的最终稿件也是这么一遍遍筛出来的。如果你拿这份资源做毕设或课程项目我建议花一个晚上把 wordcloud.html 里的词频数据和 wh.png 的图形做一个交叉验证在 HTML 里找一个中等字号的美食词回去在 CSV 里搜索它数一下真实提及次数看是否和词频一致。这一步能同时检验清洗逻辑、分词逻辑和渲染逻辑是成本最低的验收方式。希望这篇笔记里的代码片段和踩坑记录能帮到你至少在跑通这套流程时少撞几堵我已经撞过的墙。本文还有配套的精品资源点击获取