1. 项目缘起与整体分析框架
1.1 为什么选长租公寓投诉数据作为分析标的
长租公寓这个行业在过去几年经历了过山车式的变化,头部品牌暴雷、租金贷风波、房东与租客两头受损的新闻层出不穷。我之所以挑蛋壳公寓的投诉数据来做这个分析项目,原因很直接:它是一个天然的、高质量的、带有强烈现实意义的数据集。投诉文本里包含了租金纠纷、押金退还、房屋质量、管家服务、合同条款争议等多个维度,既有结构化的字段(投诉时间、投诉平台、处理状态),又有大量非结构化的文本内容,非常适合拿来练手完整的分析链路。
从技能覆盖的角度看,这个项目能一次性串起数据采集、数据清洗、文本分词、情感分析、关键词提取、可视化呈现这几大核心环节。很多新手做数据分析项目时,要么只做结构化数据的聚合统计,要么只跑一个情感分析模型就结束了,链路是断的。而投诉分析这个场景天然要求你把结构化字段和非结构化文本结合起来看,才能得出有业务价值的结论。
这个项目适合谁?我认为有三类人值得动手做一遍:第一类是刚学完Python数据分析基础、想找一个真实数据集练完整流程的初学者;第二类是想补充NLP文本分析实战经验的数据从业者;第三类是对住房租赁行业感兴趣、想用数据视角理解行业问题的人。不管你属于哪一类,跟着走完一遍,你会对“从原始数据到业务洞察”这条链路有更具体的体感。
1.2 分析框架的搭建思路
拿到一个投诉数据集,最忌讳的就是上来就写代码跑统计。我习惯先花二十分钟把分析框架想清楚,问自己三个问题:这份数据能回答哪些业务问题?每个问题需要什么字段支撑?最终输出给谁看?
针对蛋壳公寓投诉数据,我梳理出的核心分析框架分为四层。第一层是投诉概览,回答“投诉总量多少、时间分布如何、处理状态怎样”这类描述性问题。第二层是投诉内容拆解,回答“投诉集中在哪些问题上、高频关键词是什么”这类结构性问题。第三层是情感与严重程度分析,回答“投诉者的情绪倾向如何、哪些问题引发的负面情绪最强”这类深度问题。第四层是关联与趋势分析,回答“不同城市/不同时间段的投诉特征有何差异、投诉量与某些事件节点是否相关”这类交叉问题。
这个四层框架的好处是层层递进,每一层的输出都能作为下一层的输入。比如第一层发现某个月投诉量激增,第二层就去拆解那个月的投诉关键词,第三层再看那批投诉的情感强度,第四层再关联当时可能的外部事件。这样分析下来,报告是有逻辑链条的,而不是一堆孤立图表的堆砌。
注意:框架不是死的。实际分析过程中,你可能会在第二层发现一个意料之外的高频问题,那就应该灵活调整后续方向,而不是硬按原计划走。数据分析的价值往往就藏在这些“意外”里。
2. 数据获取与清洗的关键细节
2.1 数据来源与采集思路
做投诉分析,数据来源通常有几个渠道:公开的消费者投诉平台、社交媒体上的用户吐槽、应用商店的评论、以及裁判文书网上的相关纠纷记录。我在这个项目中主要使用的是公开投诉平台上关于蛋壳公寓的投诉记录,这些记录通常包含投诉标题、投诉正文、投诉时间、处理状态、涉及金额等字段。
采集方式上,如果目标平台没有反爬限制,用 requests 加 BeautifulSoup 就能搞定;如果页面是动态渲染的,就需要用到 Selenium 或 Playwright。我这里不展开讲具体采集代码,因为不同平台结构差异太大,但我要强调一个原则:采集前先手动翻二十页,把字段结构和翻页规律摸清楚,再动手写代码。很多人上来就写爬虫,结果采到一半发现字段对不上,返工成本极高。
采集下来的原始数据通常是脏的。我拿到的原始数据大概有八千多条记录,但直接可用的不到六成。剩下的问题包括:重复投诉(同一用户多次提交)、字段缺失(处理状态为空)、文本乱码(编码问题)、以及大量无意义的灌水内容。这些都需要在清洗阶段处理。
2.2 数据清洗的实操要点
数据清洗这一步,我一般按“去重、补缺、纠错、过滤”四步走。去重不能只看投诉标题,因为不同用户可能用相同标题投诉不同问题。我的做法是组合“用户ID+投诉时间+投诉正文前50字”作为去重键,这样既能去掉真正的重复提交,又不会误删相似但不同的投诉。
补缺方面,处理状态字段的缺失率大概在15%左右。对于这部分数据,我没有直接删除,而是标记为“未知状态”单独归为一类。原因很简单:缺失本身可能携带信息。如果缺失集中在某个时间段,那可能说明那个阶段平台处理流程出了问题,这本身就是一条分析线索。
纠错主要针对文本编码和特殊字符。原始数据里有一批记录出现了中文乱码,排查后发现是采集时编码声明不一致导致的。解决办法是统一用chardet检测编码后再解码,对于检测失败的记录,用ftfy库做修复。另外,投诉正文里夹杂了大量表情符号、HTML标签残留和多余空白,这些都用正则表达式清理干净。
过滤环节要特别小心。我一开始把字数少于10个字的投诉全删了,后来发现有些短投诉信息量很大,比如“押金不退”四个字就精准说明了问题。所以后来我调整了策略:不按字数过滤,而是按“是否包含有效信息”来判断。具体做法是维护一个停用词表和一个无效内容模式表,把纯表情、纯标点、纯广告的内容剔除,其余保留。
import re import jieba def clean_text(text): if not isinstance(text, str): return "" # 去除HTML标签 text = re.sub(r'<[^>]+>', '', text) # 去除URL text = re.sub(r'http[s]?://\S+', '', text) # 去除多余空白 text = re.sub(r'\s+', ' ', text).strip() # 去除纯表情和特殊符号(保留中文、英文、数字和常用标点) text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9,。!?、;:""''()]', '', text) return text def tokenize(text): stopwords = set(['的', '了', '是', '在', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这']) words = jieba.lcut(text) return [w for w in words if w not in stopwords and len(w) > 1]上面这段代码是我实际用的清洗和分词函数,你可以直接拿去改。注意停用词表要根据你的数据特点调整,比如投诉数据里“公寓”“蛋壳”这类词出现频率极高但区分度低,也应该加入停用词。
实操心得:清洗完的数据一定要做一次抽样人工检查。我一般随机抽50条,逐条看清洗后的文本是否通顺、是否丢失了关键信息。这一步花不了多少时间,但能帮你发现很多自动化检查发现不了的问题。
3. 核心分析维度的实现与解读
3.1 投诉时间分布与趋势分析
时间维度是投诉分析的第一把钥匙。把投诉时间按天或按周聚合,画出趋势线,你能直观看到投诉量的波动规律。我在这个项目里发现了一个很明显的特征:投诉量在工作日明显高于周末,而且每天有两个高峰时段,分别是上午10点到11点、下午3点到4点。这个规律其实很好理解——租客遇到问题后,通常会在工作时间联系客服,联系不上或处理不满意才会去投诉平台提交投诉。
更有价值的是月度趋势。把投诉量按月聚合后,我看到了几个明显的峰值。这些峰值出现的时间点,往往对应着某些行业事件或平台政策调整。比如某个月投诉量突然翻倍,回溯发现那个月正好是租金贷还款规则调整的时期。这种“数据波动对应现实事件”的发现,就是投诉分析的核心价值所在。
实现上,用 pandas 的resample方法就能轻松完成时间聚合:
import pandas as pd df['投诉时间'] = pd.to_datetime(df['投诉时间']) # 按周聚合 weekly = df.set_index('投诉时间').resample('W').size() # 按月聚合 monthly = df.set_index('投诉时间').resample('M').size()画图的时候,我建议用折线图展示趋势,同时在峰值位置标注具体数值和可能的事件说明。这样图表自己会“讲故事”,不需要你在旁边写一大堆解释。
3.2 投诉文本的关键词提取与主题聚类
文本分析是这个项目的重头戏。我用了两种方法来提取投诉内容的核心主题:一种是基于 TF-IDF 的关键词提取,另一种是基于 LDA 的主题聚类。两种方法各有优劣,结合起来看效果最好。
TF-IDF 的思路很直接:如果一个词在某条投诉里出现频率高,但在所有投诉里出现频率低,那这个词就很有代表性。我用sklearn的TfidfVectorizer跑了一遍,取权重最高的30个词,得到的结果包括“押金”“退款”“租金贷”“管家”“维修”“合同”“违约金”“断电”“漏水”等。这些词基本覆盖了长租公寓投诉的主要问题类型。
但 TF-IDF 只能给出孤立的词,不能告诉你这些词之间的关联。所以我接着跑了 LDA 主题模型,设定主题数为5,跑出来的结果把投诉分成了几个明显的簇:第一簇围绕“押金退还”和“退款周期”,第二簇围绕“租金贷”和“征信影响”,第三簇围绕“房屋维修”和“设施故障”,第四簇围绕“管家服务”和“沟通态度”,第五簇围绕“合同条款”和“违约金争议”。
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.decomposition import LatentDirichletAllocation # TF-IDF tfidf = TfidfVectorizer(max_features=1000, tokenizer=lambda x: x, preprocessor=lambda x: x) tfidf_matrix = tfidf.fit_transform(df['分词结果']) feature_names = tfidf.get_feature_names_out() # 取权重最高的词 weights = tfidf_matrix.sum(axis=0).A1 top_words = sorted(zip(feature_names, weights), key=lambda x: -x[1])[:30] # LDA主题模型 lda = LatentDirichletAllocation(n_components=5, random_state=42) lda.fit(tfidf_matrix) for idx, topic in enumerate(lda.components_): top_features = [feature_names[i] for i in topic.argsort()[-10:]] print(f"主题{idx+1}: {', '.join(top_features)}")这里有个细节要注意:LDA 的主题数不是随便定的。我试了3到8个主题,最后发现5个主题时,每个主题的内部一致性最好,主题之间的区分度也最明显。你可以用困惑度(perplexity)或者主题一致性(coherence)来辅助判断,但最终还是要靠人工看结果是否“讲得通”。
注意:分词质量直接决定主题聚类的效果。如果你的分词结果里混入了大量无意义词,LDA 跑出来的主题会很模糊。所以分词后一定要做一次词频统计,把高频但无意义的词加入停用词表,反复迭代两三轮。
3.3 情感分析与严重程度分级
投诉文本天然带有负面情绪,但负面程度是有差异的。有人只是表达不满,有人则是愤怒甚至绝望。把这种情绪强度量化出来,能帮你判断哪些问题最需要优先解决。
我做情感分析用的是 SnowNLP 这个库,它对中文文本的情感倾向判断还算靠谱。基本用法很简单:
from snownlp import SnowNLP def get_sentiment(text): try: return SnowNLP(text).sentiments except: return None df['情感得分'] = df['清洗后文本'].apply(get_sentiment)SnowNLP 返回的是0到1之间的分数,越接近0表示越负面,越接近1表示越正面。投诉数据的情感得分普遍偏低,大部分集中在0.1到0.3之间。我把得分低于0.1的定义为“极度负面”,0.1到0.3为“负面”,0.3到0.5为“偏负面”,0.5以上为“中性或正面”。
但光看情感得分还不够,因为有些投诉虽然情绪激烈,但问题本身可能并不严重(比如管家回复慢了一点)。所以我结合了另一个维度:投诉中是否涉及金额、是否涉及征信、是否涉及人身安全。这三个维度中任意一个为“是”,就标记为“高严重度”。这样交叉分析下来,就能把投诉分成“高严重度高负面”“高严重度低负面”“低严重度高负面”“低严重度低负面”四类,处理优先级一目了然。
3.4 城市与处理状态的交叉分析
蛋壳公寓的业务覆盖了多个城市,不同城市的投诉特征有明显差异。我把城市字段和处理状态字段做了交叉分析,发现了一些有意思的现象。
从投诉量来看,一线城市的投诉绝对数量最多,但考虑到房源基数,部分新一线城市的投诉率反而更高。从处理状态来看,“已回复”的比例在各城市之间差异不大,但“已解决”的比例差异显著。有些城市的投诉解决率不到30%,而有些城市能到60%以上。这个差异可能跟当地运营团队的配置、客服响应机制有关。
做交叉分析用 pandas 的pivot_table就能搞定:
pivot = pd.pivot_table(df, values='投诉编号', index='城市', columns='处理状态', aggfunc='count', fill_value=0) # 计算解决率 pivot['解决率'] = pivot['已解决'] / pivot.sum(axis=1)这个表格输出后,我建议按解决率排序,把解决率最低的几个城市单独拎出来看它们的投诉内容有什么特点。我在项目里发现,解决率低的城市,投诉中“管家失联”“电话打不通”这类关键词的出现频率明显更高,说明问题可能出在当地的运营团队稳定性上。
4. 可视化呈现与报告撰写
4.1 图表选择与排版逻辑
分析做完之后,怎么呈现给读者看,这本身就是一个需要认真对待的环节。我的原则是:每张图表只讲一件事,图表之间要有逻辑递进。
报告的第一部分我用了一个横向柱状图展示投诉量前10的城市,让读者一眼看到投诉的地域集中度。第二部分用折线图展示月度投诉趋势,并在峰值处加了标注。第三部分用词云图展示高频关键词,词云虽然被很多人诟病不够精确,但它在“快速传达整体印象”这件事上确实有效。第四部分用堆叠柱状图展示不同问题类型的情感得分分布,让读者看到哪些问题引发的情绪最激烈。
工具选择上,静态图表我用 matplotlib 和 seaborn,交互式图表用 pyecharts。pyecharts 的好处是生成的HTML文件可以直接在浏览器里打开,鼠标悬停能看到具体数值,适合做探索性分析。但如果最终报告是要打印或转成PDF,那还是用 matplotlib 更稳妥。
import matplotlib.pyplot as plt import seaborn as sns # 设置中文字体 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 月度趋势图 fig, ax = plt.subplots(figsize=(12, 5)) monthly.plot(kind='line', ax=ax, marker='o') ax.set_title('蛋壳公寓投诉量月度趋势') ax.set_xlabel('月份') ax.set_ylabel('投诉量') # 标注峰值 max_month = monthly.idxmax() max_value = monthly.max() ax.annotate(f'峰值: {max_value}', xy=(max_month, max_value), xytext=(max_month, max_value + 50), arrowprops=dict(arrowstyle='->', color='red')) plt.tight_layout() plt.savefig('monthly_trend.png', dpi=150)实操心得:matplotlib 的中文字体问题坑了无数人。如果你在 Linux 或 macOS 上跑代码,
SimHei可能不存在,需要换成Arial Unicode MS或PingFang SC。最稳妥的办法是先把字体文件下载到项目目录,然后用font_manager手动注册。
4.2 分析报告的撰写结构
图表做完只是半成品,真正交付的是分析报告。我的报告结构一般是:摘要、数据说明、核心发现、详细分析、建议、附录。摘要部分用五句话概括最重要的五个发现,让没时间细看的人也能抓住重点。数据说明部分交代数据来源、时间范围、字段含义和清洗规则,保证分析过程可追溯。核心发现部分用“发现+数据支撑+业务含义”的三段式写法,每条发现都配一张图或一个表格。
详细分析部分就是前面几个章节的展开,但要注意语言转换。给业务方看的报告,不要出现“TF-IDF”“LDA”这种术语,要说“关键词提取”“主题归类”。给技术方看的报告,则可以保留术语,但要把参数和实现细节写清楚。
建议部分是最容易写空的地方。我的做法是每条建议都必须对应一个具体的数据发现,并且给出可操作的行动项。比如“某城市解决率低于40%,建议排查当地客服团队人员配置”,而不是泛泛地说“建议提升服务质量”。
5. 常见问题与排查技巧实录
5.1 数据采集阶段的典型坑
做这类项目,采集阶段最容易遇到的问题有三个。第一个是分页逻辑变化。有些平台的前几页是正常分页,翻到后面就变成滚动加载了,如果你的代码只处理了分页链接,采到一半就会断。解决办法是先用浏览器开发者工具观察网络请求,确认是分页还是滚动,再决定用 requests 还是 Selenium。
第二个是反采集机制。频繁请求会触发验证码或IP限制。我的应对策略是:控制请求频率(每次请求间隔2到3秒)、使用随机 User-Agent、必要时用代理池。但要注意,代理池的维护成本不低,如果数据量不大,手动采集加自动采集结合可能更划算。
第三个是字段错位。不同页面的HTML结构可能有细微差异,导致采集下来的字段对不上。我一般会在采集代码里加一个校验步骤:每采集100条,就打印前3条的字段值,人工确认没问题再继续。
5.2 文本分析阶段的常见问题
情感分析不准是最常见的问题。SnowNLP 在通用语料上表现还行,但投诉文本里有很多行业特定表达,比如“租金贷”“押一付三”“免押金”这些词,模型可能理解不了。我的改进方法是:先跑一遍基础情感分析,然后人工标注200条作为验证集,看看准确率如何。如果准确率低于70%,就需要考虑用行业语料微调模型,或者干脆换用基于规则的方法——维护一个负面词表和程度副词表,用加权求和的方式计算情感得分。
主题聚类结果不理想也很常见。除了前面说的分词质量问题,还有一个原因是投诉文本长度差异太大。有的投诉只有一句话,有的写了上千字。LDA 对长文本更友好,短文本容易跑出模糊的主题。我的处理方式是:对短文本做数据增强,比如把同一用户的多条投诉合并;或者在特征提取阶段,对短文本给予更高的权重。
5.3 可视化阶段的排版问题
图表太多、信息过载是新手常犯的错误。我见过一份报告放了二十多张图,读者根本记不住重点。我的建议是:核心图表不超过8张,每张图只回答一个问题。如果某个发现需要多张图才能说清楚,那就把它拆成多个发现,每个发现配一张图。
另外,图表的配色也很重要。投诉分析天然带有负面色彩,我一般用蓝色系或灰色系作为主色调,避免用大红大绿。重点数据用橙色或红色标注即可,不要整张图都是高饱和度颜色。
| 常见问题 | 排查思路 | 解决方案 |
|---|---|---|
| 采集数据字段错位 | 抽样检查HTML结构 | 增加字段校验步骤,每100条人工确认 |
| 情感分析准确率低 | 人工标注验证集评估 | 微调模型或改用规则方法 |
| 主题聚类模糊 | 检查分词质量和文本长度分布 | 优化停用词表,对短文本做增强 |
| 图表信息过载 | 问自己“这张图回答什么问题” | 精简图表数量,每图只讲一件事 |
| 中文字体显示异常 | 检查系统字体列表 | 手动注册字体文件或换用系统已有字体 |
6. 项目扩展与个人体会
这个项目做完之后,其实还有很多可以延伸的方向。比如你可以把投诉数据和房源数据关联起来,看看投诉是否集中在某些特定房源或某些特定管家身上。你也可以做一个简单的预警模型,当某个城市的投诉量在短时间内超过阈值时自动触发提醒。甚至可以把分析结果做成一个交互式看板,让运营人员自己筛选城市和时间段来查看。
我自己在这个项目里最大的体会是:数据分析的价值不在于用了多复杂的模型,而在于你能不能从数据里读出业务故事。我见过太多人把 LDA 跑出五个主题就结束了,但没有去追问“为什么押金问题在某个时间点集中爆发”“为什么某个城市的解决率持续走低”。这些追问才是分析的核心,模型只是帮你更快地找到追问的方向。
另外一个小技巧:做文本分析时,不要只盯着高频词。有时候低频但高权重的词反而更有信息量。比如“征信”这个词在整体投诉中出现的频率可能只有3%,但它一旦出现,往往意味着问题非常严重。所以我在报告里专门做了一个“低频高影响词”的板块,把这类词单独拎出来分析,效果很好。
最后再分享一个数据清洗的小经验:清洗规则不要一次写死,而是做成可配置的。我一开始把清洗规则硬编码在函数里,后来发现不同批次的数据需要不同的处理方式,改起来很麻烦。后来我把停用词表、正则规则、过滤阈值都抽出来放到配置文件里,每次调整只需要改配置,不用动代码。这个习惯在后续做其他数据项目时也帮了我大忙。