-
2021-11-03 16:02:48更多相关内容
-
电脑版微信聊天记录导出打印方法.pdf
2021-11-17 23:57:34电脑版微信聊天记录导出打印方法.pdf -
微信导出聊天记录_android系统.pdf
2021-10-09 18:41:40微信导出聊天记录_android系统.pdf -
微信聊天记录导出(2020新版)
2020-08-16 09:53:43微信聊天记录导出(2020新版) 首先说明,坑的部分主要是数据库破解。 1. 本地备份提取聊天记录 这里主要讲小米手机,苹果手机参考https://www.zhihu.com/question/66251440,其他安卓手机可以用模拟器然后root提取。 ...微信聊天记录导出(2020新版)
首先说明,坑的部分主要是数据库破解。
项目地址:https://github.com/fly-dragon211/Wechat-message-analysis
1. 本地备份提取聊天记录
这里主要讲小米手机,苹果手机参考https://www.zhihu.com/question/66251440,其他安卓手机可以用模拟器然后root提取。
要导出微信安卓客户端的聊天记录,首先得找到聊天记录的数据库。
安卓客户端的聊天记录储存在私有目录/data/data/com.tencent.mm/MicroMsg
下,这个目录需要root权限才能进去,但是,那样太太太麻烦了,好在我们MI6有本地备份的功能,利用这个功能。我们轻而易举就可以获得数据库。需要的工具
-
首先到手机:设置->更多设置->备份和重置->本地备份 里面点击新建备份,选择软件程序中的微信进行备份,注意只选择微信。
-
然后到文件管理
/内部储存设备/MIUI/backup/ALLBackup/
下将备份的文件夹复制到电脑 -
然后用任意一种压缩包软件(我用的是7zip)打开这个
com.tencent.mm.bak
文件,并且将apps\com.tencent.mm\r\MicroMsg\systemInfo.cfg
、apps\com.tencent.mm\r\MicroMsg\CompatibleInfo.cfg
和apps\com.tencent.mm\r\MicroMsg\xxxx\EnMicroMsg.db
三个文件解压到电脑上。这里xxxx是一串随机的字母,代表你的微信用户,每个人不一样,一般是最大的那个文件夹,我这里是下图所示文件夹:
2. 破解数据库密码
找到聊天数据库了,但是目前还不能得到聊天记录,因为这个数据库是
sqlcipher
加密数据库,需要密码才能打开。数据库密码有很多种生成方式:
-
手机
IMEI
+uin
(微信用户iduserinformation
) 将拼接的字符串MD5加密取前7位如
IMEI
为123456
,uin
为abc
,则拼接后的字符串为123456abc
将此字符串用MD5加密(32位)后为
df10ef8509dc176d733d59549e7dbfaf
那么前7位df10ef8
就是数据库的密码,由于有的手机是双卡,有多个IMEI
,或者当手机获取不到IMEI
时会用默认字符串1234567890ABCDEF
来代替,由于种种原因,并不是所有人都能得出正确的密码,此时我们可以换一种方法。 -
反序列化
CompatibleInfo.cfg
和systemInfo.cfg
不管是否有多个
IMEI
,或者是微信客户端没有获取到IMEI
,而使用默认字符串代替,微信客户端都会将使用的信息保存在MicroMsg
文件夹下面的CompatibleInfo.cfg
和systemInfo.cfg
文件中,可以通过这两个文件来得到正确的密码,但是这两个文件需要处理才能看到信息。 -
使用hook方式得到数据库的密码,这个方法最有效参考
-
暴力破解
我开始用反序列化:
javac IMEI.java java IMEI systemInfo.cfg CompatibleInfo.cfg
运行完成后就会得到密码
参考链接但是出现了如下错误:
错误: 找不到或无法加载主类 IMEI 原因: java.lang.ClassNotFoundException: IMEI
于是我换了第一种方法,可是找不到uid
寻找uid
uid不是微信号,原来是保存在下面路径:
/data/data/com.tencent.mm/shared_prefs/auth_info_key_prefs.xml,
在该文件中,键值“auth_uin”即为该用户的uin。
但是我发现根本没有这个路径,可能是我的手机没有root,于是我把备份的文件解压,在里面搜索这个,终于找到了。
最终确定uid,然后MD5(IMEI少一位+uin)的输出字符串作为密码,取前7位小写,就可以破解数据库了。
得到数据库之后可以分析一下你的聊天记录,顺便制作一个词云来给你的心上人看一下你们都聊了啥👀
参考:
https://zhuanlan.zhihu.com/p/77418711
https://github.com/Heyxk/notes/issues/1
https://www.sohu.com/a/355273307_704736
数据分析
下面就是最关键的数据分析。聊天记录包含了非常丰富的数据,这里我只做了两个比较简单的例子,一个是针对时间做聊天时间段分布的统计;一个是针对内容做字符匹配,统计一些高频词汇出现的次数,比如“早安”、“晚安”、“想你”、“爱”等等(你懂的)。
详情见本项目。代码都有注释,我写了一个类把直方图,高频词汇统计,词云等结合起来了。
# -*- coding: utf-8 -*- """ Created on Sun Aug 16 10:41:08 2020 @author: fly """ import numpy as np import pandas as pd import time import re import datetime import seaborn as sns import matplotlib.pyplot as plt from matplotlib.font_manager import * # 如果想在图上显示中文,需导入这个包 import xlwt import wordcloud # 词云 import imageio import jieba # 中文分词 class WechatAnalysis: def __init__(self, data_frame, name): self.data_frame = data_frame self.wechat_name = name self.chat_time, self.chat_content = self.get_time_and_content() self.font_path = r'C:\Windows\Fonts\MSYH.TTC' # 微软雅黑 def get_time_and_content(self): # 把聊天内容和时间取出 chat = self.data_frame chat_time = [] chat_content = [] for i in range(len(chat) - 1): content = chat[i:i + 1] if content['talker'].values[0] == self.wechat_name: t = content['createTime'].values[0] // 1000 # 除以1000用以剔除后三位0 c = content['content'].values[0] chat_time.append(t) chat_content.append(c) return chat_time, chat_content def get_time_hist(self, time_flag=0): """ :param time_flag: 0 hour, 1 year day, 2 month :return: """ # 画小时核密度分布图 str_list = ['小时', '天', '月'] data_list = [self.to_struct_time(t)[time_flag] for t in self.chat_time] # 得到数据列表 max_indx = np.argmax(np.bincount(data_list)) # 出现次数最多的数据 max_num = np.bincount(data_list)[max_indx] # 出现次数 print('\n.......................\n开始画图\n.......................') myfont = FontProperties(fname=r'C:\Windows\Fonts\MSYH.TTC', size=22) # 标题字体样式 myfont2 = FontProperties(fname=r'C:\Windows\Fonts\MSYH.TTC', size=18) # 横纵坐标字体样式 myfont3 = FontProperties(fname=r'C:\Windows\Fonts\FZSTK.TTF', size=18) # 标注字体 sns.set_style('darkgrid') # 设置图片为深色背景且有网格线 if time_flag == 0: sns.distplot(data_list, 24, color='lightcoral') plt.xticks(np.arange(0, 25, 1.0), fontsize=15) plt.ylabel('聊天概率', fontproperties=myfont2) elif time_flag == 1: sns.distplot(data_list, 365, kde=False, color='lightcoral') plt.xticks(np.arange(1, 366, 30), fontsize=15) plt.ylabel('消息条数', fontproperties=myfont2) plt.annotate("这%s我们发了%d条消息!" % (str_list[time_flag], max_num), xy=(max_indx,max_num), fontproperties=myfont3) elif time_flag == 2: sns.distplot(data_list, 12, kde=False, color='lightcoral') plt.xticks(np.arange(0, 12, 1.0), fontsize=15) plt.ylabel('消息条数', fontproperties=myfont2) plt.annotate("这%s我们发了%d条消息!" % (str_list[time_flag], max_num), xy=(max_indx, max_num), fontproperties=myfont3) plt.yticks(fontsize=15) plt.title('聊天时间分布', fontproperties=myfont) plt.xlabel('时间段/' + str_list[time_flag], fontproperties=myfont2) fig = plt.gcf() fig.set_size_inches(15, 8) fig.savefig('chat_time.png', dpi=100) plt.show() print('\n.......................\n画图结束\n.......................') def get_hour_slice(self): hour_set = [self.to_struct_time(t)[0] for t in self.chat_time] print('\n.......................\n开始聊天时段统计\n.......................') time_slice = [0, 0, 0, 0, 0, 0] deep_night = [] for i in range(len(hour_set)): if 0 <= hour_set[i] < 6: time_slice[0] += 1 deep_night.append([self.chat_time[i], self.chat_content[i]]) elif 6 <= hour_set[i] < 10: time_slice[1] += 1 elif 10 <= hour_set[i] < 14: time_slice[2] += 1 elif 14 <= hour_set[i] < 18: time_slice[3] += 1 elif 18 <= hour_set[i] < 22: time_slice[4] += 1 else: time_slice[5] += 1 labels = ['凌晨0点至6点', '6点至10点', '10点至14点', '14点至18点', '18点至22点', '22点至次日凌晨0点'] time_distribution = { labels[0]: time_slice[0], labels[1]: time_slice[1], labels[2]: time_slice[2], labels[3]: time_slice[3], labels[4]: time_slice[4], labels[5]: time_slice[5] } for label in labels: print("{ name: '%s': %d}, \n" % (label, time_distribution[label])) ''' 深夜聊天记录 ''' wbk = xlwt.Workbook() sheet = wbk.add_sheet('late') for i in range(len(deep_night)): sheet.write(i, 0, time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(deep_night[i][0]))) sheet.write(i, 1, deep_night[i][1]) wbk.save('聊得很晚.xls') print('\n.......................\n聊天时段统计结束\n.......................') def get_word_cloud(self, chinese_slice=False, stopwords=None, image_out_name=None): """ :param chinese_slice: Whether Use jieba to slice the sentences. :param stopwords: a set include some words to exclude. :return: """ font_path = self.font_path if image_out_name is None: image_out_name = 'word-heart.png' if chinese_slice: text = ",".join(self.chat_content) text_list = jieba.lcut(text) text = " ".join(text_list) image_out_name = 'zh-'.__add__(image_out_name) else: text = " ".join(self.chat_content) mk = imageio.imread("heart.png") # 构建并配置词云对象w,注意要加scale参数,提高清晰度 w = wordcloud.WordCloud(width=1000, height=700, background_color='white', font_path=font_path, mask=mk, scale=2, stopwords=stopwords, contour_width=1, contour_color='red') # 将string变量传入w的generate()方法,给词云输入文字 w.generate(text) # 展示图片 # 根据原始背景图片的色调进行上色 image_colors = wordcloud.ImageColorGenerator(mk) plt.imshow(w.recolor(color_func=image_colors)) # 根据原始黑白色调进行上色 # plt.imshow(wc.recolor(color_func=grey_color_func, random_state=3), interpolation='bilinear') #生成黑白词云图 # 根据函数原始设置进行上色 # plt.imshow(wc) # 隐藏图像坐标轴 plt.axis("off") plt.show() # 将词云图片导出到当前文件夹 w.to_file(image_out_name) def get_word_statistic(self): ''' 字符统计 ''' chat_content = self.chat_content chat_time = self.chat_time print('\n..........\n开始字符统计\n............\n') start = datetime.datetime.now() pattern_love = '.*?(爱).*?' pattern_morning = '.*?(早).*?' pattern_night = '.*?(晚安).*?' pattern_miss = '.*?(想你).*?' pattern_set = [pattern_love, pattern_morning, pattern_night, pattern_miss, '.*?(在干嘛).*?', '.*?(嘻嘻).*?'] statistic = list(np.zeros(len(pattern_set), dtype=np.int)) for i in range(len(chat_content)): for j in range(len(pattern_set)): length = len(re.findall(pattern_set[j], str(chat_content[i]))) statistic[j] += length print("在%d个日日夜夜里" % ((max(chat_time) - min(chat_time)) // (3600*24))) for i, pattern in enumerate(pattern_set): print("我们说了%d次 %s" % (statistic[i], pattern.strip('.*?()'))) end = datetime.datetime.now() print('\n..........\n字符统计结束,用时: {}\n............\n'.format(end - start)) @staticmethod def to_struct_time(t): struct_time = time.localtime(t) # 将时间戳转换为struct_time元组 hour = round((struct_time[3] + struct_time[4] / 60), 2) month = struct_time.tm_mon yday = struct_time.tm_yday return hour, yday, month if __name__ == '__main__': myGirl = '微信UID !!!' csv_path = '你导出的csv文件' chat = pd.read_csv(csv_path, sep=',', encoding='utf-8', usecols=[6,7,8]) # createTime talker content‘ dtype: float, string,string Wechat = WechatAnalysis(chat, myGirl) # Get the hour distribution Wechat.get_time_hist() Wechat.get_hour_slice() # Get word cloud plt.figure(2) Wechat.get_word_cloud() # Not slice the chinese words plt.figure(3) Wechat.get_word_cloud(stopwords={'嘻嘻', '嗯嗯', '嗯呢', '这样子', '这样子呀', '捂脸'}) Wechat.get_word_statistic()
参考:
- 爱情大数据https://blog.csdn.net/iphilo/article/details/79052325
- 词云可视化https://www.cnblogs.com/wkfvawl/p/11585986.html
-
-
Android 通过AccessibilityService实现微信聊天记录导出
2018-11-30 15:32:33接上Android 微信聊天记录、联系人备份并导出为表格继续讲 不太了解AccessibilityService可以看看这篇文章 基本原理: 首先打开 DDMS 捕捉界面元素 拿到resourceid,调用方法 List<AccessibilityNodeInfo...接上Android 微信聊天记录、联系人备份并导出为表格继续讲
不太了解AccessibilityService可以看看这篇文章
基本原理:
首先打开 DDMS 捕捉界面元素
拿到resourceid,调用方法
List<AccessibilityNodeInfo> mListView = rootNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/a-c");
能拿到一个 listview 的 list, 事实上 mListView 的长度只有1再通过
myListView.get(0)
就可以准确的拿到 listview了,然后可以调用滚动的方法
/* 滚动 */ listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) //向上滚动 listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) //向下滚动
每滚动一次,根据 resourceId 获取当前屏幕的textview 集合,再遍历集合,调用 textview.getText() 方法,就能拿到聊天记录了;
下面直接贴代码,讲的很详细了,1:1的注释
package com.cxk.wechatlog; import android.accessibilityservice.AccessibilityService; import android.content.Intent; import android.graphics.Rect; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Toast; import java.util.List; /** * Created by 曾轲 * <p> * 获取即时微信聊天记录服务类,该功能在微信6.5.8上可正常使用 */ public class WeChatLogService extends AccessibilityService { /** * 聊天对象 */ private String ChatName; /** * 小视频的秒数,格式为00:00 */ private String VideoSecond; private String ChatRecord; /** * 根据聊天列表两次滚动后最底部的人是不是同一个,来判断聊天列表是否到了底部 */ private boolean listIsBottom = false; private String bottomName = ""; /** * 根据聊天消息滚动前后三条是否重复,用来判断消息界面是否滚动到了最顶部 */ private boolean chatMessageIsTop = false; private String chatMessage1 = ""; private String chatMessage2 = ""; private String chatMessage3 = ""; String topMessage1= ""; String topMessage2= ""; String topMessage3= ""; /** * 聊天人的姓名 */ String chatName= ""; /** * 聊天信息保存目录 */ private String mCurrApkPath = Environment.getExternalStorageDirectory().getPath() + "/"; @Override public void onAccessibilityEvent(AccessibilityEvent event) { int eventType = event.getEventType(); switch (eventType) { case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { String currentActivity = event.getClassName().toString(); //如果在微信界面 if (currentActivity.equals(WeChatTextWrapper.WechatClass.WECHAT_CLASS_LAUNCHUI)) { Log.e("界面","微信主页"); AccessibilityNodeInfo rootNodeInfo = getRootInActiveWindow(); //获取聊天列表的 listview List<AccessibilityNodeInfo> listview = rootNodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/bny"); if (listview!=null&&listview.size() != 0) { //如果 listview 没有滑动到最底部,就一直无限循环遍历聊天记录 while (!listIsBottom) { //获取当前页面聊天 list的单个条目和聊天人名字 List<AccessibilityNodeInfo> ChatList = rootNodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/agu"); List<AccessibilityNodeInfo> NameList = rootNodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/agw"); //如果没有聊天信息,直接跳出,并终止循环 if(NameList.size()==0||NameList==null){ listIsBottom=true; return; } //获取当前list 最后一个人员的名字,与上一次滑动比较,如果相同说明到了最底部 String lastName = NameList.get(NameList.size() - 1).getText().toString(); if (bottomName.equals(lastName)) { listIsBottom = true; } else { bottomName = lastName; listIsBottom = false; //遍历循环列表,进入聊天详情页进行备份 if (ChatList!=null&ChatList.size() != 0) { for (int j = 0; j < ChatList.size(); j++) { //页面停顿防止过快无法找到目标 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } //进入聊天界面 ChatList.get(j).performAction(AccessibilityNodeInfo.ACTION_CLICK); //延迟防止页面还没完全载入,拿不到根节点信息 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } AccessibilityNodeInfo chatRootNodeInfo = getRootInActiveWindow(); //遍历下一个人的聊天记录,需要重置定置状态 chatMessageIsTop=false; getWeChatLog(chatRootNodeInfo); } } //循环遍历完当前页面的列表后滑动遍历下一个页面 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } listview.get(0).performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }else{ } } else if (currentActivity.equals(WeChatTextWrapper.WechatClass.WECHAT_CLASS_CONTACTINFOUI)) { Log.e("界面","联系人界面"); } else if (currentActivity.equals(WeChatTextWrapper.WechatClass.WECHAT_CLASS_CHATUI)) { Log.e("界面","聊天界面"); } } break; } } /** * 遍历获取聊天消息 * @param rootNode */ private void getWeChatLog(AccessibilityNodeInfo rootNode) { if (rootNode != null) { //获取聊天人的姓名 List<AccessibilityNodeInfo> listName = rootNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/gp"); if(listName!=null&&listName.size()!=0){ chatName = listName.get(0).getText().toString(); Log.e("name",chatName); } //获取聊天详情页的listview List<AccessibilityNodeInfo> listChatRecord = rootNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/a3e"); //如果没有聊天记录,直接返回 if(listChatRecord!=null&&listChatRecord.size()==0){ Log.e("消息类型","当前没有聊天记录"); performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); return; } //利用姓名创建文件 //当消息详情不在最顶部时无限循环遍历消息记录 while(!chatMessageIsTop){ //有聊天记录,开始遍历循环 //获取聊天头像list List<AccessibilityNodeInfo> imageName = rootNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/ik"); //获取聊天信息list List<AccessibilityNodeInfo> record = rootNode.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/im"); //有聊天头像说明有聊天记录(但是不一定有文字信息,可能是图片,分享等等) if (imageName!=null&&imageName.size() != 0) { //如果record的 size 为零说明当前截取到的 list 没有文字 if (record.size() == 0) { Log.e("消息类型","当前没有文字消息"); //判断当前这条消息是不是和上一条一样,防止重复 //获取聊天对象 ChatName = imageName.get(0).getContentDescription().toString().replace("头像", ""); Log.e("AAAA", ChatName + ":" + "对方发的是图片或者表情"); try { Thread.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); } //当前 listview 显示的都是非文字消息,乡下滚动一次后再次遍历循环 listChatRecord.get(0).performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); return; //有文本消息,开始辨析备份文本消息 }else { //获取当前页面顶部的三条文字,与上一次滚动的三条文字对比,判断是否滚动到最顶部了 switch (record.size()) { //判断 size 防止索引越界 case 1: topMessage1 = record.get(record.size() - 1).getText().toString(); topMessage2=""; chatMessage2=""; topMessage3=""; chatMessage3=""; break; case 2: topMessage1 = record.get(record.size() - 1).getText().toString(); topMessage2 = record.get(record.size() - 2).getText().toString(); topMessage3=""; chatMessage3=""; break; case 3: topMessage1 = record.get(record.size() - 1).getText().toString(); topMessage2 = record.get(record.size() - 2).getText().toString(); topMessage3 = record.get(record.size() - 3).getText().toString(); break; default: topMessage1 = record.get(record.size() - 1).getText().toString(); topMessage2 = record.get(record.size() - 2).getText().toString(); topMessage3 = record.get(record.size() - 3).getText().toString(); break; } //如果三条消息都重复证明已经滚动到了最顶部,返回聊天列表 if(chatMessage1.equals(topMessage1)&&chatMessage2.equals(topMessage2)&&chatMessage3.equals(topMessage3)){ chatMessageIsTop=true; performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); }else{ //不是第一条,重新赋值最后三条消息记录,遍历保存文字信息 chatMessage1=topMessage1; chatMessage2=topMessage2; chatMessage3=topMessage3; chatMessageIsTop=false; //遍历获取文字,并写入文件 for (int i = record.size()-1; i >= 0; i--) { //保存消息 //Log.e("文字消息",record.get(i).getText().toString()); //Log.e("index",record.get(i).toString()); //获取文本消息的位置,根据位置来判断但前消息是谁发的 Rect outBounds = new Rect(); record.get(i).getBoundsInScreen(outBounds); //因为对方发送的消息的左侧 left 坐标是永远不会变的,自己发送的消息会被换行折叠,因此可以用此来区分 String leftIndex = outBounds.left+""; //不同手机这个值不一样,需要自己适配 if(leftIndex.equals("156")){ //对方发送的消息 saveMassage(chatName,record.get(i).getText().toString()); }else{ //自己发送的消息 saveMassage("我",record.get(i).getText().toString()); } } //遍历完毕后滚动下一屏聊天记录开始,并停止.8秒等待聊天记录加载 listChatRecord.get(0).performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); try { Thread.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); } } } //有聊天列表但是没有聊天头像和文字消息.类似于招行信用卡的公众号,不作处理直接返回聊天列表遍历下一个 }else{ chatMessageIsTop=true; performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } } private void saveMassage(String chatName, String message) { } /** * 遍历所有控件,找到头像Imagview,里面有对联系人的描述 */ private void GetChatName(AccessibilityNodeInfo node) { for (int i = 0; i < node.getChildCount(); i++) { AccessibilityNodeInfo node1 = node.getChild(i); if ("android.widget.ImageView".equals(node1.getClassName()) && node1.isClickable()) { //获取聊天对象,这里两个if是为了确定找到的这个ImageView是头像的 if (!TextUtils.isEmpty(node1.getContentDescription())) { ChatName = node1.getContentDescription().toString(); if (ChatName.contains("头像")) { ChatName = ChatName.replace("头像", ""); } } } GetChatName(node1); } } /** * 必须重写的方法:系统要中断此service返回的响应时会调用。在整个生命周期会被调用多次。 */ @Override public void onInterrupt() { Toast.makeText(this, "我快被终结了啊-----", Toast.LENGTH_SHORT).show(); } /** * 服务开始连接 */ @Override protected void onServiceConnected() { Toast.makeText(this, "服务已开启", Toast.LENGTH_SHORT).show(); super.onServiceConnected(); } /** * 服务断开 * * @param intent * @return */ @Override public boolean onUnbind(Intent intent) { Toast.makeText(this, "服务已被关闭", Toast.LENGTH_SHORT).show(); return super.onUnbind(intent); } /** * 遍历所有控件:这里分四种情况 * 文字聊天: 一个TextView,并且他的父布局是android.widget.RelativeLayout * 语音的秒数: 一个TextView,并且他的父布局是android.widget.RelativeLayout,但是他的格式是0"的格式,所以可以通过这个来区分 * 图片:一个ImageView,并且他的父布局是android.widget.FrameLayout,描述中包含“图片”字样(发过去的图片),发回来的图片现在还无法监听 * 表情:也是一个ImageView,并且他的父布局是android.widget.LinearLayout * 小视频的秒数:一个TextView,并且他的父布局是android.widget.FrameLayout,但是他的格式是00:00"的格式,所以可以通过这个来区分 * * @param node */ public void GetChatRecord(AccessibilityNodeInfo node) { for (int i = 0; i < node.getChildCount(); i++) { AccessibilityNodeInfo nodeChild = node.getChild(i); //聊天内容是:文字聊天(包含语音秒数) if ("android.widget.TextView".equals(nodeChild.getClassName()) && "android.widget.RelativeLayout".equals(nodeChild.getParent().getClassName().toString())) { if (!TextUtils.isEmpty(nodeChild.getText())) { String RecordText = nodeChild.getText().toString(); //这里加个if是为了防止多次触发TYPE_VIEW_SCROLLED而打印重复的信息 if (!RecordText.equals(ChatRecord)) { ChatRecord = RecordText; //判断是语音秒数还是正常的文字聊天,语音的话秒数格式为5" if (ChatRecord.contains("\"")) { Toast.makeText(this, ChatName + "发了一条" + ChatRecord + "的语音", Toast.LENGTH_SHORT).show(); Log.e("WeChatLog",ChatName + "发了一条" + ChatRecord + "的语音"); } else { //这里在加多一层过滤条件,确保得到的是聊天信息,因为有可能是其他TextView的干扰,例如名片等 if (nodeChild.isLongClickable()) { Toast.makeText(this, ChatName + ":" + ChatRecord, Toast.LENGTH_SHORT).show(); Log.e("WeChatLog",ChatName + ":" + ChatRecord); } } return; } } } //聊天内容是:表情 if ("android.widget.ImageView".equals(nodeChild.getClassName()) && "android.widget.LinearLayout".equals(nodeChild.getParent().getClassName().toString())) { Toast.makeText(this, ChatName+"发的是表情", Toast.LENGTH_SHORT).show(); Log.e("WeChatLog",ChatName+"发的是表情"); return; } //聊天内容是:图片 if ("android.widget.ImageView".equals(nodeChild.getClassName())) { //安装软件的这一方发的图片(另一方发的暂时没实现) if("android.widget.FrameLayout".equals(nodeChild.getParent().getClassName().toString())){ if(!TextUtils.isEmpty(nodeChild.getContentDescription())){ if(nodeChild.getContentDescription().toString().contains("图片")){ Toast.makeText(this, ChatName+"发的是图片", Toast.LENGTH_SHORT).show(); Log.e("WeChatLog",ChatName+"发的是图片"); } } } } //聊天内容是:小视频秒数,格式为00:00 if ("android.widget.TextView".equals(nodeChild.getClassName()) && "android.widget.FrameLayout".equals(nodeChild.getParent().getClassName().toString())) { if (!TextUtils.isEmpty(nodeChild.getText())) { String second = nodeChild.getText().toString().replace(":", ""); //正则表达式,确定是不是纯数字,并且做重复判断 if (second.matches("[0-9]+") && !second.equals(VideoSecond)) { VideoSecond = second; Toast.makeText(this, ChatName + "发了一段" + nodeChild.getText().toString() + "的小视频", Toast.LENGTH_SHORT).show(); Log.e("WeChatLog","发了一段" + nodeChild.getText().toString() + "的小视频"); } } } GetChatRecord(nodeChild); } } }
package com.cxk.wechatlog; public class WeChatTextWrapper { public static final String WECAHT_PACKAGENAME = "com.tencent.mm"; public static class WechatClass{ //微信首页 public static final String WECHAT_CLASS_LAUNCHUI = "com.tencent.mm.ui.LauncherUI"; //微信联系人页面 public static final String WECHAT_CLASS_CONTACTINFOUI = "com.tencent.mm.plugin.profile.ui.ContactInfoUI"; //微信聊天页面 public static final String WECHAT_CLASS_CHATUI = "com.tencent.mm.ui.chatting.ChattingUI"; } public static class WechatId{ /** * 通讯录界面 */ public static final String WECHATID_CONTACTUI_LISTVIEW_ID = "com.tencent.mm:id/ih"; public static final String WECHATID_CONTACTUI_ITEM_ID = "com.tencent.mm:id/iy"; public static final String WECHATID_CONTACTUI_NAME_ID = "com.tencent.mm:id/j1"; /** * 聊天界面 */ public static final String WECHATID_CHATUI_EDITTEXT_ID = "com.tencent.mm:id/a_z"; public static final String WECHATID_CHATUI_USERNAME_ID = "com.tencent.mm:id/ha"; public static final String WECHATID_CHATUI_BACK_ID = "com.tencent.mm:id/h9"; public static final String WECHATID_CHATUI_SWITCH_ID = "com.tencent.mm:id/a_x"; } }
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.cxk.wechatlog"> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".WeChatLogService" android:enabled="true" android:exported="true" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/wechatlog_service_config"></meta-data> </service> </application> </manifest>
源码地址 :
https://github.com/KeZengOo/AccessibilityServiceWeChatHelper
注意事项 :
1.微信每一次更新都会导致 resourceId 变化,所以尽量不要更新或者及时更改代码中的 resourceId
2.旧版本的微信每一个文本框都是用的 textview,可以直接调用 getText()获取聊天文字,新版本为了防止这个操作,变成自定义的 View 了,目前我还没有找到解决方案....希望有大神能点拨点拨...
3.我的代码在微信6.5.8上运行完美,高于该版本的都不行(参考第二条)
4.目前只是第一个 demo 版本,有很多不足的地方,遍历到公众号,等非聊天窗口时,可能会有异常
5.安装后必须在设置里打开辅助功能里的对应开关,再切换到微信,即可正常运行
6.如果觉得还不错,请给我点个赞吧
-
微信聊天记录导出工具WeChatExporter开源啦!
2018-08-07 17:57:53微信聊天记录导出工具。无需越狱手机,即可导出备份微信聊天记录。目前支持文字、语音、图片、视频的查看。 项目基于nodejs实现,框架采用angularjs 目前支持导出iOS系统导出,软件运行仅限MacOS系统。(其实安卓...【2019年08月21日更新】
距离第一次发布软件已经有了许多新功能和稳定性上的提升,本文的一些内容已经过时,欢迎直接到GitHub上看ReadMe:https://github.com/tsycnh/WeChatExporter
之前曾经写过一个导出微信聊天记录的工具,偶尔自己用一下,现在免费开源出来,希望大家喜欢。
WeChatExporter
微信聊天记录导出工具。无需越狱手机,即可导出备份微信聊天记录。目前支持文字、语音、图片、视频的查看。
项目基于nodejs实现,框架采用angularjs目前支持导出iOS系统导出,软件运行仅限MacOS系统。(其实安卓和Windows系统也能用,只是现在懒得适配多平台)
项目地址:https://github.com/tsycnh/WeChatExporter
使用方法:一、准备工作
Step1:数据导出:
首先需要将微信聊天数据进行导出。目前只支持iOS系统,如果你用的是安卓机,可以尝试将聊天记录迁移到iPad上,再导出。按照下图使用iTunes备份整机数据,注意不要选择给iPhone备份加密
使用第三方软件导出微信备份数据,这里使用的是iMazing,需要导出的是Documents文件夹。
Step2:安装nwjs(0.23.1版本) 官网:https://nwjs.io
二、运行软件
Step1:下载项目
git clone https://github.com/tsycnh/WeChatExporter
Step2:
cd path/to/WeChatExporter
Step3:
cd development
Step4: 运行nwjs
/path/to/nw/nwjs.app/Contents/MacOS/nwjs .
即可运行导出工具。
三、使用软件
目前工具由三部分组成:
soft1: 用来查看并确定要导出的聊天对象
soft2:用来导出并转换数据
soft3:直接查看聊天内容
Step1: 点击soft1进入分析模式,输入导出的Documents文件夹路径,然后进入分析模式
Step2: 左上角显示的是在当前手机上登陆过的微信帐号,点击任意一个将在左下角显示和你聊过天的朋友,默认只显示聊天消息总数超过100的朋友(或群聊)。
Step3:点击左下角任意一聊天对象,会在右侧显示10条最近的聊天记录,以做确认之用。
Step4:这时右上角会显示两串红色的字符,分别是你的微信账户和聊天对象(均经过MD5加密)。将这两个数值复制下来。
Step5:点击左上角微信备份按钮跳转到主页,点击Soft2 进入解析多媒体模式。
Step6:按要求填写表单,日期区间可以控制导出聊天记录的时间范围,默认不填表示全部导出。然后点击开始生成数据。生成结束后会得到一个文件夹,即
path/to/output
里面存放了所有需要的信息。至此Documents目录已经没有用了,可以删除。
Step7:回到主页进入Soft3 页面,输入刚到导出的output目录,即可开始查看导出的聊天记录了。
之后再查看直接进入Soft3页面即可。
PS:目前有些流程还是有些累赘和繁琐,有待改进
欢迎有能力同学来对这个项目做贡献!
项目地址:https://github.com/tsycnh/WeChatExporter
待添加功能
- soft1和soft2合并
- 为微信用户添加头像
- 为微信用户添加昵称
- 导出html功能
- 聊天查看页面增加图像点击放大
-
微信聊天记录导出到电脑的方法.doc
2021-10-10 16:18:37微信聊天记录导出到电脑的方法.doc -
微信聊天记录导出(iOS) [2019.7.24]
2019-07-24 14:05:44博客地址:https://www.busby.com.cn/2019/07/24/微信聊天记录导出(iOS)[2019.7.24]/ 最前 前不久很久,我的小傻瓜女票误删了我们俩的微信聊天记录,也没有iOS系统或PC端微信聊天记录的备份。无奈微信聊天记录在... -
微信聊天记录导出为电脑txt文件教程
2019-08-09 11:35:49本文的最终目的是将手机微信的聊天记录导出到电脑里,变成txt文本文件,然后对其进行分析。 网上有一些工具也可以完成这个功能,但是基本都是付费的。手动操作的话,找了很多的博客,基本没有完全有效的。最终找到... -
android微信聊天记录导出到电脑【微信安卓版技巧】
2015-05-18 09:04:37android微信聊天记录导出到电脑【微信安卓版技巧】 微信,对它又爱又恨!爱的是微信能替代很多手机通话短信,恨的是有些较早前的手机不能友好支持,比如ytkah之前用的i8000,挺上手的,就是没办法装... -
微信聊天记录导出
2018-01-03 02:32:58关注过我的人大概都知道,2016年我就实现了一版基于iOS的聊天记录导出功能(https://zhuanlan.zhihu.com/p/23034838),但因为现在绝大多数用户的iPhone都没越狱,我就只能发布非官方的微信版本,可这样就算是违背... -
记录导出微信聊天记录到硬盘的过程
2022-02-13 13:09:55本文记载了将微信聊天记录导出到电脑成html文件到过程 -
分享一个非常不错的微信聊天记录导出软件
2020-05-07 02:53:23微信里积累了数年的聊天记录,连iPhone都吃不消了,可惜你依旧不能删掉它们。把重要的聊天记录导...第二部 聊天记录导出 根据选择的账号和联系人导出聊天记录,瞬间即可导出选中的聊天记录。支持增量导出,即有新... -
微信聊天记录导出+自动聊天机器人
2020-06-20 12:17:54最近想做一个可以无聊的时候和微信好友对话的功能,用到些许...首先是导出微信聊天记录到txt: https://blog.csdn.net/swinfans/article/details/88712593 此处假设大家已经完成了聊天记录导出到txt的任务... ... -
如何导出微信聊天记录
2021-04-22 15:09:39有时候聊天记录里有些碎片化的记录想要整理出来单独看,所以去调研了一下有什么好的办法 太长不看版 最便捷的方式是多选,然后邮件发送,然后稍微处理下格式就行了! 想自己捣鼓一下的: ios的朋友可以试 -
导出 Mac 版微信聊天记录
2021-03-29 12:46:21macOS 微信的“备份与恢复”功能只能从手机微信导出到 Mac, 但是微信其实又在本地存了加密的 sqlite3 数据库; 本地数据库的是一系列 *.db 文件,可以用如下命令查看, ls -alh ~/Library/Containers/... -
Python 情人节超强技能 导出微信聊天记录生成词云,深入讲解Python
2022-03-22 18:14:30fetchall返回筛选结果 data=open(“聊天记录.txt”,‘w+’,encoding=‘utf-8’) for i in value: data.write(i[0]+’\n’) 将筛选结果写入 聊天记录.txt data.close() cursor.close() conn.close() 关闭连接 记得把... -
超简单:mac导出微信聊天记录(附上粉丝群全部聊天记录)
2021-09-13 11:49:40之前给大家详细讲解过如何用小米手机导出微信聊天记录:godweiyang:微信聊天记录导出为电脑txt文件教程今天再给大家讲解一下如何直导出mac版本微信的聊天记录,当然如果你没有mac,那可以直接关闭这篇文章了。... -
最全的ios系统导出微信聊天记录&生成词云教程
2019-05-22 00:10:27对于如何导出手机上的微信聊天记录,网上绝大部分教程提到的“楼月微信聊天记录导出恢复助手”和“手机博士微信聊天记录查看”等软件都是收费的,免费版本只能查看很少的几条聊天记录并且不能导出。在这里提供一种... -
520小玩意之Python词云:导出与女票的微信聊天记录并分析
2020-05-20 10:53:171.导出Mac版微信聊天记录 Mac版微信在本地存放了聊天记录的数据库,数据库使用的是开源的 sqlcipher加密了里面的数据。在终端输入下面命令,可以查看这些数据库的路径。 ls -alh ~/Library/Containers/... -
Mac导出微信聊天记录到world
2020-05-02 00:02:43很多人都知道PC版微信软件可以对手机微信聊天记录进行备份,于是很多人产生了一个疑问,怎么在电脑上打开这些微信聊天记录进行查看呢?如何把这些记录保存到Word文档或txt文本中呢?如何导出里面的语音消息,小视频... -
微信聊天数据导出
2021-04-15 10:00:041、使用iPhone手机,聊天记录都已经在手机上; 2、使用Mac电脑上的iTunes连接iPhone,请注意,不要选择加密iPhone备份内容,然后把手机上的资料库整个同步到Mac电脑; 3、去http://wxbackup.imxfd.com点下载软件... -
更换Mac电脑如何迁移微信端聊天记录(以Timemachine备份情况为例)
2021-01-08 01:22:08以Timemachine备份为例,如果没有timemachine没有备份,也可以参照下面方法在旧Mac电脑上操作,核心方式就是找到旧数据用替换的方式迁移旧的微信数据,以保证数据不丢失。  搜索输入:com.tencent.xinWeChat ... -
获取微信的聊天记录导出为Excel
2019-07-31 18:15:33获取微信的聊天记录导出为Excel (ios端) 工具 iTunes 楼月免费iTunes备份管理器 DB Browser for SQLite python 步骤 通过iTunes备份ipone中的数据到电脑上, 打开楼月免费iTunes备份管理器选择备份的记录 导出... -
[ 实践 ] 将安卓微信聊天记录导出为可读格式的一些记录
2019-11-23 14:04:56现在大部分人都在用微信进行通信,微信官方宣称服务器不会存储用户的聊天信息,那好,微信的聊天记录存储在哪里?以什么方式进行存储的? 网上查证,微信的聊天记录是在/data/data/com.tecent.mm/MicroMsg目录下... -
菜鸟记录:安卓手机导出微信聊天记录
2020-12-20 21:22:42安卓手机导出微信聊天记录 [ios导出文末有文章,但没试过]大致流程!!第一步:安卓手机获取root权限(必须获取)一、备份聊天记录到电脑第二步:电脑安装手机模拟器第三步:获取聊天记录数据库 EnMicroMsg.db第四步:... -
【安卓wechat微信导出聊天记录】
2021-12-27 10:09:025、将电脑微信聊天记录再次备份到模拟器微信里。如果你的电脑微信被退出了,可以登录模拟器微信,点击扫码登录哦。 6、打开右侧小图标,点击打开安卓文件夹 7、左侧点击根目录,进入/data/data/com.tencent.m -
通过微信聊天记录生成词云
2022-05-19 21:18:40对于微信聊天记录,可以通过采集聊天记录,通过聊天数据生成词云两步实现 一、聊天记录数据采集 整个任务中最困难的即为聊天数据采集。由于QQ和微信的聊天记录提取难度不同,对于QQ聊天数据,简单的导出即可。对于... -
坚果pro微信聊天记录导出
2021-11-09 20:40:26用9008把userdata分区导出, 参考:https://zhuanlan.zhihu.com/p/35422254 可能会失败 windows api readfile failed your device is probably not on this port 01:47:56: ERROR: function: sahara_rx_data:194 ...