上周末我抽空做了个一直想做的实验:把微信好友的数据完整地拉下来,做一次纯个人向的数据分析和可视化。最开始只是因为好奇——想知道自己的好友男女比例到底是不是传言中的“三七开”,后来做着做着发现,这个项目虽然入口很浅,但涉及数据采集、清洗、分析、可视化一整条链路,几乎是把Python数据分析的核心流程完整过了一遍。这篇文章把整个过程复盘出来,包含完整代码、实现思路和我踩过的坑,适合有Python基础、想做一个能写进简历或给自己看的数据分析实战项目的朋友参考。
项目本身不复杂,也不依赖深度学习或大模型,就是一个典型的数据分析项目:采集微信好友的昵称、性别、地区、个性签名、头像等基础信息,然后用pandas做清洗统计,再结合matplotlib、pyecharts、wordcloud等可视化库把结果变成直观的图表。整个过程跑通后,你得到的不仅是一堆图表,更是一套完整的数据分析思维框架。
1. 项目概述:这个教程要解决什么问题
1.1 为什么想做微信好友数据分析
先说动机。微信是目前社交关系沉淀最深的产品,好友列表里的性别、地区、签名,本质上是高度真实的人群画像数据。市面上有很多所谓的“微信好友分析工具”,但大多数是黑盒,有的甚至会上传你的好友数据到服务器,隐私风险很高。自己写脚本的好处有两个:一是完全本地运行,数据不过第三方服务器;二是整个过程可控,每一步做了什么、怎么处理的,心里有数。
另一个原因是技术上的。数据分析项目最怕的是“没有像样的数据源”,大部分教程用的都是网上现成的csv或者kaggle下载的数据集,做出来总觉得隔了一层。微信好友数据是我们天天接触、有真实体感的个人数据,分析结果出来之后你能立刻判断对不对——比如你的好友集中在哪些城市、男女比例是否符合你对社交圈的认知,这种“验证感”是虚拟数据集给不了的。
1.2 项目目标与适合人群
这个项目的核心目标可以拆成四个递进的层次:
- 采集层:通过Python脚本登录微信,拉取好友的基本信息列表,保存成结构化数据;
- 清洗层:对性别、地区、签名等字段做标准化处理,去掉空值和噪音;
- 分析层:统计男女比例、地域分布、签名关键词频率等关键指标;
- 可视化层:把统计结果制作成柱状图、地图、词云、照片墙,甚至拼成一屏可视化大屏。
适合人群方面,我的建议是:如果你已经掌握了Python基础语法,会一些pandas和matplotlib的简单操作,但缺少一个完整的实战项目来串联这些知识,这个项目非常适合你。它的技术门槛不高,但涉及的知识点密度相当大,可以一次性把“数据获取→清洗→分析→可视化的标准流程”走通。
1.3 整体技术架构
技术选型上没有用重型框架,纯Python脚本即可完成。核心依赖如下:
- itchat:基于web微信协议的第三方库,负责扫码登录、获取好友信息;
- pandas:表格数据处理,负责数据清洗和统计分析;
- matplotlib:基础图表绘制;
- pyecharts:生成交互式图表和可视化大屏的HTML文件;
- wordcloud / stylecloud:个性签名词云图;
- Pillow:头像下载后的拼图处理;
- jieba:中文分词,用于个性签名的关键词提取。
需要说明的是,pyecharts有两个大版本:0.5.x和1.x。两个版本的API差异很大,网上很多教程还是0.5.x的写法,安装后直接from pyecharts import Bar就能用;而1.x要用from pyecharts.charts import Bar。我建议新项目直接用1.x版本,语法更规范,文档也更清楚。本文代码基于pyecharts 1.x编写。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据获取:先拿到一份“干净”的好友列表
2.1 方案选型:为什么优先选择itchat
在动手写代码之前,最关键的问题是“数据从哪来”。目前社区里常见的方式有三种:第一种是itchat这类web微信接口库直接拉取,第二种是从微信电脑版的备份数据库里解析,第三种是手动复制粘贴。第三种显然是反人类的,我直接排除。第二种方案虽然稳定,但需要逆向解析sqlite数据库,而且微信每次升级都可能让解密方式失效,坑比较深,对新手不太友好。
itchat虽然也不是官方接口,但好处是封装彻底,几行代码就能拿到好友数据,适合快速验证想法。它的原理其实很简单:模拟浏览器向web微信的前端接口发请求,扫码登录后获取cookie,然后请求好友列表接口。当然,web微信本身已经被腾讯逐步收紧,现在很多新注册的微信号无法登录网页版。这点提前说清楚,如果你扫码后提示“该微信号无法登录网页版”,那就别折腾itchat了,直接用后面我会说的备用方案。
提示:不要拿你的主微信号去玩这种第三方登录,尤其是涉及支付、重要资料备份的账号,风险不可控。建议开一个小号,加一些测试好友后再跑。
2.2 采集流程与代码实现
数据获取的流程可以分为三步:扫码登录、拉取好友列表、逐字段存储。
第一步,安装依赖:
bash复制pip install itchat pandas matplotlib pyecharts wordcloud jieba pillow
第二步,登录并获取好友列表:
python复制import itchat
import pandas as pd
# 登录。hotReload=True会把登录状态缓存到本地文件,短时间内再次运行不用重新扫码。
itchat.auto_login(hotReload=True)
# update=True会强制从服务器刷新好友列表,不带这个参数默认走本地缓存。
friends = itchat.get_friends(update=True)
print(f"总共获取到 {len(friends)} 条记录")
这里有一个重要的细节:friends列表的第一条记录不是好友,而是你自己的账号信息。itchat.get_friends()返回的列表中索引0是当前登录账号自己,从索引1开始才是真正的好友列表。我第一次没注意这个,把统计结果里多算了一个“自己”,导致百分比失衡,排查了半天。
拿到原始数据后,我习惯先把所有字段转成DataFrame看一眼结构:
python复制df = pd.DataFrame(friends[1:])
df.to_csv('friends_raw.csv', index=False, encoding='utf-8-sig')
print(df.columns.tolist())
itchat返回的每条好友记录包含几十个字段,虽然很多字段没什么用,但像NickName、Sex、Province、City、Signature、HeadImgUrl这些核心字段基本都有。拿到这些,就完成了数据采集最重的一步。
2.3 备用方案:基于备份文件导出的思路
如果itchat用不了,网上还有一些基于本地备份文件的开源工具,原理是把手机或电脑上微信的聊天记录备份文件导出,再解析成可读的HTML或数据库。这类工具中比较知名的是GitHub上“留痕”系列,它能把微信聊天记录解密并导出成网页版,顺带也包含好友信息表。
我自己测试过这类工具,效果还算稳定,但安装过程比较折腾,需要用到加密数据库的密钥提取。这里不展开具体步骤,只给你一个思路:如果你连itchat扫码都过不了,就去搜微信聊天记录导出、备份解析相关的开源项目,把导出的结果转换成表格后,后面的清洗和分析流程完全一样。数据源不同,但分析框架是通用的。
3. 数据清洗与预处理:90%的时间都花在这
3.1 认识原始数据字段
拿到原始数据后,别急着画图。先花几分钟看看字段和脏数据情况。我通常会执行:
python复制df = pd.read_csv('friends_raw.csv')
# 查看缺失值情况
print(df[['Sex', 'Province', 'City', 'Signature']].isnull().sum())
# 查看唯一值
print(df['Sex'].value_counts())
print(df['Province'].value_counts().head(20))
这一步能快速发现几个常见问题。第一,Sex字段是数字编码:1代表男,2代表女,0代表未设置。但要注意,很多账号没有设置性别,这个比例高的时候能到20%以上,属于正常的脏数据。第二,Province和City字段大量为空,有些是“海外”,有些是“”空字符串,这些后面都要统一处理。第三,Signature看起来是文本,但内部混入了很多表情符号用[表情]之类的占位符表示,甚至有些签名就是一堆乱码。
3.2 性别、地区、签名的清洗策略
性别字段清洗最简单,直接做映射:
python复制sex_map = {1: '男', 2: '女', 0: '未知'}
df['sex_label'] = df['Sex'].map(sex_map)
这里我特意把“未知”保留而不是直接删掉。因为真实场景里,性别缺失并不代表数据错误,如果直接删除,统计口径就不全了。后续分析时可以选择只看男和女的数量,但在终版报告里我会把“未知”单列出来,让看图的人知道数据空白率是多少,这是数据分析的一个基本素养。
地区字段的清洗相对麻烦。我做了三件事:第一步,把空字符串统一替换为NaN;第二步,把Province和City串成一个完整的“省份-城市”字段;第三步,把“”这种特殊符号清理掉。
python复制df['Province'] = df['Province'].replace('', None)
df['City'] = df['City'].replace('', None)
df['location'] = df['Province'].fillna('') + '-' + df['City'].fillna('')
df['location'] = df['location'].str.strip('-')
这里有个细节:省份和城市在微信里是分开存的,有些人只填了省份,有些人只填了城市,所以拼接后要处理多余的短横线。
个性签名的清洗更有意思。我主要做两步:第一步是去掉表情占位符,比如[表情]、[微笑]这类,以及所有非中文字符;第二步是过滤空签名,因为大约有1/3的好友签名是空白。清洗后的签名才适合送到分词工具里做关键词提取。
python复制import re
def clean_signature(s):
if not isinstance(s, str):
return ''
s = re.sub(r'\[.*?\]', '', s) # 去掉表情占位符
s = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', s) # 只保留中英文和数字
return s.strip()
df['sig_clean'] = df['Signature'].apply(clean_signature)
3.3 头像批量下载与预处理
头像数据是很多人容易忽略的“富矿”。每个好友的头像URL存储在HeadImgUrl字段里,可以通过HTTP请求下载下来。这批头像最后可以做一张“好友头像墙”,视觉效果非常震撼。
下载头像时我踩过一个比较隐蔽的坑:itchat返回的HeadImgUrl是相对路径,前面要拼上https:前缀才能访问。如果直接拿原字段下载,会报404。
python复制import requests
import os
import time
os.makedirs('avatars', exist_ok=True)
for idx, row in df.iterrows():
url = 'https:' + row['HeadImgUrl']
try:
resp = requests.get(url, timeout=5)
if resp.status_code == 200:
with open(f'avatars/{idx}.jpg', 'wb') as f:
f.write(resp.content)
except Exception as e:
print(f'下载失败: {idx}, {row["NickName"]}, {e}')
time.sleep(0.1) # 控制请求频率,避免被封
下载过程中会有个别头像地址失效,导致保存成空文件。别慌,这属于正常损耗。后期生成头像墙时,只需要判断文件大小不为0的文件即可。
4. 可视化实战:把分析结果变成图
4.1 好友性别分布:柱状图与玫瑰图
数据清洗完毕,先做一个最简单的性别分布图。这里我用两种方式展示同一个数据:传统的柱状图和南丁格尔玫瑰图。玫瑰图实际是极坐标系下的柱状图,当数据是分类变量并且希望突出“占比差异”时,玫瑰图的效果比柱状图好很多,这也是pyecharts里比较讨喜的图形。
python复制from pyecharts.charts import Bar, Pie
from pyecharts import options as opts
sex_counts = df['sex_label'].value_counts()
# 柱状图
bar = (
Bar()
.add_xaxis(sex_counts.index.tolist())
.add_yaxis("好友数", sex_counts.values.tolist())
.set_global_opts(title_opts=opts.TitleOpts(title="微信好友性别分布"))
)
bar.render('sex_bar.html')
pyecharts生成的是交互式HTML文件,双击可以直接在浏览器里打开,比matplotlib的静态图好看得多。但注意一点:在Jupyter Notebook里,需要设置render_notebook(),在脚本里则是render('xxx.html')。
玫瑰图就是把Pie改成带半径的扇区:
python复制pie = (
Pie()
.add(
"性别",
[list(z) for z in zip(sex_counts.index.tolist(), sex_counts.values.tolist())],
radius=["20%", "60%"],
rosetype="radius",
)
.set_global_opts(title_opts=opts.TitleOpts(title="微信好友性别分布玫瑰图"))
)
pie.render('sex_rose.html')
4.2 好友地区分布:地图与Top榜单
地区分布是这个项目里最有“地理感”的分析。我的做法是:先按省份聚合并统计数量,然后把数据读取到pyecharts的Map组件里展示中国地图热力图。另一个视角是统计城市Top10,用条形图展示。
省份聚合一步到位:
python复制province_counts = df['Province'].fillna('未知').value_counts()
map_chart = (
Map()
.add(
"好友数量",
[list(z) for z in zip(province_counts.index.tolist(), province_counts.values.tolist())],
maptype="china",
)
.set_global_opts(
title_opts=opts.TitleOpts(title="微信好友省份分布"),
visualmap_opts=opts.VisualMapOpts(max_=100),
)
)
map_chart.render('province_map.html')
有一个版本问题要提醒:pyecharts的新版本把中国地图文件拆成了独立的包,如果你直接跑上面代码报错LazyLoad dict is empty,需要先执行:
bash复制pip install echarts-countries-pypkg echarts-china-provinces-pypkg echarts-china-cities-pypkg
或者更推荐的是安装全国地图包:
bash复制pip install pyecharts_snapshot
不过这属于老版本解法,新版本pyecharts已经默认内置了地图数据,如果报错,优先查一下pyecharts的版本是否有更新。
城市Top10的条形图没什么难度,但这里我建议加一个“好友所在城市密度”的角度,即用城市名加省份名一起展示,避免同名城市造成误读。比如“南京-江苏”和“南京-福建”其实是两个完全不同的地方。
4.3 个性签名词云与头像墙
个性签名是特别能反映好友“气质”的字段。处理方式很简单:把所有签名拼接成一个长文本,用jieba分词,过滤掉停用词,最后丢给wordcloud生成词云。
python复制import jieba
from wordcloud import WordCloud
import numpy as np
from PIL import Image
text = ' '.join(df['sig_clean'].dropna().tolist())
# 使用jieba分词,过滤单字和常见停用词
words = jieba.cut(text)
words = [w for w in words if len(w) > 1 and w not in stop_words]
filtered_text = ' '.join(words)
# 指定中文字体,否则词云会出现乱码
wc = WordCloud(
font_path='C:/Windows/Fonts/simhei.ttf',
width=1200,
height=800,
background_color='white',
max_words=200,
)
wc.generate(filtered_text)
wc.to_file('signature_wordcloud.png')
这里有几个容易翻车的细节。第一是字体:wordcloud默认字体不支持中文,如果不指定font_path,最后生成的就是一排小方块。最好是自己下载一个“黑体”或“宋体”的ttf文件放项目目录里,用相对路径引用,避免在服务器上没有Windows字体可用。第二是分词质量:jieba对网络用语和新词的切分效果一般,“打工人”“摸鱼”这类词可能会被拆开,必要时可以在jieba词典里手动加入这些词。第三是停用词表:网上有现成的中文停用词表,但签名数据里的高频无效词通常是“每天”“一个”“还是”“真的”这类,最好结合自己数据多跑两遍,手动补充停用词。
头像墙的做法比想象中简单。原理就是把所有好友头像缩小成等尺寸的小图,然后用Pillow在一块画布上一张一张地粘贴。核心代码如下:
python复制from PIL import Image
import glob
avatars = glob.glob('avatars/*.jpg')
size = 100 # 每张头像边长
cols = 20
rows = (len(avatars) + cols - 1) // cols
canvas = Image.new('RGB', (cols * size, rows * size), 'white')
for i, path in enumerate(avatars):
x = (i % cols) * size
y = (i // cols) * size
try:
img = Image.open(path).resize((size, size))
canvas.paste(img, (x, y))
except Exception as e:
continue
canvas.save('avatar_wall.jpg')
注意,头像文件里可能有下载失败的空文件,Image.open会直接抛异常,这里用try...except跳过是合理的。另外,如果好友数量很多,比如超过1000个,建议把每张头像的边长缩小到50,不然最后生成的图片会巨大,浏览器浏览器打开或者缩放都会卡。
4.4 进阶玩法:把多张图表拼成可视化大屏
单张图表的展示力有限,把多张图表拼成一个可视化大屏,才是真正的“项目亮点”。这里说的不是用PS硬拼,而是用pyecharts的Page或Grid组件,把多个图表统一渲染进一个HTML页面。
我常用的是Page的drag模式,它可以让你在浏览器里用鼠标拖动每张图的位置,非常适合做“可自由布局的分析报告”:
python复制from pyecharts.charts import Page
page = Page(layout=Page.DraggablePageLayout)
page.add(bar, pie, map_chart)
page.render('friend_analysis.html')
渲染完成后,浏览器打开friend_analysis.html,可以手动拖拽图表位置。拖好之后,右上角会有一个“save config”按钮,点击后会生成一个JSON配置文件。把这个文件保存下来,再用page.save_resize_html方法重新渲染一次,就能得到一张布局固定的大屏页面:
python复制Page.save_resize_html(
"friend_analysis.html",
cfg_file="chart_config.json",
dest="friend_analysis_final.html",
)
这套操作比较小众,网上资料少,我第一次做的时候卡了很久。实际上pyecharts的官方文档里就有一页专门讲这个,路径是“复杂布局-拖拽图表”,建议直接看官方文档,比我说的更好理解。
5. 常见问题与排查手册
5.1 登录、接口与数据完整性
问题一:扫码登录后提示“该微信号无法登录网页版”
这是目前最常见的失败场景。微信对web端的支持逐步收缩,很多账号没有登录网页版的权限。出现这种情况,一是用身边其他朋友的账号测试,确认是否账号权限问题;二是直接切换备用方案的导出工具,不要死磕itchat。另外特别提醒:如果某个账号能扫码登录,也不要频繁切换设备或短时间内多次登录,容易被风控。
问题二:hotReload失效,每次都要重新扫码
hotReload=True会把登录状态缓存到itchat.pkl文件。如果这个文件被删除,或者登录状态过期,就需要重新扫码。另外,如果你在运行过程中再次调用itchat.auto_login(),它也有可能重置状态。解决办法是:把获取数据这一步和后续分析分开,获取完数据后立刻导出csv,后面基于csv做分析和可视化,不要一直依赖登录态。
问题三:好友数量比通讯录里少很多
抛开公众号、服务号不说,web微信接口返回的好友列表本身就不包含部分特殊类型联系人,比如企业微信联系人、部分设备账号。另外,如果你用了“仅聊天”权限,可能也拉不到全部信息。所以最终统计结果不等于手机通讯录的总数,这是正常的,不必纠结。
5.2 中文显示与图片处理
问题四:csv文件用Excel打开是乱码
这几乎是我见过的最常见问题。解决办法不是改Excel的编码设置,而是在to_csv的时候指定编码为utf-8-sig:
python复制df.to_csv('friends_clean.csv', index=False, encoding='utf-8-sig')
utf-8-sig会在文件开头加一个BOM头,Excel识别BOM后就会用UTF-8解码,乱码问题就消失了。
问题五:pyecharts图表在Jupyter里不显示
render('xxx.html')是生成文件,要在Notebook里直接看,需要用render_notebook()。如果你用的是新版本pyecharts,还需要确认notebook环境已经安装相应依赖,一般升级pyecharts到最新版即可。
问题六:词云生成后全是方块
这基本就是字体问题。确认font_path指向的字体文件存在且是ttf格式,尽量不要用.ttc字体文件,wordcloud对.ttc的支持不太好。我通常把字体文件复制到项目根目录,用font_path='simhei.ttf'这种相对路径,最稳。
问题七:pyecharts的Map图省份数据不对
Map组件需要的数据格式是“省份名+数值”,省份名必须和地图内置的名称一致。比如“内蒙古自治区”和“内蒙古”在pyecharts的地图数据里对应关系比较微妙,建议自己打印pyecharts.datasets里的标准省份名称,或者把数据里的省份名统一处理后再传给Map,否则会出现“地图上显示不出来”的尴尬情况。
5.3 隐私与合规提醒
整个项目做下来,你手里已经有了一份包含好友昵称、头像、地区、签名、性别等信息的完整数据集。这里面有大量真实个人的隐私数据。这里郑重提醒几件事:第一,这个项目只适合分析自己的账号数据,不要拿别人的账号或数据随便跑;第二,生成的可视化结果如果要发朋友圈或博客,务必要做脱敏处理,尤其是好友头像墙这种图,涉及大量真人头像,网上公开发布前一定要三思;第三,不要利用好友数据分析做任何营销推广、骚扰、用户画像倒卖之类的事情,一旦越过边界,性质就完全变了。
我做头像墙的时候,最开始没想太多,直接把成品图发给了几个要好的朋友,结果发现大家的第一反应不是“哇好酷”,而是“我的头像在哪个位置,你有没有存我头像”。这让我意识到,在技术极客眼里这只是图片拼贴,但对普通人来说,这是对自己的数据被怎么使用非常敏感的。所以整个项目最后我还是把公开分享的头像墙做了模糊化处理,只保留所有人轮廓,这既保护朋友隐私,也避免自己惹上不必要的麻烦。
这个项目后续还可以继续扩展的方向有很多,比如如果你能导出聊天记录,可以做好友聊天频率分析、关键词热度分析,甚至利用好友的互动数据构建关系网络图;如果账号好友量级很大、达到几万级别,再把数据导入到Spark里做分布式统计分析也不迟;如果想把图表做到实时刷新,可以尝试把数据接进Kafka消息队列,用流处理方式驱动大屏更新。但这些都是另一个量级的工程了,先把离线分析这步走扎实,比什么都强。
最后再说一个小技巧。整个项目跑通后,我会把关键的分析代码封装成一个analyze.py脚本,输入csv路径,自动输出所有图表文件。以后但凡想再分析一次,只要数据更新了,跑一遍脚本就能拿到一套全新的可视化报告,不用每次都从登录那步开始重复操作。这其实也是数据分析工作的一个常态:一次开发,重复使用,把繁琐流程沉淀成工具,剩下的时间就可以放在解读数据、挖掘洞察上了。
