简介这是为Python数据分析学习者与社交网络爱好者准备的微信好友分析实践资源。压缩包内共5个文件一个核心脚本analysis_friends.py三个已生成的HTML报告分别对应好友城市、性别、省份分布以及一个pyecharts依赖库whl格式整体大小仅2.79MB轻量易存。其中whl文件有助于离线安装依赖避免环境配置问题三个HTML页面可直接用浏览器打开即使不运行代码也能看到可视化结论。脚本演示了从好友数据清洗、统计到图表输出的典型流程借助pyecharts生成交互式图表直观展现好友地域分布、性别比例等社交画像。资源既适合作为Pandas与可视化库的实战练习也可以修改数据源后复用为个人社交网络分析模板甚至进一步扩展至聊天记录的关系挖掘。目前已有71人学习下载内容充实适合希望快速上手微信数据探索的读者。1. 微信好友分析这份 Python 资源到底能拆出什么拿到「微信好友分析.rar」这个压缩包时第一反应是看看里面有什么——五个文件一个analysis_friends.py核心脚本、三个已经渲染好的 HTML 报表、一个pyecharts-0.1.9.4的离线 wheel 包。这个项目做的事很直接用 Python 登录微信网页接口把好友的性别、省份、城市、签名拉下来再用 pyecharts 生成三个可交互的网页图表。做出来之后你能很直观地看到自己的好友里男女比例是多少、集中在哪些省份和城市、签名里大家最爱写什么。它适合两类人一是刚开始学数据分析、想拿真实社交数据练手的新手二是要做用户画像或私域运营、需要快速了解好友基本盘的从业者。代码量不大但坑不少下面先从代码结构和登录方式拆起。2. 先看懂这套代码再动手从登录鉴权到好友信息落地2.1 压缩包里的五件套与依赖定位解压后直接面对的就是这五个文件先把它们各自的身份弄清楚后面跑代码时才知道哪个是干活的、哪个是垫脚的。文件类型用途analysis_friends.pyPython 脚本主程序负责登录、采集、统计、渲染analysisSex.html网页性别分布的饼图或环形图analysisProvince.html网页省份分布的地图或柱状图analysisCity.html网页城市分布的柱状图或列表pyecharts-0.1.9.4-py2.py3-none-any.whl离线依赖包pyecharts 0.1.9.4 安装包供离线环境安装这个 wheel 包的版本信息很关键。pyecharts-0.1.9.4是 2017 年前后的老版本那时 pyecharts 的 API 风格还是Pie、Bar、Map这类直接可用的图表类渲染出一个完整独立的 HTML 文件。如果你现在的环境里已经装了新版的 pyecharts比如 1.x 或 2.x那 API 全变了老脚本直接跑不起来。所以压缩包自带这个 wheel就是要让你在干净环境里装这个特定版本避免版本漂移。安装方式很简单先把压缩包解压到一个目录然后在终端里执行# 先确认 Python 版本建议 3.6 到 3.8 之间 python --version # 安装压缩包里的旧版 pyecharts pip install pyecharts-0.1.9.4-py2.py3-none-any.whl # 安装其他必要依赖 pip install wxpy pandas这里wxpy是个关键依赖它是itchat的升级封装底层还是走微信网页版协议用于扫码登录和拉取好友数据。之所以要用 wxpy 而不是直接调 itchat是因为 wxpy 的接口更友好bot.friends()直接返回好友对象列表每个对象身上带着sex、province、city、signature这些属性省去了自己解析原始 JSON 的步骤。注意wxpy 依赖的 itchat 协议只对个人微信号有效被限制登录网页版的账号会直接报错或提示需要短信验证。建议用一个不常用的小号来跑。2.2 登录与数据采集核心调用逻辑接下来打开analysis_friends.py它的核心逻辑其实就是三个步骤登录、遍历好友、把每个好友字段写进一个结构化的数据容器里。下面这段代码是从实操角度复刻的等价写法便于理解每一行在干什么from wxpy import Bot # 扫码登录cache_path 会把 session 持久化到本地 # 这样同一台机器短时间内不用反复扫码 bot Bot(cache_pathTrue) # 只获取纯好友列表不包含群聊和公众号 friends bot.friends() data [] for f in friends: data.append({ nickname: f.nick_name, remark: f.remark_name, sex: 男 if f.sex 1 else (女 if f.sex 2 else 未填), province: f.province, city: f.city, signature: f.signature, username: f.name }) # 打印一个统计预览确认抓取数量正常 print(抓取好友数:, len(data)) print(字段样例:, data[0])从代码可以看到bot.friends()返回的是一个Friend对象列表每个Friend的属性在运行时已经填好不需要额外发请求。这里最需要留意的三个参数是cache_pathTrue把登录凭证序列化到本地生成一个wxpy.pkl或类似缓存文件。如果设为 False每次跑脚本都要重新扫码。f.sex微信里性别现在有 1 男、2 女其余情况包括未设置、公众号等特殊类型是 0 或不存在。f.signature个性签名可能是纯文本可能带 emoji也可能为空字符串。它后面做词云或情感分析时是主料但编码问题也在它身上。数据采集完这几个字段就构成了后面所有统计的原料。要注意的是个别字段可能缺失比如remark可能是Nonecity可能是个空字符串「」而不是None这类脏值在统计之前必须统一处理否则后面做groupby时会出现一个名为「」的分组看起来非常突兀。2.3 字段边界哪些值可靠、哪些值要留个心眼微信好友信息里有几个字段的可靠性差别很大我先把自己踩过的边界说清楚。字段可靠程度说明nick_name高用户昵称基本都会返回remark_name中你自己设置的备注没设置时是Nonesex中高很多用户没填性别返回 0 或 31province低大量用户为「未知」或空字符串city低同样存在大量缺失且「北京」和「北京市」不统一signature中可能有空白、带 emoji 或超长文本其中sex字段有个特殊值「31」这是因为微信的协议里有些特殊账号会返回未知的其他数字统计时最好先把不等于 1 和 2 的值全部归到「未知」里不要硬写死if sex not in (1, 2)就跳过否则最后饼图里会出现一坨没有标签的空白扇区。省份和城市缺失的根源在于微信账号注册时很多用户关闭了地区展示或者直接选了「未知」作为省。这个字段本身就不是强制的所以拿到的大概率不是完整数据后面做地图时——特别是用 pyecharts 的Map时——要提前把「未知」这个值从数据里过滤掉否则地图组件会报一个匹配不上地理名词的警告。3. 把好友数据做成可视化pyecharts 0.1.9.4 的典型配置套路3.1 三个 HTML 分别对应什么图表解压出来已经带好了analysisSex.html、analysisProvince.html、analysisCity.html三个成品页面说明作者是跑过一次脚本的。从 pyecharts 0.1.9.4 的常见用法推断这三个文件分别对应analysisSex.html好友性别分布的环形图或饼图展示男、女、未知的比例。analysisProvince.html省份分布的中国地图热力图或柱状图用来展现地域集中度。analysisCity.html城市 TOP 排行榜柱状图把好友数量最多的几个城市列出来。这类图表的生成方式在老版本 pyecharts 里非常直白比如性别分布图的核心代码长这样from pyecharts import Pie # 统计各性别数量 sex_counts {男: 123, 女: 98, 未知: 45} # 创建饼图实例标题为好友性别分布 pie Pie(好友性别分布, title_poscenter) pie.add( 性别, list(sex_counts.keys()), list(sex_counts.values()), is_label_showTrue, legend_posright ) # 渲染成 HTML 文件 pie.render(analysisSex.html)Pie.add三个位置的参数第一个是系列名称第二个是数据标签列表第三个是数值列表。有一种容易踩的写法是把第一个参数传成「好友」之类的自定义字符串图表能渲染但图例和图内 label 就会变成这个奇怪的名字。另外is_label_showTrue是显示每个扇区的百分比或数值如果不打开图表上就只有色块没有数字阅读成本会高不少。地图和柱状图的做法同理Map.add接受省份名和数值的 pair 列表from pyecharts import Map # 只保留省份名非空的条目 province_data [(广东, 31), (浙江, 22), (北京, 18)] map_chart Map(好友省份分布, width800, height600) map_chart.add( 省份分布, province_data, maptypechina, is_visualmapTrue, visual_text_color#333 ) map_chart.render(analysisProvince.html)这里maptypechina告诉图表用中国地图老版本 pyecharts 内置了中国地图数据不会额外请求外链离线环境也能渲染。is_visualmapTrue开启右侧颜色标尺数值越大颜色越深这个是地图热力效果的关键开关。3.2 老版本 pyecharts 的配置参数速查0.1.x 版本的 pyecharts 在图表初始化时支持一系列全局配置参数比如title_pos、width、height、background_color等。如果你对着新版的文档去写老版代码会发现接口完全对不上。下面是我整理的一份适合这个压缩包的常用参数清单参数作用典型值title图表标题传字符串显示在页面上title_pos标题位置center或leftwidth/height图表尺寸数字或100%is_label_show是否显示数据标签True/Falselegend_pos图例位置right/left/centeris_visualmap是否显示色阶表True/False地图常用visual_text_color视觉标尺文字颜色#333注意is_label_show和show_label是两码事。老版本里is_label_show控制的是每项数据上的文字显示而show_label是另一个控制图例边的 label 的参数两个名字相近含义完全不同。如果发现图表上数据值不显示优先检查是不是把这个参数设成了False。3.3 中文乱码与字体渲染问题生成 HTML 图表后打开浏览器可能会出现中文标签变成方块字或乱码的情况。这个问题的根源不在 pyecharts而在于 HTML 文件的字符声明和操作系统的默认字体渲染。0.1.9.4 版本生成的 HTML 文件头默认带charsetUTF-8正常情况下不会乱码但如果你的代码里用了open()以gbk方式写文件或者手动拼接 HTML 片段那就会出事。我一般会在代码里强制指定编码写入避免 IDE 默认编码不一致导致的乱码# 用 utf-8 显式写入避免 Windows 默认 gbk 导致 HTML 乱码 with open(analysisSex.html, w, encodingutf-8) as f: f.write(pie.render_embed())render_embed()会返回渲染好的页面内容再用自己控制的文件对象写入。这样做有个好处可以顺便做点二次加工比如替换默认模板里的标题、加一段说明文字再落盘。如果直接在浏览器里打开后字体是乘号方块说明系统缺少中文字体一般换到 Linux 服务器的无头环境时会出现本地 Windows 或 macOS 基本没有这个问题。4. 统计口径与边界性别分布、TOP 城市为什么不能直接信4.1 「未知」和空字符串必须前置处理把好友数据跑出来之后第一件事不是画图而是先看统计口径。很多新手拿到脚本直接跑图能出来但男性和女性的总和加上「未知」的数不等于好友总数就开始怀疑代码是不是抓漏了。其实大概率是数据本身的问题——性别字段存在未填、异常值省份字段存在「未知」、空字符串、以及「北京市」和「北京」这种同义不同字的情况。处理脏值的中立办法是统一归类后再统计。比如性别字段先把所有非 1、2 的数值替换成「未填」import pandas as pd # 假设 data 是前面抓到的列表转成 DataFrame df pd.DataFrame(data) # 性别归一非 1/2 一律算未知 df[sex] df[sex].apply(lambda x: 男 if x 1 else (女 if x 2 else 未知)) # 省份归一空字符串、未知、None 全部归为「未知」 df[province] df[province].apply( lambda x: 未知 if x in (None, , 未知, none) else x ) # 城市字段同样处理同时去掉前后空白 df[city] df[city].apply( lambda x: 未知 if x in (None, , 未知) else str(x).strip() ) # 看下各归一类之后的分布 print(df[sex].value_counts()) print(df[province].value_counts().head(10))这里 lambda 表达式写得比较保守没有做更多的模糊匹配。真实场景里你可能还会遇到「广东 广州」这种带空格的城市名或者「内蒙古」被简写成「内蒙」这类情况需要自己在代码里维护一个别名映射表逐一替换。注意这个归一化步骤必须放在统计之前否则后面所有计数都是错的。我自己习惯在抓数据后先跑一个df.info()看缺失值总数再决定要清洗哪些列。4.2 公众号和群聊混入导致数量虚高wxpy 的bot.friends()默认返回的是纯好友但很多教程里会把bot.chats()拿来的对象也塞进去里面混着群聊和公众号。如果被混入的是公众号它的sex通常是 0province也是空结果你的饼图里「未知」占了一大片TOP 城市榜单也被「未知」霸榜。这个现象背后不是数据没抓到而是过滤条件不严。血泪经验是凡是涉及好友统计的脚本必须在采集阶段就区分对象类型。bot.friends()是正确做法但如果代码里有bot.chats()或bot.groups()的调用就要看清楚返回对象是不是被当成好友在统计了。严谨的过滤方式是这样的from wxpy import Friend # 只保留 Friend 类型排除群聊和公众号 real_friends [f for f in friends if isinstance(f, Friend)] print(纯好友数量:, len(real_friends))isinstance(f, Friend)是最稳妥的判据不要靠判断f.raw.get(PYQuanPin)是否存在来区分因为公众号和部分特殊账号也会带这个字段。数据量特别大的时候判断对象的类型比判断字段值更可靠。4.3 城市名标准化同一城市三个写法的问题城市统计的痛点不是缺失而是不一致。同一座城市在微信字段里可能叫「北京」、可能叫「北京市」、也可能叫「北京城区」刷出来三根柱子外行看着像三个地方内行一看就知道是一处。这里可以使用一个简单的别名映射来统一# 城市别名映射表按需扩充 city_alias { 北京市: 北京, 北京城区: 北京, 上海城区: 上海, 上海市: 上海, 重庆市: 重庆, 天津城区: 天津, # 其他城市按同样的思路补充 } def normalize_city(name): name str(name).strip() return city_alias.get(name, name) df[city] df[city].apply(normalize_city)把「北京城区」「北京市」都归一成「北京」之后TOP 城市的排名才有参考价值。补充别名映射表的过程比较繁琐常见做法是从df[city].value_counts()里挑出 top 30挨个看看有没有同城异名的条目然后补进映射表。纯手工但有效没有捷径。4.4 可视化 HTML 只是静态快照一个比较容易忽略的边界是analysisSex.html、analysisProvince.html这些产物是静态快照数据抓完后固定下来不会实时更新。如果你隔几天再跑一次脚本新的 HTML 会覆盖旧的。需要注意的是页面内嵌了 echarts 的 js 库老版本 pyecharts 默认是把整个 echarts.min.js 打进去了所以单个 HTML 文件体积可能很大打开时如果显示空白先检查文件大小是不是正常再考虑编码问题。3MB 到 5MB 都属于正常范围如果只有几十 KB大概率渲染失败了。5. 微信好友分析避坑实录登录、渲染、编码三类高频事故5.1 扫码登录后脚本立即退出二维码刷新太快现象运行脚本弹出二维码刚扫完码程序就抛异常退出提示LoginFailed或KeyError第二次运行时二维码直接不出现提示缓存已失效。原因微信网页版对登录 session 有较严格的有效期限制。cache_pathTrue生成的缓存文件在短时间内频繁登录或被多设备同时登录时会失效。异常信息里的KeyError多半是在解析登录响应时缺少了期望字段本质上是微信服务端对扫码次数做了限制。解决换一个微信号重试前先把本地的wxpy.pkl缓存文件删除。删除后重新扫码一般能解决。如果仍失败检查是否在调试过程中多次扫码触发了风控暂停几分钟再跑。代码里可以加一个兜底提示import os, wxpy # 清理旧缓存避免失效缓存干扰新登录 if os.path.exists(wxpy.pkl): os.remove(wxpy.pkl) bot wxpy.Bot(cache_pathTrue) print(登录成功开始抓取好友数据)从那以后我每次跑脚本前都会强制走一遍缓存清理流程宁多删不残留。5.2 生成的 HTML 在浏览器里打开是白屏现象pyecharts 正常执行render没有报错但生成的 HTML 用浏览器打开后是一片空白控制台报ReferenceError: echarts is not defined。原因0.1.9.4 版本的render()方法在某些平台上生成的是「未压缩依赖版」的 HTML它把 echarts 库以外部引用方式加载而不是内嵌。如果你双击打开 HTML 时处于离线环境外部 js 加载失败页面自然空白。解决用render_embed()代替render()前者会把 echarts 库完整内嵌到 HTML 中彻底摆脱外部依赖# 使用嵌入方式渲染页面自带 echarts 库离线也能打开 with open(analysisSex.html, w, encodingutf-8) as f: f.write(pie.render_embed())这个改动虽然会增大 HTML 文件体积但换来了离线可用性。对压缩包里自带的 HTML 文件如果打开也是白屏直接重新执行一遍脚本再生成即可。5.3 个性签名里带 emoji 导致写入报错现象signature字段里包含 emoji 表情字符代码里如果用str.encode(gbk)写入文件会报UnicodeEncodeError如果是把签名写进 pandas DataFrame再to_csv则可能生成乱码文件。原因emoji 是 Unicode 超出 BMP 平面的字符GBK 编码无法表达。而 pandas 的to_csv在 Windows 上默认编码是gbkemoji 字符直接翻车。解决统一使用 UTF-8 编码写入所有文件。如果签名本身不是分析重点可以在清洗阶段直接用正则把非 BMP 字符剥离掉import re # 移除 emoji 等非 BMP 字符保留常规中英文和标点 def clean_signature(text): if not isinstance(text, str): return # 将文本编码为 utf-8 后再解码同时丢弃无法编码的 emoji text text.encode(utf-8, ignore).decode(utf-8) # 过滤掉控制字符和特殊符号 text re.sub(r[\u0000-\u001f\u007f], , text) return text.strip() df[signature] df[signature].apply(clean_signature)这里先encode(utf-8, ignore)会把 emoji 丢弃成空字符串再decode回来保证后续操作正常。如果是做词云把 emoji 直接丢掉影响不大因为分词器本身也处理不了它们。5.4 同一脚本在不同 Python 版本下行为不一致现象在 Python 3.7 下正常跑通的脚本换到 Python 3.11 后Bot()登录直接报错或者pyecharts导入时报缺模块。原因wxpy 的底层 itchat 依赖了旧版 websocket 库和加密模块在高版本 Python 下某些依赖库不再维护存在兼容性问题。pyecharts 0.1.9.4 则是依赖了flask和jinja2的旧接口新版jinja2改了Markup的导入路径导致模板渲染失败。解决给这个项目单独建一个虚拟环境Python 版本锁定在 3.6 到 3.8 之间。venv 创建和依赖安装命令如下# 创建 Python 3.7 虚拟环境 python3.7 -m venv wechat_env source wechat_env/bin/activate # Windows 下用 wechat_env\Scripts\activate # 先装 wheel 再装其余依赖 pip install pyecharts-0.1.9.4-py2.py3-none-any.whl pip install wxpy pandasPython 3.8 以上跑 wxpy 在登录阶段会有概率报No module named websocket的兼容问题虽然不是必现但花时间排查不如直接锁版本。这个虚拟环境的做法对任何基于老库的项目都适用算是通用后悔药。6. 把分析结果做深一层好友特征向量与简单聚类画像有了性别、省份、城市、签名这些字段之后除了画饼图和地图还能做一件信息量更大的事——把每个好友表示成一组特征向量然后做聚类看看自己的好友圈子里是不是天然存在几个特征分明的小群体。这一步不需要额外采集数据只用现有字段工具换成scikit-learn就行。常见的做法是把每个好友映射成一个多维特征向量维度包括性别编码男/女/未知、省份编码、签名长度、是否填了城市等。签名本身不做复杂自然语言处理先只用长度和是否为空这两个简单特征跑出一版粗糙但可解释的聚类结果from sklearn.feature_extraction.text import CountVectorizer from sklearn.cluster import KMeans # 构造特征矩阵性别 省份 签名长度 df[sex_code] df[sex].map({男: 0, 女: 1, 未知: 2}) df[province_code] df[province].astype(category).cat.codes df[sig_len] df[signature].apply(lambda x: len(str(x))) df[has_sig] df[signature].apply(lambda x: 1 if str(x).strip() else 0) # 取四列作为特征 features df[[sex_code, province_code, sig_len, has_sig]].fillna(0) print(特征矩阵维度:, features.shape) # 分成 4 个簇随机种子固定方便复现 kmeans KMeans(n_clusters4, random_state42, n_init10) df[cluster] kmeans.fit_predict(features) # 输出每个簇的规模和平均特征快速判断画像 print(df.groupby(cluster)[[sig_len, sex_code, has_sig]].mean())KMeans的几个参数值得单独说。n_clusters4表示把好友分成四群这个数字需要自己试我一般从 3 开始试到 6配合轮廓系数选一个解释性最强的random_state42保证不同次运行聚类结果一致不然每次跑出来的群成员都在变n_init10是 scikit-learn 新版本为了避免局部最优默认使用的初始化次数老版本没有这个参数如果是 1.2 以下版本可以去掉。聚类结果出来之后可以把每个簇的典型特征打印出来比如「第 0 簇全是女性、签名普遍较长、集中在广东」这就是好友画像的粗版本。更进一层可以把省份字段里的 top 城市做成独热编码丢进特征矩阵但维度会膨胀此时需要先给城市名做低频合并——只保留频率超过 5 的城市其余统一归为「其他」否则稀疏矩阵会让聚类结果被极少量的冷门城市主导。这一步相当于给画像做了一次降噪。这类特征画像的实际价值不止是「更了解自己的好友」这么简单。做私域运营的人可以用它快速区分好友里的活跃用户、沉默用户、同城用户给后续分层运营提供数据支撑做社交产品的人可以用同样套路分析样本用户的好友结构判断产品渗透率。从这份资源延伸出去的玩法其实比压缩包里几个 HTML 文件要宽得多。如果要把聚类结果也导出成 HTML别忘了老版本 pyecharts 没有直接画散点矩阵的接口可以先画一个改版柱状图横轴是簇 ID纵轴是簇内人数用颜色区分性别占比。这个图放在analysisCity.html配套展示整个分析流程才算闭环。从一开始拿「微信好友分析.rar」只为了看性别饼图到后来把它扩展成好友画像工具这个项目的价值在于数据是真实的流程是完整的坑是密集的——恰好是练手和落地都需要的组合。完整的脚本、依赖 wheel 和示例输出都在资源包里直接解压就能跑。希望帮到你。本文还有配套的精品资源点击获取
