每年年末,我的一大乐趣就是翻阅各大 APP 出品的个人年度数据报告。

一来是出于职业习惯,二来是认为这些数据姑且能算是我过去一年真实存在的证据。

但我总感觉差些什么,在今年我终于意识到了, 那就是微信这个几乎每天都占据最多使用时长榜首的应用,竟然没有一个使用数据分析报告。

本着好玩的想法,我尝试着自己完成了一个非官方版的微信好友聊天分析报告,代码目前已开源在 GitHub,具体使用方式请参照仓库的说明

最终的效果如下,包含了一些聊天情况数据及聊天关键词的词云图等:





{.gallery data-height=“440”}

:::warning

完整复现本文分析至少所需以下内容:

  1. 至少拥有一台 iOS / iPadOS 设备,如果只是安卓用户,本文暂未覆盖,阅读本文可能会浪费你的时间。
  2. 有耐心等待不确定的备份时间,一般在几个小时以上,并且有重来一次的决心。

:::

流程

数据准备

数据分析脱离数据就无从谈起,整个过程中最耗时、最重要的就是获得与微信好友的聊天记录。

本文采用 WX Backup 这款工具来获取微信聊天记录,如何解密微信的本地数据库不在本篇的讨论范围内,有时(挖)间(坑)可以单独开一篇聊一聊。

iTunes 备份

据官网介绍,你需要做的就是将带有聊天记录的设备连接到电脑(Mac/PC)上,并使用 iTunes 进行一次完整备份。

从 19 年苹果宣布取消在 macOS 上的独立 iTunes APP 之后,iTunes 就被集成在 Finder 当中。请注意,在备份的时候不要选择「加密备份」。

截屏2022-01-30 21.10.21

:::info

完整备份一次需要非常久的时间(由设备使用容量所决定,但一般不低于两小时),因此如果你恰好有一些 iOS / iPadOS 设备,有个技巧比较适合:

  1. 将需要分析的用户的聊天记录迁移至另一台使用容量较小的设备,这样获取数据的耗时一般就会缩短不少。

:::

:::danger

不要忘记你的设备依然与你的 iTunes 保持着连接,不要因为一些习惯性动作(接电话、离开一会儿等)拔掉数据线。血的教训,但也只能再来一次。

:::

导出聊天记录

当你备份完成之后,你可以使用 WX Backup 来导出你想要分析联系人的聊天记录(图来自于 WX Backup):

img

选择一个联系人导出后会得到一个文件夹,本次分析所需要的数据就在 js 文件夹中的 messages.js 中。

image-20220216235240250

将 JS 转换为 Excel

为方便后续处理,可以先将 messages.js 文件转换为熟悉的 Excel 文件,转换代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import json
import pandas as pd
from pathlib import Path
from datetime import datetime


