微信聊天记录导出为电脑文件实操教程(附代码)

news2024/10/5 21:08:08

写在前面

最近,微信中加的群有点多,信息根本看不过来。如果不看,怕遗漏了有价值的信息;如果一条条向上翻阅,实在是太麻烦。

有没有办法一键导出所有聊天记录

一来翻阅更方便一点,二来还可以让 AI 帮我总结一下,避免遗漏有价值的内容。

网上翻阅了很多资料,完全有效的不多,而且很多工具都需要收费。

最终找到一个开源项目(传送门),本文将参考这个项目,分享给大家:导出微信聊天记录的几个关键步骤

    1. 手机微信数据库导入电脑端
    1. 破解数据库密码
    1. 导出数据库
    1. 提取联系人信息和聊天记录

希望给有类似需求的小伙伴带来帮助。

话不多说,直接上实操!

关键步骤拆解

1. 手机微信数据库导入电脑端

对于很少用电脑端微信的小伙伴,首先需要先把手机微信的数据迁移到电脑端:

在手机端微信,依次点击**「我-设置-聊天-聊天记录迁移与备份-迁移」**,选择迁移到电脑微信;

在这里插入图片描述

继续选择部分 或者 全部聊天记录,如果聊天数据较多,可能需要稍等一段时间~

2. 破解数据库密码

电脑端自己的微信数据存放在哪?

在电脑端微信,左下角依次点击「设置-文件管理」,找到自己的微信数据存放位置,然后打开对应的文件夹。
在这里插入图片描述
文件最后一级目录就是自己的微信号,如果登录过多个微信账号的需要注意切换,比如下面这张就是我的微信数据存放位置,其中的 Msg 文件夹中存放的就是微信中所有的联系人和聊天信息。
在这里插入图片描述
打开 Msg 文件夹,会发现这里有很多 .db 结尾的,就是微信数据存放的数据库文件。如果你用任何数据库软件打开,这时是打不开的。

因为还需要数据库密码。

怎么破解数据库密码?

在这里插入图片描述

参考这个项目,我把其中破解数据库密码部分的代码提取出来了,方便大家直接使用:

def get_key(db_path, addr_len):
    def read_key_bytes(h_process, address, address_len=8):
        array = ctypes.create_string_buffer(address_len)
        if ReadProcessMemory(h_process, void_p(address), array, address_len, 0) == 0: return "None"
        address = int.from_bytes(array, byteorder='little')  # 逆序转换为int地址(key地址)
        key = ctypes.create_string_buffer(32)
        if ReadProcessMemory(h_process, void_p(address), key, 32, 0) == 0: return "None"
        key_bytes = bytes(key)
        return key_bytes

    def verify_key(key, wx_db_path):
        if not wx_db_path or wx_db_path.lower() == "none":
            return True
        KEY_SIZE = 32
        DEFAULT_PAGESIZE = 4096
        DEFAULT_ITER = 64000
        with open(wx_db_path, "rb") as file:
            blist = file.read(5000)
        salt = blist[:16]
        byteKey = hashlib.pbkdf2_hmac("sha1", key, salt, DEFAULT_ITER, KEY_SIZE)
        first = blist[16:DEFAULT_PAGESIZE]

        mac_salt = bytes([(salt[i] ^ 58) for i in range(16)])
        mac_key = hashlib.pbkdf2_hmac("sha1", byteKey, mac_salt, 2, KEY_SIZE)
        hash_mac = hmac.new(mac_key, first[:-32], hashlib.sha1)
        hash_mac.update(b'\x01\x00\x00\x00')

        if hash_mac.digest() != first[-32:-12]:
            return False
        return True

    phone_type1 = "iphone\x00"
    phone_type2 = "android\x00"
    phone_type3 = "ipad\x00"

    pm = pymem.Pymem("WeChat.exe")
    module_name = "WeChatWin.dll"

    MicroMsg_path = os.path.join(db_path, "MSG", "MicroMsg.db")

    type1_addrs = pm.pattern_scan_module(phone_type1.encode(), module_name, return_multiple=True)
    type2_addrs = pm.pattern_scan_module(phone_type2.encode(), module_name, return_multiple=True)
    type3_addrs = pm.pattern_scan_module(phone_type3.encode(), module_name, return_multiple=True)
    type_addrs = type1_addrs if len(type1_addrs) >= 2 else type2_addrs if len(type2_addrs) >= 2 else type3_addrs if len(
        type3_addrs) >= 2 else "None"
    # print(type_addrs)
    if type_addrs == "None":
        return "None"
    for i in type_addrs[::-1]:
        for j in range(i, i - 2000, -addr_len):
            key_bytes = read_key_bytes(pm.process_handle, j, addr_len)
            if key_bytes == "None":
                continue
            if db_path != "None" and verify_key(key_bytes, MicroMsg_path):
                return key_bytes.hex()
    return "None"

