做数据分析这些年,我经常被问到的一个问题是:手头没有像样的数据可以练手怎么办。其实每个人的微信好友列表就是一个现成的、真实的数据集。这个项目就是把我的微信好友数据批量导出来,用 Python 做了一轮完整的数据清洗、特征分析和可视化,最后产出一份能直接看的好友画像报告。整个过程不复杂,涉及的库就是 pandas、pyecharts、jieba 和 wordcloud,非常适合刚入门数据分析的同学作为第一个完整项目来练手,也可以给有经验的工程师当作一套标准化的数据处理流程参考。
先说清楚这个项目能解决什么问题:一是让你掌握“拿到原始数据之后,怎么一步步变成有价值的结论”的完整思路;二是把数据分析与可视化中最常用到的技能点都串起来,包括数据清洗、分组统计、文本挖掘、图表绘制和报告组装。你不用懂太深的理论,跟着流程走一遍,基本的数据分析思维就能建立起来。下面我直接把当时的实操过程、踩过的坑和最终代码都摊开来讲。
1. 项目概述与整体设计思路
1.1 我们到底在分析什么
很多人一听到“微信好友数据分析”,第一反应就是要爬聊天记录、采集朋友圈,其实不是。好友分析的对象只是通讯录里的基础信息,核心字段就这几个:昵称、性别、地区、个性签名、备注名、标签分组。这些字段在你自己的通讯录里都能看到,不涉及聊天内容,合规性上要友好很多。
基于这些字段,我们能回答的问题很实在:
- 好友中男女比例大约是多少,未知性别占比高不高
- 好友都集中在哪些省市,社交圈是否偏地域化
- 个性签名里出现最多的关键词是什么,反映了什么特征
- 大家的备注习惯是什么样的,有没有统一规律
- 标签分组分布如何,是否有人做了很好的好友分类管理
我当时做这个项目的目标,就是把这几个问题用图表直观地呈现出来。最终产出的不是一堆表格,而是一个可以滚动查看、可以交互的 HTML 报告,发给朋友看的时候大家都觉得很有意思。这种“从数据到结论再到分享”的完整闭环,才是数据分析的真实价值。光有一堆图表没有洞察,那叫画图,不叫分析。
1.2 技术选型:为什么是 pandas 加 pyecharts
技术选型上其实我纠结过一阵子,因为可视化的库实在太多了。matplotlib 是基础,但画出来默认风格偏老,中文乱码问题能把人折磨死;seaborn 统计图表漂亮,但做地图和交互式图表要额外组合;plotly 交互能力很强,不过在离线环境下使用和部署有点繁琐。
我最终把主力放在 pandas 加 pyecharts 这套组合上。pandas 负责数据清洗和统计计算,这是它的看家本领;pyecharts 负责所有图表输出,它在底层封装了 ECharts,既支持交互、又对中文字体支持得不错,生成的 HTML 文件可以直接发给任何人打开看,不需要搭建服务,也不依赖网络。
| 对比项 | matplotlib | pyecharts | plotly |
|---|---|---|---|
| 中文支持 | 需要手动设置字体 | 默认较好 | 需要配置字体 |
| 交互性 | 弱 | 强,自带 tooltip | 强 |
| 地图支持 | 弱,需要额外库 | 内置地图组件 | 需要在线资源 |
| 学习成本 | 中等 | 低,API 简洁 | 中等 |
| 输出形式 | 静态图片 | HTML/图片 | HTML/图片 |
当然我不是说 matplotlib 没用,它是很多高级定制的基础。但如果你做的是一份要发给别人看、要带交互效果的分析报告,pyecharts 是更省事的选择。环境方面,Python 3.8 以上都行,安装命令很简单:
bash复制pip install pandas pyecharts jieba wordcloud
这里提个醒:pyecharts 版本变化挺大,网上很多旧教程用的是 1.x 版本,代码写法完全不一样。本文代码基于 pyecharts 2.x 版本,也就是当前 pip 默认安装的版本,如果你发现 from pyecharts.charts import Bar 报错,先检查版本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据获取与预处理:先把数据搞干净
2.1 数据获取的现实情况与合规底线
先聊一个绕不开的现实情况:微信官方目前没有提供一套公开、稳定的批量导出好友信息接口。早年大家常用的网页协议版方案现在大范围失效,而且扫码登录有风控风险;本地数据库解析的方案又涉及逆向和隐私,工具版本跟着微信更新走,今天能跑通明天可能就不行。所以在这个环节,我不建议你把精力全部押在“一定要拿到全量真实数据”上。
我的建议是两条腿走路:如果你想先跑通流程,直接用下面的模拟数据脚本,几分钟就能生成一份结构完全一致的 CSV 文件,所有分析和可视化代码都不受影响;如果你确实想分析自己的通讯录,可以用手机通讯录里能看到的字段,在 Excel 里手动整理一份脱敏版本,几十条数据一样能学会整套分析方法。等你真正确认某个数据导出工具可靠、且只在自己电脑上处理不对外泄露,再去做完整导入也不迟。
这里我需要强调一个底线:整个项目只允许分析你自己的账号、你自己的好友数据,不能在公开渠道传播好友的原始信息。我后面在报告展示环节也做了昵称打码处理,这是对数据最基本的尊重。合规和安全永远排在技术前面。
2.2 定义统一的数据格式
为了让后续代码复用性更强,我一开始就把数据格式固定了下来。不管数据从哪来,最终都要转换成这样一个 CSV,包含六个字段:昵称、性别、省份、城市、签名、备注、标签。标签字段可能存在多条,用英文分号分隔。
| 字段名 | 说明 | 示例 |
|---|---|---|
| nickname | 好友昵称 | 阿哲 |
| sex | 性别代码 | 1 表示男,2 表示女,0 表示未知 |
| province | 省份 | 广东 |
| city | 城市 | 深圳 |
| signature | 个性签名 | 但行好事,莫问前程 |
| remark | 备注名 | 大学室友-阿哲 |
| tags | 标签分组 | 同学;朋友 |
没有真实数据时,可以用 Faker 库生成一批模拟数据来练手。如果嫌 Faker 安装麻烦,也可以自己写一个随机组合的小脚本,核心是让数据“像真的”——中文昵称、全国各省市、有重复有缺失,这样清洗环节才有意义。下面是我当时用的生成脚本,保持简洁,你可以按需扩展:
python复制import pandas as pd
import random
names = ["阿哲", "小鹿", "大熊", "晨晨", "老周", "Alice", "Kevin", "小满", "阿杰", "圆圆"]
provinces = ["广东", "广东", "北京", "上海", "浙江", "江苏", "四川", "湖北", "湖南", "福建", "山东", "河南"]
cities = {"广东": ["深圳", "广州", "东莞"], "北京": ["北京"], "上海": ["上海"], "浙江": ["杭州", "宁波"], "江苏": ["南京", "苏州"], "四川": ["成都"], "湖北": ["武汉"], "湖南": ["长沙"], "福建": ["厦门", "福州"], "山东": ["济南", "青岛"], "河南": ["郑州"]}
signatures = ["但行好事,莫问前程", "爱生活,爱自由", "", "Keep learning", "搬砖中", "努力赚钱", "岁月静好", "不想上班", "读万卷书", "知行合一"]
rows = []
for i in range(200):
province = random.choice(provinces)
rows.append({
"nickname": random.choice(names) + str(random.randint(1, 99)),
"sex": random.choice([0, 1, 2, 1, 1, 2]),
"province": province,
"city": random.choice(cities[province]),
"signature": random.choice(signatures),
"remark": random.choice(["", "同事", "大学同学", "客户", "老友"]),
"tags": ";".join(random.sample(["同事", "朋友", "同学", "家人", "客户"], random.randint(1, 3)))
})
pd.DataFrame(rows).to_csv("wechat_friends.csv", index=False, encoding="utf-8-sig")
print("模拟数据生成完成")
这里注意一个细节:保存 CSV 时我用的是 encoding="utf-8-sig",不是简单的 utf-8。这样 Excel 打开时中文才不乱码。如果后期用 pandas 读取时发现乱码,也优先检查文件编码格式。
2.3 数据清洗:pandas 完整代码
拿到数据之后,最忌讳的是一上来就画图。真实数据里一定会有空值、重复值和脏格式,不处理干净,后面所有统计都会失真。我的清洗流程分五步走:
第一步读文件,指定编码为 utf-8-sig,避免中文乱码;第二步去重,微信里因为改昵称、重新通过好友等原因,同一个人的重复记录很常见;第三步处理缺失值,性别填 0,省份城市填“未知”,签名填空字符串;第四步把性别代码映射成可读文本;第五步拆分省市字段,因为有些数据源会把省市放在一列里。
python复制import pandas as pd
df = pd.read_csv("wechat_friends.csv", encoding="utf-8-sig")
print("原始数据量:", len(df))
# 1. 去重,保留最后一条记录
df = df.drop_duplicates(subset=["nickname", "remark"], keep="last")
print("去重后数据量:", len(df))
# 2. 填充缺失值
df["sex"] = df["sex"].fillna(0).astype(int)
df["province"] = df["province"].fillna("未知")
df["city"] = df["city"].fillna("未知")
df["signature"] = df["signature"].fillna("")
df["remark"] = df["remark"].fillna("")
# 3. 性别映射
sex_map = {0: "未知", 1: "男", 2: "女"}
df["sex_text"] = df["sex"].map(sex_map)
# 4. 省市字段如果混在一起,就拆开
if "region" in df.columns and "province" not in df.columns:
df[["province", "city"]] = df["region"].str.split(" ", n=1, expand=True)
# 5. 空字符串视作未知
df["province"] = df["province"].replace("", "未知")
df["city"] = df["city"].replace("", "未知")
df.to_csv("wechat_friends_clean.csv", index=False, encoding="utf-8-sig")
print(df.head())
清洗完的数据干净了很多,但如果你细看,还会有两个隐藏问题:一个是部分省份在整个微信体系里其实是“海外”或者“其他”这种非常规值,处理时不用强行改成省份,保留“海外”和“其他”即可;另一个是签名里有大量广告模板,比如“加我微信 xxx”这种,需要在后续文本分析时做过滤。这些我放在第五部分详细说,因为它们通常要等到画图阶段才能明显暴露出来。
3. 数据分析:把好友列表拆开看
3.1 好友规模、性别与备注习惯
清洗完数据,我很喜欢先做一个“整体概览”,用最简单的方式对数据集建立直觉。常见的做法是统计总人数、男女人数、未知人数,然后看一下备注率和标签率。备注率我定义为 remark 非空的好友占比,它能在一定程度上反映这个账号的社交管理习惯。
python复制total = len(df)
male = (df["sex_text"] == "男").sum()
female = (df["sex_text"] == "女").sum()
unknown = (df["sex_text"] == "未知").sum()
remark_rate = (df["remark"] != "").mean()
print(f"总好友数: {total}")
print(f"男性好友: {male}, 占比 {male / total:.2%}")
print(f"女性好友: {female}, 占比 {female / total:.2%}")
print(f"性别未知: {unknown}, 占比 {unknown / total:.2%}")
print(f"备注率: {remark_rate:.2%}")
这里有个统计思维上的坑:性别未知的人数往往不在少数,如果直接拿男女比作结论,会忽略掉近五分之一的数据。正确做法是先展示“未知”的比例,然后在“已知性别的好友”中再算一次男女比,两个口径一起呈现,信息才完整。
关于备注习惯,我发现超过半数好友没有被备注,而在有备注的好友里,前缀词出现频率最高的是“客户”“同事”“大学同学”这几种。这说明微信昵称更像是个人标签,而备注才是真正的联系人管理工具。这个字段后续可以做更细的文本分析,也可以作为好友分组质量的一个参考指标。
3.2 地域分布与城市聚集
地域字段是最直观的社交圈画像维度。微信好友分布在哪些省份,往往直接反映了你的工作生活轨迹。我的统计方式是把省份和城市分别做 value_counts,然后取前 20 个省份做展示。
python复制province_stats = df["province"].value_counts().reset_index()
province_stats.columns = ["province", "count"]
city_stats = df["city"].value_counts().reset_index()
city_stats.columns = ["city", "count"]
print("省份分布 TOP10:")
print(province_stats.head(10))
从结果来看,好友地域分布往往呈现明显的头部效应:两三个省份占据了一半以上的好友数量。比如我在广东工作过几年,广东好友占比接近三成;老家湖南也有一定比例;剩下的则分散在全国各地。这种“少数地区集中、多数地区分散”的分布,是社交关系的典型长尾结构。
如果你想把城市层级也展开,可以做一张城市 TOP20 的横向条形图。横向条形图的优势是城市名称不会因为太长而被截断,在移动设备上阅读也友好。真正要得出结论时,不要只停留在“广东最多”,要继续追问:为什么广东最多?是不是和求学、工作经历相关?这种“数据结果 + 业务解释”的组合,才是数据分析思维的核心。
3.3 个性签名与关键词画像
个性签名是文本分析的主战场。我的思路是把所有签名拼接成一个大文本,用 jieba 做中文分词,再用停用词表过滤掉“的”“了”“是”这类无意义虚词,最后统计词频。这里有个关键点:词频统计前先清理广告签名,比如“招代理加微信 xxx”“原单货源”这类文本,否则它们会霸榜,把真实用户兴趣全部淹没。
python复制import jieba
from collections import Counter
# 过滤明显广告
ad_keywords = ["加微信", "招代理", "代购", "原单", "刷单", "兼职"]
def is_ad(sig):
return any(k in sig for k in ad_keywords)
texts = df[~df["signature"].map(is_ad)]["signature"].tolist()
full_text = " ".join(texts)
stopwords = set()
with open("stopwords.txt", "r", encoding="utf-8") as f:
for line in f:
stopwords.add(line.strip())
words = [w for w in jieba.cut(full_text) if len(w) > 1 and w not in stopwords]
word_freq = Counter(words).most_common(30)
print(word_freq)
在建立停用词表时,除了通用虚词,我还会加入“签名”“微信”“哈哈哈”这类没有分析价值的词。注意 jieba 对部分新词和网络热词的分词不一定准,你可以维护一个自定义词典。分词这步没有统一标准,关键是你得解释清楚词频结果背后的含义,而不是把 Top30 词列完就算交差。
3.4 数据洞察:从“看数据”到“做决策”
如果只是输出统计数字,这个项目还停留在“数据处理”阶段。真正让报告有价值的,是那些能回答业务问题的洞察。我当时整理出三个结论,每个结论背后都有一组数据支撑:
- 好友性别比在已知性别的样本中约为 6:4,男性略多,但未知性别的比例高达 18%,说明很多人没有填写性别信息,分析时要打折扣看。
- 地域分布高度集中在广东、湖南、北京三地,进一步结合备注字段发现,广东和北京的好友多是同事和客户,湖南的好友多为同学和亲戚。这说明我的社交圈子基本是“工作地+老家”的双中心结构。
- 签名关键词排名第一的是“生活”,第二是“自由”,可见比较多的好友把表达重点放在生活态度上,而不是具体职业描述。
这种“数据 + 解释 + 行动”的框架,是所有数据分析项目通用的模板。你可以不需要机器学习和高级建模,把描述性统计做扎实、能讲出故事,已经超过很多人了。
4. 可视化呈现:把结论画出来
4.1 基础图表:性别饼图、地区条形图、签名词云
可视化环节我建议从小到大逐级呈现:先画最基础的饼图、条形图,再上地图和词云。用 pyecharts 画性别分布饼图,代码非常直接:
python复制from pyecharts.charts import Pie
from pyecharts import options as opts
sex_data = df["sex_text"].value_counts().reset_index().values.tolist()
pie = (
Pie()
.add("", sex_data, radius=["40%", "70%"])
.set_global_opts(title_opts=opts.TitleOpts(title="好友性别分布"))
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {d}%"))
)
pie.render("gender_pie.html")
这里有个我一开始容易忽略的点:add 方法传入的数据格式必须是 [[key1, value1], [key2, value2]] 这种二元素列表的列表,不是 DataFrame 也不是字典。我早期经常因为格式不对导致图表空白,最后靠打印 sex_data 才发现问题。
地区条形图我用 Bar,把 province_stats 按数量从高到低取前 20 画出横向条形:
python复制from pyecharts.charts import Bar
top20 = province_stats.head(20).sort_values("count")
bar = (
Bar()
.add_xaxis(top20["province"].tolist())
.add_yaxis("好友数", top20["count"].tolist())
.reversal_axis()
.set_global_opts(title_opts=opts.TitleOpts(title="好友省份分布 TOP20"))
)
bar.render("province_bar.html")
词云的生成用 wordcloud。这个库最大的坑是字体,中文显示必须有中文字体文件,否则输出全是方块。Windows 系统可以直接用 C:/Windows/Fonts/simhei.ttf,macOS 用 /System/Library/Fonts/PingFang.ttc,Linux 则要自己装一个中文字体。
python复制from wordcloud import WordCloud
import matplotlib.pyplot as plt
font_path = "C:/Windows/Fonts/simhei.ttf"
wordcloud = WordCloud(font_path=font_path, width=800, height=600, background_color="white", max_words=100)
wc = wordcloud.generate_from_frequencies(dict(word_freq))
plt.figure(figsize=(10, 8))
plt.imshow(wc, interpolation="bilinear")
plt.axis("off")
plt.savefig("signature_wordcloud.png", dpi=150, bbox_inches="tight")
生成的词云如果太乱,原因多半是停用词表不够全,或者 max_words 设得太大。我的经验是先跑一遍,看高频词里哪些明显是噪音,把它们加进停用词表再跑一次,两三轮就能收敛。
4.2 地图可视化:省份分布图
地图是这个项目的视觉亮点。pyecharts 的 Map 组件用起来简单,但有个注意点:地图的省份名称必须用全称,比如“广东”,不能是“广东省”或“粤”,否则数据匹配不上,显示出来就是 0。
python复制from pyecharts.charts import Map
province_pairs = province_stats[province_stats["province"] != "未知"].values.tolist()
map_chart = (
Map()
.add("好友数", province_pairs, maptype="china")
.set_global_opts(
title_opts=opts.TitleOpts(title="好友省份分布地图"),
visualmap_opts=opts.VisualMapOpts(max_=50, is_piecewise=True),
)
)
map_chart.render("province_map.html")
如果你发现地图组件渲染后显示空白或者地图区域颜色不变化,先检查两件事:第一,pyecharts 是否安装了最新的地图扩展包;第二,省份名列表里是否有“未知”“海外”这类非省名数据,它们不在中国地图的区域内,但不会报错,只会让图表看起来少了点颜色。处理方式很简单,过滤掉非省份值就可以了。
4.3 组合成一份可分享的报告
图表单独生成 HTML 文件没问题,但发给别人时一个个文件很麻烦。我的做法是用 pyecharts 的 Page 组件把多个图表组合到一个页面里,一次性输出一份完整报告。Page 的布局默认是纵向滚动,效果接近公众号长图,阅读体验很好。
python复制from pyecharts.charts import Page
page = Page(layout=Page.SimplePageLayout)
page.add(pie, bar, map_chart)
page.render("wechat_friend_report.html")
你也可以在 Page 里加入 HTML 原生组件,自己写几个标题段落和解读文字,让报告更像一篇图文并茂的文章,而不是干巴巴的图表堆叠。这一步对非技术背景的读者特别友好,也是我后来做任何数据分析项目都会保留的习惯:一定要有叙事结构。
如果你使用的是 Jupyter Notebook,直接在每个代码块后面调用 chart.render_notebook() 就能在单元格里展示交互图,调试起来更快。不过最终交付给别人的,我还是建议生成独立的 HTML 文件,因为对方不一定装 Python。
5. 问题排查与避坑指南
5.1 中文乱码的三种情况与解决
中文乱码几乎是每个新手必经的坑,而且表现形式不止一种。如果你用 pandas 读取 CSV 后看到乱码,多数是文件编码问题,读取时指定 encoding="utf-8-sig",或者保存时统一用它;如果 matplotlib 图表上的中文变成方块,那是缺少中文字体配置,在绘图前加两行代码设置 rcParams;如果 wordcloud 生成的图片里中文消失或变方块,则是字体路径问题,需要手动指定系统的中文字体文件。
python复制import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "PingFang SC"]
plt.rcParams["axes.unicode_minus"] = False
我见过有人卡在最后一步很久,其实解决方案非常简单,但要看你具体是哪个环境。Windows、macOS、Linux 的字体路径完全不同,我建议在你的环境里先跑 matplotlib.font_manager 查看已安装字体,再选择最合适的一个。
5.2 地图加载失败或白屏
地图组件加载失败,大概率是地图数据文件缺失。pyecharts 2.x 已经内置了中国地图,但如果你的环境版本很旧,或者自己编译过,可能会遇到地图数据不完整的问题。一个快速排查方法是用浏览器打开生成的 HTML,按 F12 看控制台有没有报错,常见的错误是“china-map not exists”。
出现这个问题时,可以先去确认 pyecharts 的版本,再决定是否重装地图扩展包。我的原则是尽量使用干净的新建虚拟环境,避免把旧项目里的 pyecharts 版本带过来,因为版本之间的 API 和地图数据差异真的很大。
提示:如果你最终只需要静态图片,在地图组件渲染后,可以用 pyecharts 的
make_snapshot配合截图工具转成图片,但在 Windows 下渲染时会依赖额外的浏览器环境,配置起来稍麻烦。大部分场合,直接交付 HTML 文件更省事。
5.3 词云太乱和广告签名过滤
词云乱的根本原因是文本噪音没有清干净。除了加停用词表,我还会做两件事:一是把所有英文转成小写,避免“Keep”和“keep”被当成两个词;二是把重复无意义的助词、语气词统一过滤。针对广告签名,我用了一套关键词黑名单,比如“招代理”“加我”这类,一旦命中就整条签名剔除,不参与分词。
这里有个取舍要提醒你:过度清洗会让词云失去原汁原味,但清洗不足又会被广告词霸屏。我的标准是保留能反映用户生活态度的内容,过滤以“营销导流”为目的的内容。这一条可以根据你自己的数据分布灵活调整。
5.4 隐私与数据安全提醒
最后这条我必须说,也是每个做跟个人数据相关项目的都应该在脑子里绷紧的弦。微信好友数据属于个人敏感信息,就算是你自己的通讯录,里面也包含了好友的昵称、头像、签名,这些内容在未经授权的情况下不能随意公开。我分享报告时,所有昵称都做了打码,城市精确到省级,签名里如果带有手机号或微信号,我一律先删除。
另外,不要随便把数据导出工具生成的中间文件上传到论坛、网盘或者在线分析平台。数据安全的最高原则就是“能本地处理就本地处理,能不流动就不流动”。这个项目本身是为了学习和练手,不要因为一时图方便去触碰这些红线。
踩过几次坑之后,我自己的体会是:这个项目的难点并不在代码,而在“先想清楚要回答什么问题,再决定画什么图、做什么分析”。数据分析通常不是一上来就堆代码,而是先定义问题、确定口径、检查数据质量,最后才是可视化呈现。如果你能从这篇教程里带走一点东西,我希望是这一整套“清洗 — 分析 — 洞察 — 表达”的思路,而不只是某几行代码。
最后再分享一个小技巧:当你把报告做出来后,试着把它发给你身边懂业务但不写代码的朋友,让他们提感受。如果他们能一眼看懂你想表达什么,说明图表选对了;如果他们一脸茫然,那大概率不是图的问题,而是你的分析主线还不够清晰。迭代几轮之后,你会明显感觉到自己在数据分析思维上的进步。