def js2excel(path):
with open(path, 'r') as f:
load_dict = json.loads(f.read().replace('var data = ', ''))
message = pd.DataFrame(load_dict['message']).rename(columns={'m_nsFromUsr': '发送人', 'm_uiCreateTime': '发送时间', 'm_nsContent': '消息内容'})
message['发送时间'] = message['发送时间'].apply(lambda timestamp: datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %T'))
ORDER = ['发送人', '发送时间', '消息内容']
message = message[ORDER]
return message


if __name__ == '__main__':
folder = 'XXX' # 替换为导出联系人的文件夹名称
path = Path(folder) / 'js/message.js'
message = js2excel(path)
message.to_excel('消息.xlsx', index=False)

在运行完脚本之后便可以获得一个包含了所有消息的 Excel 表格。

image-20220217000131987

数据分析

本脚本当前支持以下分析:

  1. 计算成为好友的天数:根据本地聊天记录计算与目标用户第一次聊天迄今为止的天数。如果换过手机等原因造成聊天记录缺失等的情况会导致数据不准确。
  2. 计算聊天的天数:计算成为好友迄今为止当天至少发送过一条消息的天数。
  3. 计算聊天最频繁的日期:计算成为好友迄今为止当天发送消息数量最多的日期。
  4. 计算聊天最频繁的时间段:计算一天 24 小时内聊天最频繁的时间段。
  5. 计算聊天到最晚的日期及聊天内容:计算聊天至最晚(最接近凌晨 5 点)的日期及聊天内容。
  6. 计算发送与接受的消息量统计:计算成为好友迄今为止发送与接受的消息量统计。
  7. 计算聊天最频繁的关键词:统计关键词词频,可输入用户字典。

完整分析器代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import re
import pkuseg
import pandas as pd
from datetime import datetime
from dateutil.parser import parse
from collections import Counter



class WeChatMessageAnalyser(object):

def __init__(self, message):
self.message = message

def 计算成为好友的天数(self):
# 根据本地聊天记录计算与目标用户第一次聊天迄今为止的天数。如果换过手机等原因造成聊天记录缺失等的情况会导致数据不准确
date = self.message.loc[0, '发送时间'][:10].split('-')
date = f'{date[0]}{date[1]}{date[2]}日'
return {
'成为好友的日期': date,
'成为好友的天数': (datetime.now() - parse(self.message.loc[0, '发送时间'])).days
}

def 计算聊天的情况(self):
self.message['发送日期'] = self.message['发送时间'].apply(lambda x: x[:10])
# 计算成为好友迄今为止当天至少发送过一条消息的天数
unique_day = list(set(self.message['发送日期']))
day_counter = Counter(day[:4] for day in unique_day)
result = {f'{k} 年聊天的天数': v for k, v in day_counter.items()}
result['总计聊天的天数'] = len(unique_day)
# 计算成为好友迄今为止当天发送消息数量最多的日期
message_day = self.message['发送日期'].to_list()
frequent_day = Counter(message_day).most_common(1)[0]
result['聊天最频繁的日期'] = {
'日期': frequent_day[0],
'消息数量': frequent_day[1]
}
# 计算一天 24 小时内聊天最频繁的时间段
message_day_time = self.message['发送时间'].apply(lambda x: x[11:13]).to_list()
frequent_day_time = Counter(message_day_time).most_common(1)[0]
result['聊天最频繁的时间'] = {
'时间': frequent_day_time[0],
'消息数量': frequent_day_time[1]
}
# 计算聊天至最晚(最接近凌晨 5 点)的日期及聊天内容
self.message['与 5 点相差的时间'] = self.message['发送时间'].apply(lambda x: (parse('05:00:00') - parse(x[11:])).seconds / 60)
index = self.message['与 5 点相差的时间'].idxmin()
result['聊天最晚的时间'] = {
'日期': self.message.loc[index, '发送日期'],
'发送时间': self.message.loc[index, '发送时间'][11:],
'发送内容': self.message.loc[index, '消息内容']
}
return result


def 计算消息量(self):
# 计算成为好友迄今为止发送与接受的消息量统计
return {
'总共消息': self.message.shape[0],
'收到消息': self.message[self.message['发送人'].notnull()].shape[0],
'发送消息': self.message[self.message['发送人'].isnull()].shape[0]
}


def 计算关键词(self):
# 统计关键词词频
content = ''
for _, row in self.message.iterrows():
pattern = re.compile(r'[A-Za-z]', re.S)
if res := re.findall(pattern, row['消息内容']):
continue
else:
content = f'{content} {row["消息内容"]}'
# 设置停用词
stop_words = [' ', '的', '了', '我', '是', '你', '好', '也', '就', '不', '吗', '个', '有', '还', '一', '都', '这', '在', '啊', '没', '要', '去', '太', '会', '那', '看', '哦', '说', '这个', '给', '很', '人', '天', '那个', '两', '他', '还是', '应该', '跟', '什么', ']', '她', '吧', '能', '对', '想', '然后', '[', '用', '做', '上', '时候', '!', ',', '得', '大', '但是', '自己', '下', '这样', '能', '着', '挺', '写', '一下', '?', '已经', '因为', '找', '小', '次', '和', '打', '呢', '好像', '可能', '呀', '感觉', '来', '没有', '嘛', '过', '行', '多', '啦', '把', '到', '再', '过', '柴]', '觉得', '这么', '先', '发']
# 设置用户字典
lexicon = ['笑死', '妹妹', '甜妹', '渣男', '预训练', '臭宝', '宝贝', '宝', '好滴', '牛蛙', '牛哇']
seg = pkuseg.pkuseg(user_dict=lexicon, model_name='web')
seg_list = seg.cut(content)
seg_list = [word for word in seg_list if word not in stop_words]

counter = Counter(seg_list)
return {
'关键词': counter.most_common(25)
}

:::info

高版本如果直接使用 pip install pkusge 失败的话,可以使用 GitHub 仓库来进行安装,具体原因参照相关 Issue

1
pip install https://github.com/lancopku/pkuseg-python/archive/master.zip

:::

未来升级方向

当前的分析脚本还有非常多不完善的地方,以及还有很多功能还没实现。

未来我会从以下方面来升级,但具体什么时候开始还不明确,先挖个坑。

一是增加分析的种类,例如分析最常用的表情包等。

二是增加分析的功能,2020 年年末美国德克萨斯大学"Language left behind on social media exposes the emotional and cognitive costs of a romantic breakup"的研究表明通过分析情侣的聊天记录,可以找到即将分手的证据。目前的分析脚本当中忽略了信息的时间连续性,为此现存的一个改进空间是通过分析连续的聊天记录,来计算聊天双方之间当前的“亲密度”,以此来预测追求成功的可能性,同样这个数值也会泛化为“健康度”,以修正双方之间的关系。

另外在选择分词工具的时候,我在 pkusegthulacjieba 等之间纠结了很久。总结来看是目前缺乏一个比较好的方式可以快速评估各类分词工具的相关性能,我应该也会做下这方面的研究。