3. 导出数据库

上一步中,得到数据库的密码后,就可以将源文件中的数据库导出来,核心代码如下:

# 通过密钥解密数据库
def decrypt(key: str, db_path, out_path):
    """
    通过密钥解密数据库
    :param key: 密钥 64位16进制字符串
    :param db_path:  待解密的数据库路径(必须是文件)
    :param out_path:  解密后的数据库输出路径(必须是文件)
    :return:
    """
    if not os.path.exists(db_path) or not os.path.isfile(db_path):
        return False, f"[-] db_path:'{db_path}' File not found!"
    if not os.path.exists(os.path.dirname(out_path)):
        return False, f"[-] out_path:'{out_path}' File not found!"

    if len(key) != 64:
        return False, f"[-] key:'{key}' Len Error!"

    password = bytes.fromhex(key.strip())
    with open(db_path, "rb") as file:
        blist = file.read()

    salt = blist[:16]
    byteKey = hashlib.pbkdf2_hmac("sha1", password, salt, DEFAULT_ITER, KEY_SIZE)
    first = blist[16:DEFAULT_PAGESIZE]
    if len(salt) != 16:
        return False, f"[-] db_path:'{db_path}' File Error!"

    mac_salt = bytes([(salt[i] ^ 58) for i in range(16)])
    mac_key = hashlib.pbkdf2_hmac("sha1", byteKey, mac_salt, 2, KEY_SIZE)
    hash_mac = hmac.new(mac_key, first[:-32], hashlib.sha1)
    hash_mac.update(b'\x01\x00\x00\x00')

    if hash_mac.digest() != first[-32:-12]:
        return False, f"[-] Key Error! (key:'{key}'; db_path:'{db_path}'; out_path:'{out_path}' )"

    newblist = [blist[i:i + DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)]

    with open(out_path, "wb") as deFile:
        deFile.write(SQLITE_FILE_HEADER.encode())
        t = AES.new(byteKey, AES.MODE_CBC, first[-48:-32])
        decrypted = t.decrypt(first[:-48])
        deFile.write(decrypted)
        deFile.write(first[-48:])

        for i in newblist:
            t = AES.new(byteKey, AES.MODE_CBC, i[-48:-32])
            decrypted = t.decrypt(i[:-48])
            deFile.write(decrypted)
            deFile.write(i[-48:])
    return True, [db_path, out_path, key]

def parse_db(key, db_path, output_dir):
    close_db()
    os.makedirs(output_dir, exist_ok=True)
    tasks = []
    for root, dirs, files in os.walk(db_path):
        for file in files:
            if '.db' == file[-3:]:
                if 'xInfo.db' == file:
                    continue
                inpath = os.path.join(root, file)
                output_path = os.path.join(output_dir, file)
                tasks.append([key, inpath, output_path])
            else:
                try:
                    name, suffix = file.split('.')
                    if suffix.startswith('db_SQLITE'):
                        inpath = os.path.join(root, file)
                        # print(inpath)
                        output_path = os.path.join(output_dir, name + '.db')
                        tasks.append([key, inpath, output_path])
                except:
                    continue
    for i, task in enumerate(tasks):
        flag, msg = decrypt(*task)
        print(f"[{i+1}/{len(tasks)}] {flag} {msg}")
    
    print('开始数据库合并...')
    # 目标数据库文件
    target_database = os.path.join(output_dir, 'MSG.db')
    # 源数据库文件列表
    source_databases = [os.path.join(output_dir, f"MSG{i}.db") for i in range(1, 50)]
    if os.path.exists(target_database):
        os.remove(target_database)
    shutil.copy2(os.path.join(output_dir, 'MSG0.db'), target_database)  # 使用一个数据库文件作为模板
    merge_databases(source_databases, target_database)

