0. 前言
之所以想要提取微信的聊天记录并分析是因为也开始再学习python,但是单纯看看语法什么的又很无趣,无意间看到python可以进行微信聊天记录的分析,就自己尝试做了一下,感觉还是挺有意思的。
1.提取聊天记录数据库
我所用的是小米手机,所以提取聊天记录是主要是通过本地备份功能,其余手机也可参考下述博客,具体流程可参考:
微信聊天记录导出(2020新版)
安卓\电脑微信聊天记录导出表格
微信聊天记录数据提取并分析
利用python做微信聊天记录词云分析
提取微信数据库的主体流程都差不多,基本都是先进行备份,然后将备份文件复制到电脑进行解压,解压完成之后根据得到的数据库密码访问数据库。提取数据库的过程基本上都不会有什么问题,主要会出现的问题在于获取微信数据库密码。我开始使用的是手机IMEI + uin拼接取其32位MD5码前7位的方式,但是因为我手机上有两个IMEI码,尝试了各种组合获得的密码始终是错误的,所以就放弃采用这种方式,但是根据其他博客中的内容,这种方式也可以获取数据库密码。
我最终采用的是反序列化的方式获取数据库密码,即将微信com.tencent.mm\r\MicroMsg\systemInfo.cfg和com.tencent.mm\r\MicroMsg\CompatibleInfo.cfg这两个文件复制出来,通过参考代码获取数据库密码。开始直接在命令行通过javac编译并运行,如下所示
javac IMEI.java
java IMEI systemInfo.cfg CompatibleInfo.cfg
但是直接在命令行中运行会报 “错误: 找不到或无法加载主类 IMEI 原因: java.lang.ClassNotFoundException: IMEI”,但是将代码放在IDEA中可以运行并得到最终数据库密码,代码如下:
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.security.MessageDigest;
import java.util.HashMap;
public class IMEI {
public static void main(String[] args) {
String systemInfo_path = "D:\\wechet-anayze\\systemInfo.cfg";
String compatibleInfo_path1 = "D:\\wechet-anayze\\CompatibleInfo.cfg";
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
path));
Object DL = in.readObject();
HashMap hashWithOutFormat = (HashMap) DL;
ObjectInputStream in1 = new ObjectInputStream(new FileInputStream(
path1));
Object DJ = in1.readObject();
HashMap hashWithOutFormat1 = (HashMap) DJ;
String s = String.valueOf(hashWithOutFormat1.get(Integer
.valueOf(258)));
s = s + hashWithOutFormat.get(Integer.valueOf(1));
s = encode(s);
System.out.println("The Key is : " + s.substring(0, 7));
in.close();
in1.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static String encode(String content) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(content.getBytes());
return getEncode32(digest);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String getEncode32(MessageDigest digest) {
StringBuilder builder = new StringBuilder();
for (byte b : digest.digest()) {
builder.append(Integer.toHexString((b >> 4) & 0xf));
builder.append(Integer.toHexString(b & 0xf));
}
return builder.toString();
}
}
微信数据库及数据库密码获取完成之后就可以通过sqlcipher(官方下载地址)软件访问数据库中内容,查看具体的聊天信息等,我在后续处理中主要用到了message和contact表,message表中记录了所发送的所有消息信息,contact表中记录了所有的联系人信息,message表中主要使用到的内容如下:
message表主要内容
列名 | 内容 |
---|
msgId | 按所有消息时间顺序的唯一编号 |
type | 聊天内容类型 |
isSend | 标识消息是自己发送还是对方发送,1表示自己,0表示对方 |
createTime | 聊天时间 |
talker | 单聊的wxid或群聊编号"XXXX@chatroom" |
content | 聊天内容,单聊直接显示内容,群聊格式为“wxid:\n”内容 |
因为也挺想知道和其他人的聊天类型,所以就也根据message表中的type值结合createTime查找具体的聊天信息,从而对type类型做了如下分类,
type类型信息
type值 | 表示内容 |
---|
1 | 文本内容 |
2 | 位置信息 |
3 | 图片及视频 |
34 | 语音消息 |
42 | 名片(公众号名片) |
43 | 图片及视频 |
47 | 表情包 |
48 | 定位信息 |
49 | 小程序链接 |
10000 | 撤回消息提醒(XXXX撤回了一条消息) |
1048625 | 照片 |
16777265 | 链接 |
285212721 | 文件 |
419430449 | 微信转账 |
436207665 | 微信红包 |
469762097 | 微信红包 |
·11879048186 | 位置共享 |
… | (还有未知type信息,待补充) |
contact表主要内容
列名 | 含义 |
---|
username | 微信id,格式是"wxid_xxxxxxxx"或者一看就是自己设置的 |
alias | 自己设置的那个可以通过查找加好友的微信名(和上面那个有的有区别有的为空) |
conRemark | 联系人备注名 |
nickname | 微信名片上的名字,公众号的名字 |
contactLabelIds | 联系人标签号 |
2.数据预处理
2.1 message表和contact表获取
获取到微信数据库及数据库密码后,可通过sqlciper软件查看数据库中内容,同时也可将所需的数据导出,可通过file/Export/Table as CSV file选项,选择需要导出的数据表,将之存储为CSV文件。
这里需要注意一下:
如果在python中直接读取导出的csv文件夹,python中会报错,大致是因为编码错误。其余博客中有提到使用excel打开导出的csv文件,然后以utf-8格式另存,最后读取另存后的文件,通过试验这种方法可以进行读取,但是因为csv文件用excel打开时超过16位的数字将会被强制使用科学计数法表示,所以message表中的createTime会被使用科学计数法表示,另存后的文件中creatTime时间精度会损失,所以我最终采用了一个简单粗暴的方法:用记事本打开csv文件,然后以utf-8的形式另存,在python中就可以正常读取了。(话说用记事本打开要比用excel打开快好多倍)
2.2 message表预处理
因为message表中和contact表中含有大量数据分析时不用的数据列,所以在数据预处理阶段中,只需要提取message表和contact表中我们所关注的一些数据。对于message表而言,我们只关注talker,createTime,type, isSend, content所表示的内容,也即聊天对象,聊天时间,消息类型,消息由谁发送,聊天内容。更近一步,对于聊天内容而言,我们只关注文本消息(文本消息可用于后续制作词云),对于其他类型的消息我们只需要统计出现的次数,而不必关注具体内容。所以在预处理过程中,同时需要将非文本类型消息内容置0,这样做的另一个好处是可以大大减少message表的数据量,就个人而言,将非文本消息置零后,message文件大小由134MB缩小到29M。
提取talker,createTime,type, isSend, content内容
"""
Create on 2020-11-21
@author: muxiaohe
"""
def get_needed_data(file_path, save_file_path):
message = read_file(file_path)
message = message[['type', 'isSend', 'createTime', 'talker', 'content']]
message.to_csv(save_file_path, encoding='utf_8_sig',header=True, index=False)
将非文本消息置零
"""
Create on 2020-11-21
@author: muxiaohe
"""
def data_clean(file_path, save_path):
message = pd.read_csv(file_path, sep=',', encoding='utf-8', low_memory=False)
print("源文件大小: ", message.shape)
message.loc[message.type != 1, 'content'] = 0
print("处理后文件大小: ", message.shape)
message.to_csv(save_path, encoding='utf_8_sig', header=True, index=False)
2.3 contact表预处理
与message表类似,contact表也需去除无关数据,contact表中我们只提取 usename, alias, conRemark, nickname信息,同时在处理时只保留了有备注的联系人,没有备注的联系人也没必要分析聊天记录。
"""
Create on 2020-11-21
@author: muxiaohe
"""
def contact_pre_treatment(file_path, save_path):
contact_path = r"D:\wechet-anayze\recontact.txt"
contact_save_path = r"D:\wechet-anayze\pre-recontact.csv"
contact = pd.read_csv(contact_path, sep=',', encoding="utf-8", low_memory=False)
print("处理前文件大小: ", contact.shape)
contact = contact[['username', 'alias', 'conRemark', 'nickname']]
contact1 = contact.drop(contact[pd.isna(contact.conRemark)].index)
print("处理后文件大小: ", contact.shape)
contact1.to_csv(contact_save_path, encoding='utf_8_sig', header=True, index=False)
3.聊天记录分析
3.1 获取常用联系人聊天次数
在预处理的基础之上,获取每个联系人的聊天次数,并据此使用pyecharts绘制柱状图,使用pyecharts绘制时需特别注意:pyecharts传入数据时需要int数据,而从文件中读取赋值到list中的数值类型为int64,需要先使用int()函数进行转换,否则绘制出来的图形中data数据域将都为NaN。
"""
Create on 2020-11-21
@author: muxiaohe
"""
def get_chat_nums(message_path, contact_path):
"""
:param message_path: 预处理完成后的message表存储路径
:param contact_path: 预处理完成后的contact表存储路径
:return:
"""
message = pd.read_csv(message_path, sep=',', encoding='utf-8', low_memory=False)
contact = pd.read_csv(contact_path, sep=',', encoding='utf-8', low_memory=False)
contact = contact[['username', 'conRemark']]
username = contact['username'].tolist()
print(type(username))
contact_dict = dict(zip(contact['username'], contact['conRemark']))
contact_sum_message = {}
sum_message = 0
uname_list = []
chat_num_list = []
for uname in username:
key = contact_dict.get(uname)
value = (message['talker'] == uname).sum()
if value != 0:
contact_sum_message[key] = value
sum_message += value
uname_list.append(key)
chat_num_list.append(int(value))
print("总聊天次数: ", sum_message)
c = (
Bar(init_opts=opts.InitOpts(width="1600px", height="600px", page_title="聊天次数统计"))
.add_xaxis(uname_list)
.add_yaxis(series_name="聊天次数", y_axis=chat_num_list, color='#FF6666')
.set_global_opts(
title_opts=opts.TitleOpts(title="聊天次数统计"),
datazoom_opts=[opts.DataZoomOpts(range_start=20, range_end=40), opts.DataZoomOpts(type_="inside")],
brush_opts=opts.BrushOpts(),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=-15)),
toolbox_opts=opts.ToolboxOpts(),
legend_opts=opts.LegendOpts(is_show=False),
)
.set_series_opts(
label_opts=opts.LabelOpts(is_show=False),
markline_opts=opts.MarkLineOpts(
data=[
opts.MarkLineItem(type_="min", name="最小值"),
opts.MarkLineItem(type_="max", name="最大值"),
opts.MarkLineItem(type_="average", name="平均值"),
]
),
)
.render("chat_num_count.html")
)
3.2 获取聊天消息中不同类型消息占比
"""
Create on 2020-11-21
@author: muxiaohe
"""
import pandas as pd
from pyecharts import options as opts
from pyecharts.charts import Pie
def get_message_type_frequency(file_path, wxid):
"""
:param file_path: 经过预处理后的message表存储位置
:param wxid: 待查询人的微信id
:return:
"""
message = pd.read_csv(file_path, sep=',', encoding='utf-8', low_memory=False)
message = message[message['talker'] == wxid]
chat_type_count = message['type'].groupby(message['type']).size()
message_type = {'1': '文本内容', "3": "图片及视频", "34": "语音消息", "42": "名片信息", "43": "图片及视频",
"47": "表情包", "48": "定位信息", "49": "小程序链接", "10000": "消息撤回提醒", "1048625": "网络照片",
"16777265": "链接信息", "419430449": "微信转账", "436207665": "红包", "469762097": "红包",
"-1879048186": "位置共享"}
chat_type_count_dict = {}
for key in chat_type_count.index:
if str(key) in message_type.keys():
print(message_type.get(str(key)))
chat_type_count_dict[message_type.get(str(key))] = chat_type_count[key]
else:
chat_type_count_dict[key] = chat_type_count[key]
print("结果集类型: ", type(chat_type_count_dict))
print(chat_type_count_dict)
x_data = []
y_data = []
for key in chat_type_count_dict:
temp = [str(key), chat_type_count_dict.get(key)]
x_data.append(str(key))
y_data.append(int(chat_type_count_dict.get(key)))
a1 = []
for z in zip(x_data, y_data):
a1.append(z)
pie = Pie(init_opts=opts.InitOpts(width="1600px", height="600px", page_title="消息类型统计"))
pie.add(
"",
data_pair=a1,
center=["35%", "60%"],
)
pie.set_global_opts(
title_opts=opts.TitleOpts(title="Pie-调整位置"),
legend_opts=opts.LegendOpts(pos_left="15%"),
)
pie.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}:{c}"))
pie.render("message_type_count.html")
3.3 聊天记录词云
可选择具体的聊天对象,获取全部的聊天内容,代码中可以chinese_slice选择是否进行分词,如果不进行分词则将全部聊天内容拼接到一起,通过wordcloud模块生成词云,如果选择分词则会调用jiebe模块先进行分词,然后再通过wordcloud模块生成词云。
"""
Create on 2020-11-21
@author: muxiaohe
"""
import imageio
import jieba
import pandas as pd
import matplotlib.pyplot as plt
import wordcloud
def get_wordcloud(file_path, stopword_path, wxid, image_path):
"""
:param file_path: 预处理完成后的message表存储位置
:param stopword_path: 停用词文件存储位置
:param wxid: 待查询人微信id
:return:
"""
message = pd.read_csv(file_path, sep=',', encoding='utf-8', low_memory=False)
message = message[message['talker'] == wxid]
content = message['content']
font_path = r'C:\Windows\Fonts\MSYH.TTC'
wordcut_flag = True
image_out_name = 'word-heart.png'
stopwords = [line.strip() for line in open(stopword_path, encoding='UTF-8').readlines()]
if image_out_name is None:
image_out_name = 'word-heart.png'
if wordcut_flag:
print("进行中文分词")
outstr = ""
text = ",".join(content)
text_list = jieba.lcut(text, cut_all=False)
for word in text_list:
if word not in stopwords:
if word != '\t' and '\n':
outstr += word
outstr += " "
text = outstr
else:
print("不进行中文分词")
text = " ".join(content)
mk = imageio.imread(image_path)
w = wordcloud.WordCloud(width=1000,
height=700,
background_color='white',
font_path=font_path,
mask=mk,
scale=2,
stopwords=None,
contour_width=1,
contour_color='red')
w.generate(text)
image_colors = wordcloud.ImageColorGenerator(mk)
plt.imshow(w.recolor(color_func=image_colors))
plt.axis("off")
plt.show()
w.to_file(image_out_name)
有兴趣的话可以参考我的GitHub地址,里边有所需的全部文件及代码,有帮到的请给star呀~
微信聊天记录提取及分析
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)