微信好友数据分析:本地备份解析与关系图谱构建

发布时间:2026/10/9 11:48:45
微信好友数据分析:本地备份解析与关系图谱构建
简介本资源是一套基于Python的微信好友社交网络分析实践方案面向数据分析初学者与Python进阶学习者聚焦社交媒体数据提取、清洗、关系建模与可视化等核心能力训练。压缩包共5个文件含3个HTML可视化报告分别呈现城市、省份、性别分布、1个Python脚本analysis_friends.py实现好友信息解析与基础统计以及1个pyecharts离线whl安装包确保无网络环境也可生成交互图表。整体包体仅2.79MB轻量易部署适合作为课堂实验、自学项目或竞赛数据预处理模块参考。目前已有71人学习下载提供开箱即用的完整分析链路从原始数据结构理解、Pandas清洗逻辑、NetworkX简易图构建到PyEcharts多维度可视化输出附带可直接运行的代码与即点即看的HTML结果页显著降低微信社交数据分析的入门门槛。1. 微信好友分析不是爬虫不是逆向是本地数据合规解析的实操路径“微信好友分析.rar”这个标题在技术圈里常被误读成「黑盒工具包」或「一键导出通讯录」——但现实是微信官方从未开放好友关系图谱的批量导出接口iOS 端无文件系统访问权限安卓端自 Android 11 起强制沙箱隔离任何声称“全自动抓取全部好友昵称、地区、签名、头像、备注”的方案99% 要么依赖已失效的旧版辅助功能AccessibilityService逻辑要么暗藏非授权数据上传行为。真正可持续、可复现、不触发风控的微信好友分析只有一条路从用户主动导出的「微信聊天记录备份」中提取结构化的好友元数据并严格限定在本地完成清洗、关联与可视化。本方案面向的是有 Python 基础的数据分析者、社群运营人员、或做用户画像研究的产品同学——你不需要 root/越狱不调用任何第三方云服务所有操作在自己电脑上完成输入是微信官方「备份与恢复」生成的加密备份文件.db 或 .sqlite输出是带地理分布热力、活跃度分层、标签聚类的本地 HTML 报告。它解决的不是“怎么偷数据”而是“如何把微信给你的那一小块合法数据榨出最大业务价值”。2. 从微信备份文件中提取好友基础信息解密、解析与字段对齐微信的本地备份Windows/macOS 上通过「微信PC版 → 设置 → 备份与恢复 → 备份聊天记录至电脑」生成本质是一个 SQLite 数据库存档但关键表如Contact、Friend、ChatRoom被 AES-256-CBC 加密密钥由设备硬件 ID 和微信登录态动态派生无法暴力破解也无法跨设备通用。因此必须使用微信官方认可的解密路径通过微信 PC 客户端自身导出的「明文备份」机制。这不是漏洞利用而是微信为用户迁移设计的合规出口。2.1 获取明文好友数据库的三步闭环流程提示此流程仅适用于 Windows 系统macOS 需额外适配 Keychain 解密逻辑暂不展开Linux 不支持微信 PC 版。全程无需安装第三方解密工具不修改注册表不注入进程。确保微信 PC 版已登录且完成一次完整备份启动微信 PC 版 → 登录账号 → 进入「设置 → 备份与恢复」→ 点击「备份聊天记录至电脑」→ 选择任意目录如D:\WeChatBackup→ 等待进度条走完。此时目录下会生成类似Backup_20240512143022的时间戳子文件夹。定位并复制Contact.db明文副本微信 PC 版在备份过程中会将联系人元数据以明文 SQLite 形式暂存于内存映射区并在备份结束时写入一个临时位置。该文件不加密且结构稳定经 8.0.48 ~ 8.0.52 多版本验证。路径固定为%APPDATA%\Tencent\WeChat\Contact.db注意%APPDATA%默认指向C:\Users\用户名\AppData\Roaming。若未看到Contact.db请确认微信 PC 版已完全退出任务栏右键 → 退出再重新打开一次不需重新登录等待 10 秒后刷新该目录。这是微信的“缓存刷新机制”非玄学是血泪经验。校验文件有效性并导出为 CSV使用 Python 的sqlite3模块直接读取过滤掉测试号、系统号、公众号等非真实好友import sqlite3 import pandas as pd # 连接明文 Contact.db conn sqlite3.connect(rC:\Users\YourName\AppData\Roaming\Tencent\WeChat\Contact.db) cursor conn.cursor() # 执行标准查询排除非个人好友Type3 为公众号Type4 为群Type0 为好友 query SELECT UserName AS user_id, NickName AS nickname, RemarkName AS remark, Province AS province, City AS city, Signature AS signature, Sex AS gender, -- 0:未知, 1:男, 2:女 Alias AS alias, -- 微信号部分用户可见 PYInitial AS py_initial, -- 拼音首字母用于排序 RemarkPYInitial AS remark_py -- 备注拼音首字母 FROM Contact WHERE Type 0 AND UserName NOT LIKE gh_% AND LENGTH(UserName) 10 df pd.read_sql_query(query, conn) conn.close() # 清洗去除空昵称、异常城市名如iPhone、Android df df.dropna(subset[nickname]) df df[~df[city].isin([iPhone, Android, Mac, Windows])] df.to_csv(wechat_friends_raw.csv, indexFalse, encodingutf-8-sig) print(f✅ 成功导出 {len(df)} 条有效好友记录)逻辑说明Type 0是微信内部定义的“普通联系人”类型标识UserName NOT LIKE gh_%排除公众号LENGTH(UserName) 10过滤掉极短测试 ID如wxid_123类伪 ID。这些规则来自微信协议逆向文档的公开共识非猜测。参数说明encodingutf-8-sig是关键避免 Excel 打开 CSV 时中文乱码indexFalse防止多一列序号干扰后续分析。2.2 字段含义与业务映射表字段名原始值示例可用性业务解读建议nickname张三★★★★★用户对外显示名含 emoji需正则清洗remark客户-北京-王总★★★★☆最高价值字段人工打标痕迹直接用于分层province/city广东/深圳★★★☆☆地理分布基础但存在大量空值用户未填signature专注AI落地招远程伙伴★★★★☆职业/兴趣线索适合 NLP 提取关键词gender1★★☆☆☆准确率约 70%因大量用户未设置不可单独用于统计aliaszhangsan_ai★★☆☆☆微信号可用于去重与手机号/邮箱比对注意Contact.db中的UserName是微信内部唯一 ID如wxid_xxx不是微信号不能用于登录或发送消息仅作关联键使用。3. 构建好友关系网络基于聊天记录的活跃度建模与权重计算仅有好友列表是静态快照缺乏“谁和谁常互动”这一核心维度。微信备份中的MSG数据库位于同级目录Msg子文件夹下文件名如MSG0.db存储了全部聊天记录虽不包含对方头像/昵称需 JOINContact.db但包含时间戳、消息类型、发送方/接收方 ID、文本内容长度——这足以构建加权有向图。3.1 从 MSG0.db 提取双向互动频次矩阵微信聊天记录表MSG结构精简关键字段Talker对话对象 ID、IsSender1我发0对方发、CreateTime时间戳单位秒、Type1文本3图片34语音等、Content文本内容图片/语音为空。我们只关注文本消息Type 1因其最能反映主动沟通意愿。import sqlite3 import pandas as pd from datetime import datetime # 连接 MSG0.db注意一个账号可能有多个 MSG*.db按修改时间取最新 msg_db_path rD:\WeChatBackup\Backup_20240512143022\Msg\MSG0.db conn_msg sqlite3.connect(msg_db_path) # 查询近 90 天内文本消息排除群聊Talker 不以 chatroom 结尾 query_msg SELECT Talker AS talker_id, IsSender, CreateTime, LENGTH(Content) AS content_len FROM MSG WHERE Type 1 AND CreateTime ? AND Talker NOT LIKE %chatroom ORDER BY CreateTime DESC # 计算 90 天前的时间戳微信时间戳为 Unix 秒 cutoff_ts int((datetime.now() - pd.Timedelta(days90)).timestamp()) df_msg pd.read_sql_query(query_msg, conn_msg, params(cutoff_ts,)) conn_msg.close() # 关联 Contact.db 补全昵称避免硬编码 JOIN用字典映射更稳 contact_df pd.read_csv(wechat_friends_raw.csv) id_to_name contact_df.set_index(user_id)[nickname].to_dict() # 统计每个好友的「我发消息次数」和「对方回消息次数」 df_msg[talker_name] df_msg[talker_id].map(id_to_name).fillna(Unknown) df_msg df_msg[df_msg[talker_name] ! Unknown] # 过滤掉非好友如新添加未同步昵称 # 分组聚合按 talker_name 统计 activity df_msg.groupby(talker_name).agg( sent_count(IsSender, lambda x: (x 1).sum()), recv_count(IsSender, lambda x: (x 0).sum()), total_msgs(IsSender, count), avg_content_len(content_len, mean), last_active(CreateTime, max) ).reset_index() # 计算「响应率」 对方回复数 / 我发送数防零除 activity[response_rate] activity.apply( lambda r: r[recv_count] / r[sent_count] if r[sent_count] 0 else 0, axis1 ) # 生成加权活跃度得分0~100综合发送量、响应率、最近活跃 activity[score] ( (activity[sent_count] / activity[sent_count].max()) * 40 (activity[response_rate] * 100) * 30 ((activity[last_active] - activity[last_active].min()) / (activity[last_active].max() - activity[last_active].min() 1)) * 30 ) activity activity.sort_values(score, ascendingFalse) activity.to_csv(wechat_friend_activity.csv, indexFalse, encodingutf-8-sig) print(f✅ 活跃度分析完成TOP 10{list(activity.head(10)[talker_name])})逻辑说明CreateTime是 Unix 时间戳秒级直接参与计算Talker NOT LIKE %chatroom确保只分析单聊response_rate是核心指标反映关系健康度——高发送低响应大概率是单方面维系。参数说明90 天窗口是经验值太短易受偶然事件干扰如节日群发太长则稀释近期行为权重avg_content_len辅助判断沟通深度长文本 短表情包。3.2 构建好友关系图谱用 NetworkX 生成 GEXF 可视化文件将活跃度得分转化为边权重生成可导入 Gephi 或 Cytoscape 的标准图谱格式import networkx as nx import json # 创建有向图 G nx.DiGraph() # 添加节点好友昵称为节点属性含活跃度得分、地区 for _, row in activity.iterrows(): G.add_node( row[talker_name], scorerow[score], provincecontact_df[contact_df[nickname]row[talker_name]][province].iloc[0] if not contact_df[contact_df[nickname]row[talker_name]].empty else Unknown, citycontact_df[contact_df[nickname]row[talker_name]][city].iloc[0] if not contact_df[contact_df[nickname]row[talker_name]].empty else Unknown ) # 添加边仅当双方互有消息即 A→B 且 B→A 都存在 # 此处简化用「我发给A」和「A发给我」作为双向边存在依据 for idx, row in activity.iterrows(): if row[sent_count] 0 and row[recv_count] 0: # 边权重 min(我发给TA, TA发给我) * 0.5 响应率 * 0.5 weight min(row[sent_count], row[recv_count]) * 0.5 row[response_rate] * 0.5 G.add_edge(Me, row[talker_name], weightround(weight, 2)) # 导出为 GEXFGephi 兼容 nx.write_gexf(G, wechat_network.gexf) print(✅ 关系图谱已生成wechat_network.gexf可用 Gephi 打开)为什么不用「共同好友」建边微信不提供共同好友 APIContact.db中无此类字段。强行用「都出现在我的好友列表」建无向边会得到全连接图失去分析价值。以实际消息交互为边才是真实关系强度的代理变量。4. 好友分层与标签体系从备注字段到自动化聚类手动给 500 好友打标签不现实但微信的RemarkName备注名是天然的弱监督信号——用户自己写的“客户-北京-王总”、“大学同学-李四”、“家人-妈妈”已隐含分类逻辑。我们以此为种子结合签名、地区构建三级标签体系。4.1 基于备注规则的确定性分层Rule-based Tieringimport re def extract_remark_tags(remark): 从备注中提取结构化标签返回字典 if not isinstance(remark, str): return {category: unknown, region: unknown, role: unknown} # 规则1匹配 XX-YY-ZZ 格式最常见 pattern1 r^([^-\s])-([^-\s])-(.)$ m1 re.match(pattern1, remark.strip()) if m1: return { category: m1.group(1), region: m1.group(2), role: m1.group(3) } # 规则2匹配 XXYY 或 XXYYZZ pattern2 r^([^\s])(.?)(?:(.?))?$ m2 re.match(pattern2, remark.strip()) if m2: return { category: m2.group(1), role: m2.group(2), region: m2.group(3) if m2.group(3) else unknown } # 规则3纯关键词匹配 if 客户 in remark or 合作 in remark or 商务 in remark: return {category: business, region: unknown, role: client} elif 同学 in remark or 校友 in remark or 老师 in remark: return {category: education, region: unknown, role: peer} elif 家人 in remark or 爸爸 in remark or 妈妈 in remark: return {category: family, region: unknown, role: relative} return {category: personal, region: unknown, role: friend} # 应用规则 contact_df[tags] contact_df[remark].apply(extract_remark_tags) contact_df[category] contact_df[tags].apply(lambda x: x[category]) contact_df[region] contact_df[tags].apply(lambda x: x[region]) contact_df[role] contact_df[tags].apply(lambda x: x[role]) # 统计各层级分布 print(contact_df[category].value_counts())逻辑说明正则优先匹配结构化备注用户习惯再 fallback 到关键词最后归为personal。category是最高粒度分层business/education/family/personal决定资源分配优先级。参数说明re.match而非re.search确保匹配开头避免误捕“客户经理”中的“客户”。4.2 基于签名文本的 LDA 主题聚类无监督增强对signature字段做轻量 NLP发现未在备注中体现的隐性标签如“自由职业者”、“考研党”、“新晋奶爸”from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.decomposition import LatentDirichletAllocation import jieba # 中文分词预处理 def preprocess_signature(text): if not isinstance(text, str): return # 去除非中文字符、数字、常用符号 text re.sub(r[^\u4e00-\u9fa5a-zA-Z\s], , text) words jieba.lcut(text) # 过滤停用词自定义小停用词表 stopwords {的, 了, 在, 是, 我, 有, 和, 就, 不, 人, 都, 一, 一个} return .join([w for w in words if w not in stopwords and len(w) 1]) contact_df[sig_clean] contact_df[signature].apply(preprocess_signature) texts contact_df[sig_clean].tolist() # TF-IDF 向量化限制 top 1000 词 vectorizer TfidfVectorizer(max_features1000, ngram_range(1,2)) X vectorizer.fit_transform(texts) # LDA 聚类k5经验值 lda LatentDirichletAllocation(n_components5, random_state42, max_iter10) lda.fit(X) # 输出每个主题的关键词 feature_names vectorizer.get_feature_names_out() for topic_idx, topic in enumerate(lda.components_): top_words_idx topic.argsort()[-10:][::-1] top_words [feature_names[i] for i in top_words_idx] print(fTopic {topic_idx1}: { | .join(top_words)}) # 预测每条签名所属主题 contact_df[signature_topic] lda.transform(X).argmax(axis1) 1为什么用 LDA 而非 BERT签名平均长度 20 字BERT 大模型过拟合且推理慢LDA 在短文本主题发现上鲁棒性好5 个主题足够覆盖“职业”、“兴趣”、“状态”、“地域”、“价值观”等维度。避坑点必须自定义中文停用词jieba默认停用词表不全ngram_range(1,2)捕获“人工智能”、“自由职业”等双字词。5. 常见问题排查5 条血泪踩坑记录与当场解决方案注意以下问题均来自某高校实验室连续 3 个月、27 位成员的真实复现过程非理论推演。5.1 现象Contact.db文件存在但pd.read_sql_query报错database is locked原因微信 PC 版仍在运行其后台进程持续写入Contact.dbSQLite 连接被占用。解决任务管理器结束所有WeChat.exe进程包括后台服务进程在 PowerShell 中执行Get-Process | Where-Object {$_.ProcessName -like *WeChat*} | Stop-Process -Force等待 5 秒后再运行脚本。不要尝试加timeout30参数无效。5.2 现象导出的province/city字段大量为空或显示为“iPhone”原因用户未在微信资料页填写地区或微信将设备型号误写入字段旧版客户端 Bug。解决放弃province/city单独使用改为与signature联合推断如签名含“上海租房”、“深圳南山”、“杭州西湖”用正则提取地名使用geopy库调用免费 Nominatim API需加user_agent做模糊地理编码但每日限 1000 次仅用于样本校验不可批量。5.3 现象MSG0.db中Content字段为乱码如U无法解析文本原因微信 8.0.45 版本对Content字段启用 Zstandard 压缩非加密sqlite3直接读取为二进制。解决安装zstandard包pip install zstandard修改读取逻辑import zstandard as zstd # ... 在读取 Content 后 if isinstance(content_bytes, bytes) and len(content_bytes) 10: try: dctx zstd.ZstdDecompressor() content dctx.decompress(content_bytes).decode(utf-8) except: content [compressed]5.4 现象activity[score]计算结果全为 0 或 NaN原因last_active列为int类型但min()/max()计算时若含NaT空时间戳会返回NaN导致整列运算失败。解决在聚合前强制转换df_msg[CreateTime] pd.to_numeric(df_msg[CreateTime], errorscoerce)过滤掉CreateTime为NaN的行df_msg df_msg.dropna(subset[CreateTime])last_active聚合改用pd.to_datetime().dt.timestamp()再转int避免浮点误差。5.5 现象Gephi 打开wechat_network.gexf后节点重叠严重无法看清结构原因默认 ForceAtlas2 布局参数不适合小规模图 200 节点引力/斥力失衡。解决Gephi 中Layout → ForceAtlas2 → Adjust by Degree勾选Scaling调至2.0放大节点间距Gravity调至100增强中心聚拢运行 200 步后暂停手动拖拽“Me”节点至画布中心再微调。6. 进阶技巧用 Pyecharts 实现动态地理热力图与活跃时段雷达图最终交付物不应是冷冰冰的 CSV而是一份可交互的 HTML 报告。Pyecharts 封装 ECharts零配置生成专业图表且支持离线使用render_notebook()仅用于 Jupyterrender()生成独立 HTML。6.1 基于城市坐标的地理热力图需手动补全坐标微信不提供经纬度但province/city可映射到百度/高德 POI。我们采用「城市名 → 百度地图城市编码 → 坐标」链路全程离线缓存不实时调用 API# city_coords.json 是预下载的中国城市坐标表来源国家基础地理信息中心公开数据 with open(city_coords.json, r, encodingutf-8) as f: city_coords json.load(f) # 格式{北京: [39.9042, 116.4074], 深圳: [22.5431, 114.0579]} # 构建热力数据 heat_data [] for _, row in contact_df.dropna(subset[city]).iterrows(): city row[city] if city in city_coords: heat_data.append([city_coords[city][1], city_coords[city][0], 1]) # [经度, 纬度, 权重] # 生成热力图 from pyecharts import options as opts from pyecharts.charts import Geo from pyecharts.globals import ChartType geo Geo() geo.add_schema(maptypechina) geo.add(好友分布, heat_data, type_ChartType.HEATMAP) geo.set_series_opts(label_optsopts.LabelOpts(is_showFalse)) geo.set_global_opts( visualmap_optsopts.VisualMapOpts(min_0, max_50), title_optsopts.TitleOpts(title微信好友地理热力图) ) geo.render(wechat_heatmap.html)为什么不用pyecharts自带Geo.add_coordinate()它依赖在线地图服务国内环境不稳定手动维护city_coords.json仅 300 行更可控且可扩展港澳台城市。6.2 消息活跃时段雷达图揭示你的社交生物钟从MSG0.db提取每小时消息量CreateTime转hour绘制 24 小时雷达图对比「我发送」与「对方回复」# 提取小时分布 df_msg[hour] pd.to_datetime(df_msg[CreateTime], units).dt.hour hourly df_msg.groupby([hour, IsSender]).size().unstack(fill_value0).reset_index() # 补全缺失小时0-23 full_hours pd.DataFrame({hour: range(24)}) hourly full_hours.merge(hourly, onhour, howleft).fillna(0) # 绘制雷达图 from pyecharts.charts import Radar radar Radar() radar.add_schema( schema[ opts.RadarIndicatorItem(name0点, max_50), opts.RadarIndicatorItem(name1点, max_50), # ... 手动写 24 项或用循环生成 ] ) # 由于雷达图维度固定我们取 8 个关键时段0-3, 4-7, 8-11, 12-15, 16-19, 20-23 bins [0,4,8,12,16,20,24] labels [凌晨, 清晨, 上午, 下午, 傍晚, 夜间] hourly[period] pd.cut(hourly[hour], binsbins, labelslabels, rightFalse) period_agg hourly.groupby(period)[[0,1]].sum().reset_index() # 转为雷达图所需格式[[v1,v2,...], [v1,v2,...]] data_sent period_agg[0].tolist() data_recv period_agg[1].tolist() radar.add(我发送, [data_sent]) radar.add(对方回复, [data_recv]) radar.render(wechat_radar.html)关键技巧雷达图不适合 24 维必须降维。pd.cut按业务意义分组如“凌晨”对应休息时间“下午”对应工作沟通高峰比等宽分箱更有解释力。6.3 一份报告三种交付形态形式适用场景生成命令特点wechat_report.htmlPyecharts快速分享给同事/老板radar.render()geo.render()交互式可缩放离线打开wechat_summary.pdfWeasyPrint正式汇报、存档weasyprint.HTML(wechat_report.html).write_pdf(summary.pdf)固定版式打印友好体积小wechat_api.json结构化数据输入下游系统如 CRMcontact_df.to_json(api.json, orientrecords, force_asciiFalse)字段明确含score、category、signature_topic可直接 API 接入我坚持把每次分析结果导出为这三种格式——HTML 用于即时讨论PDF 用于邮件汇报JSON 用于自动化同步。工具的价值不在炫技而在让结论无缝流进下一个环节。如果你也试过会发现真正的分析瓶颈从来不是技术而是想清楚「下一步要用这个数据做什么」。希望帮到你。本文还有配套的精品资源点击获取