此时,会在当前目录下生成全新的数据库文件,用任何一种数据库软件都可以打开查看详细信息。比如我这里采用的是 VS Code 中的 SQLite Viewer 插件,以下图为例,在 Misc.db 中,存放的是所有联系人的信息,包括:用户名、头像和创建时间等。
在这里插入图片描述
上图中,CreateTime 就是加为好友的时间,其含义是从1970年1月1日00:00:00 UTC到当前时间的秒数,可以通过如下代码转换成字符串类型的时间,方便查看。

from datetime import datetime
def covert_time2num(datetime_str, datetime_format="%Y-%m-%d %H:%M:%S"):
    dt_obj = datetime.strptime(datetime_str, datetime_format)
    timestamp = int(dt_obj.timestamp())
    return timestamp

def covert_time2str(timestamp, datetime_format="%Y-%m-%d %H:%M:%S"):
    dt_obj = datetime.fromtimestamp(timestamp)
    return dt_obj.strftime(datetime_format)

4. 提取联系人信息和聊天记录

有了数据库之后,就可以着手提取其中的信息了。

想看看自己都加了哪些好友?他们都来自哪里?

看这里:所有联系人的信息存放在 Misc.db 中。下面这段代码展示了数据库中都存了哪些字段:

# 获取所有联系人(包括群聊)信息
contact_info_lists = micro_msg_db.get_contact() 
contact_infos = []
for contact_info_list in contact_info_lists:
    detail = decodeExtraBuf(contact_info_list[9])
    contact_info = {
        'UserName': contact_info_list[0], # 微信id
        'Alias': contact_info_list[1], # 微信号
        'Type': contact_info_list[2], # 看不出来啥类型
        'Remark': contact_info_list[3], # 备注名
        'NickName': contact_info_list[4], # 昵称
        'smallHeadImgUrl': contact_info_list[7], # 头像url
        "detail": detail, # 包括地区,个性签名,电话,性别
        "label_name": contact_info_list[10] # 标签名,用于给好友分组的标签,大部分人都没用过这个功能,所以通常没有
    }
    contact_infos.append(contact_info)

其中, ‘UserName’ 这个字段中如果包含 ‘@chatroom’ 就代表是群聊。下面我们看一条 联系人信息 的示例:

# 微信群
{'UserName': 'xxx@chatroom', 'Alias': '', 'Type': 2, 'Remark': '', 'NickName': 'xxx车主群', 'smallHeadImgUrl': 'https://wx.qlogo.cn/mmcrhead/xxx/0', 'label_name': 'None'}
# 微信好友
{'UserName': 'wxid_xxx22', 'Alias': 'Quiet_xx', 'Type': 8388611, 'Remark': '备注名', 'NickName': 'xx', 'smallHeadImgUrl': 'https://wx.qlogo.cn/mmhead/xxx/132', 'detail': {'region': ('CN', 'Beijing', 'Daxing'), 'signature': '德不孤 必有邻', 'telephone': '', 'gender': 2}, 'label_name': 'None'}

想一键获取和某个好友的聊天记录?

看这里:MicroMsg.db 中存放了所有用户的聊天记录,有很多张表。可以通过 ‘UserName’ 这个字段从数据库中检索,也可以指定信息类型和时间段,示例代码如下:

def get_chat_info(self, nickname='', remark='', alias='', time_range=None, output_type='txt', type_=None, out_path='output'):
    """
    time_range: (start_time, end_time) ('2021-08-01 12:00:00', '2021-08-02 12:00:00')
    """
    ret_info = self.get_contact_info(nickname, remark, alias)
    self.is_chatroom = ret_info['UserName'].__contains__('@chatroom')
    if type_ is None:
        messages = msg_db.get_messages(ret_info['UserName'], time_range=time_range)
    else:
        messages = msg_db.get_messages_by_type(ret_info['UserName'], type_=type_, time_range=time_range)

每条 message 的类型是不一样的,微信中所有的信息类型列举如下:

types = {
    '文本': 1,
    '图片': 3,
    '语音': 34,
    '视频': 43,
    '表情包': 47,
    '音乐与音频': 4903,
    '文件': 4906,
    '分享卡片': 4905,
    '转账': 492000,
    '音视频通话': 50,
    '拍一拍等系统消息': 10000,
}

善用自己的数据

看到这里的你,一定会有一个疑问:我拿到这些数据都有什么用?

这里猴哥列举自己目前最常用的需求:

1.总结提炼群聊信息

一开始做这件事情,最主要的目的就是这个,因为群聊信息实在太多了,根本看不完。

而把上面的工作流搭建好,把所有信息提取出来就是一行脚本的事。

下面拿猴哥最近加入的一个群来举例。

把该群的所有聊天信息提取出来,保存为一个 txt 文件,左下角显示总共有1万多条聊天记录,这得看到猴年马月去?
在这里插入图片描述

接下来,我们把这份 txt 文件,发给 Kimi,让它帮忙总结一下,下面左图就是 Kimi 给到的分析。

为了验证它没有在胡说八道,我们还可以把所有聊天记录,做一个词云(右图)。

怎么样?Kimi 总结的还是相当到位的,根据它的总结内容,我就可以决定是否需要继续去看群聊信息。

当然,我们还可以优化一下提示词,让它根据时间段来进行总结,便于我们定位到关键信息对应的时间段。
在这里插入图片描述

写在最后

把聊天记录和当前的 AI 大语言模型,结合在一起,一定还可以衍生出很多需求和有意思的应用场景,欢迎评论区给出你的想法和创意~

再次感谢 WeChatMsg 项目团队的开源精神 !

由于篇幅限制,本文用到的源码没有全部展示,需要源码的小伙伴,也可以在公众号【猴哥的AI知识库】后台私信我~

如果本文对你有帮助,欢迎点赞收藏备用!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1841994.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入了解Redis的TYPE命令

Redis作为一个高性能的内存数据库,支持多种数据结构。在管理和操作Redis数据库时,了解键对应的数据类型是至关重要的。本文将深入探讨Redis的TYPE命令,它用于返回存储在指定键中的值的数据类型。 什么是TYPE命令? TYPE命令用于查…

Zynq学习笔记--了解中断配置方式

目录 1. 简介 2. 工程与代码解析 2.1 Vivado 工程 2.2 Vitis 裸机代码 2.3 关键代码解析 3. 总结 1. 简介 Zynq 中的中断可以分为以下几种类型: 软件中断(Software Generated Interrupt, SGI):由软件触发,通常…

CTF-pwn-虚拟化-【d3ctf-2021-d3dev】

文章目录 参考流程附件检查启动信息逆向分析漏洞查看设备配置信息exp 参考 https://x1ng.top/2021/11/26/qemu-pwn/ https://bbs.kanxue.com/thread-275216.htm#msg_header_h1_0 https://xz.aliyun.com/t/6562?time__1311n4%2BxnD0DRDBAi%3DGkDgiDlhjmYh2xuCllx7whD&alic…

[Linux] Shell

chsh不是一种sh,而是一个命令行使用程序,用于更改默认shell CentOS是个开源软件,没有sh,sh是商业版的, 按ls /bin/*sh显示的sh实际上是个链接文件,连接的bash 在命令行输入新的sh名,会启动一个新的进程, 输…

计算机网络知识点汇总

计算机网络知识点汇总 第1章计算机网络体系结构 1.1 计算机网络概述 1.1.1 计算机网络的概念 ​ 计算机网络是由若干个结点(node)和连接这些结点的链路(link)组成。网络中的结点可以是就三级、集线器、交换机、或者路由器等,网络之间通过路由器进行互联&#xf…

【Java】已解决java.sql.SQLException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.sql.SQLException异常 在Java中,java.sql.SQLException是一个通用的异常类,用于表示在数据库操作中发生的错误。无论是类型错误、数据类型不匹配…

IF=9.3!MIMIC-IV数据库发文,手到擒来!| MIMIC-IV数据库周报(6.5~6.11)

重症医学数据库(MIMIC)是由计算生理学实验室开发的公开数据集,其中包括与数千个重症监护病房入院相关的去识别化健康数据,致力于推动临床信息学、流行病学和机器学习的研究。 MIMIC数据库于2003年在美国国立卫生研究院的资助下&am…

Springboot开发Webservice服务端和客户端

环境说明 Java JDK 1.8、Spring boot 2.1.6、Apache CXF 3.1.6 POM依赖 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.1.6</version&…

MySQL limit子句用法及优化(Limit Clause Optimization)

在MySQL中&#xff0c;如果只想获取select查询结果的一部分&#xff0c;可以使用limit子句来限制返回记录的数量&#xff0c;limit在获取到满足条件的数据量时即会立刻终止SQL的执行。相比于返回所有数据然后丢弃一部分&#xff0c;执行效率会更高。 文章目录 一、limit子句用…

嵌入式Linux:Linux系统中文件类型

目录 1、普通文件 2、目录文件 3、字符设备文件 4、块设备文件 5、符号链接文件 6、套接字文件 7、管道文件 8、stat命令和ls命令 8.1、stat命令 8.2、ls命令 9、stat、fstat、lstat函数 9.1、stat函数 9.2、fstat函数 9.3、lstat函数 在Windows系统中&#xff0…

【JS重点18】原型链(面试重点)

一&#xff1a;原型链底层原理 以下面一段代码为例&#xff0c;基于原型对象&#xff08;Star构造函数的原型对象&#xff09;的继承使得不同构造函数的原型对象关联在一起&#xff08;此处是最大的构造函数Object原型对象&#xff09;&#xff0c;并且这种关联的关系是一种链…

Java 集合框架:Vector、Stack 的介绍、使用、原理与源码解析

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 015 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…

哈喽GPT-4o——对GPT-4o 编程的思考与看法

GPT-4o&#xff08;“o”代表“全能”&#xff09;它可以接受任意组合的文本、音频和图像作为输入&#xff0c;并生成任意组合的文本、音频和图像输出。 &#x1f449; GPT功能&#xff1a; GPT-4o知识问答&#xff1a;支持1000token上下文记忆功能最强代码大模型Code Copilo…

多线程(总结黑马程序员)

一、什么是线程&#xff1f; 是一个程序内部的一条执行流程 多线程是什么&#xff1f; 多条线程由CPU负责调度执行 多线程的创建方式一&#xff1a;继承Thread类 //1.继承Thread类 public class MyThread extends Thread {//2.必须重写run方法Overridepublic void run() {…

AI 音乐大模型:创新的曙光还是创意产业的阴影?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

课程品牌推广与传播秘籍:让你的课程火爆全网!

如今在线教育平台的竞争愈发激烈&#xff0c;如何让你的课程在茫茫网海中脱颖而出&#xff0c;吸引更多学员的关注和报名&#xff1f; 作为一名手工酸奶品牌的创始人&#xff0c;目前全国复制了100多家门店&#xff0c;很多都是线上授课。而且我自己还有一家传媒公司&#xff…

【C++题解】1670 - 象棋大赛

问题&#xff1a;1670 - 象棋大赛 类型&#xff1a;分支问题 题目描述&#xff1a; 市里要组织象棋大赛&#xff0c;年龄在 8∼30 周岁之间的选手可以报名参赛。为了公平起见&#xff0c;大赛组委会将选手们分了青年组、少年组和儿童组&#xff0c;大赛组委会规定&#xff1a…

heygen的前世今生

heygen 关于徐卓&梁望国内创业&诗云科技成立heygen为什么原班人马在国内做和国外做产品&#xff0c;造成的结果如此迥异&#xff1f;技术原理 关于徐卓&梁望 徐卓本科毕业于同济大学&#xff0c;硕士毕业于卡内基梅隆大学计算机专业&#xff0c;之后在 Snap 工作了…

ITSS案例分享 — 强化网络安全保障水平

某科技有限公司成立于2001年&#xff0c;是中国网络安全产业领跑者&#xff0c;于2000年发力安全业务&#xff0c;在云安全、身份安全、终端安全、态势感知、高级威胁治理&#xff0c;以及威胁情报领域等拥有多项全球领先技术&#xff0c;在核心技术领域持续领跑&#xff1b;同…

【每日刷题】Day70

【每日刷题】Day70 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 922. 按奇偶排序数组 II - 力扣&#xff08;LeetCode&#xff09; 2. 905. 按奇偶排序数组 - 力扣&…