简介:这是一款用于检测微信单向好友或拉黑好友的绿色工具,面向需要管理微信社交关系的普通用户、微商及运营人员。软件采用本地Web服务架构,双击运行主程序后自动启动浏览器操作界面,无须额外配置,对非技术用户非常友好。资源包共68个文件,包含exe主程序、js前端脚本、pyd核心功能模块、conf配置文件、dll运行库等,压缩包大小27.16MB,整体为免安装绿色结构,解压即可使用。运行时需占用6379、81、8001端口,分别承担Redis缓存、微信协议解析与网页服务,包内已集成相关依赖,并提供使用前必读说明及常见问题提示,方便快速排障。目前已有472人学习下载,适合希望定期清理单向好友、识别拉黑状态、维护微信人脉质量的个人与从业者。借助该工具可自动区分好友关系类型并清晰展示检测结果,同时针对登录设备等细节给出建议,节省大量手动核对时间,是实用的日常社交管理辅助工具。
1. 微信好友检测工具到底在检测什么
“微信好友检测工具”这个标题,按字面理解就是个自动化试探机器人:把人工一条条发消息、看系统反馈、再记结果的动作,变成脚本循环执行。好友把你删了,发消息会弹“对方开启了朋友验证”;拉黑你,消息显示“被对方拒收了”。这两种反馈文案,就是工具判断的依据。做私域运营、社群管理,或者微信好友过了几百上千的人,最需要它——月底清理一波死好友,避免真正群发的时候被系统频繁拦截。这篇笔记按相对安全的 UI 自动化路线讲,不碰逆向和协议破解,适合想自己搭一个检测工具、又不想拿账号冒险的人。
2. 没有官方接口,检测的本质是试探系统反馈
微信从来就没有开放过“谁删除我、谁拉黑我”的查询能力,也不存在任何公开 API 能一次性返回好友关系状态。所以市面上的检测工具,本质思路都是同一个:主动制造一个需要系统校验好友关系的动作,然后读取系统返回的文案。文案是判断依据,代码只是把动作自动化而已。这个思路听起来简单,但落地时的坑非常多,因为不同版本的微信、不同状态下返回的提示文案不完全一样。
2.1 三种试探手段和它们的反馈特征
常见做法有三种:发普通消息、发起转账、拉群聊。三种手段触发的系统反馈不一样,对对方的打扰程度也差很多。
| 试探手段 | 关系正常时看到的反馈 | 被删除时 | 被拉黑时 | 对对方的打扰程度 |
|---|---|---|---|---|
| 发普通消息 | 正常送达,无额外提示 | 红色感叹号 + “对方开启了朋友验证” | 消息旁边没有感叹号,但页面出现“消息已发出,但被对方拒收了” | 低,对方正常时能看到消息 |
| 发起转账(0.01元) | 进入支付密码/指纹验证页,可直接取消 | 出现“请确认你和他(她)的好友关系是否正常” | 同样出现好友关系异常提示 | 极低,对方无感知,但自己操作略复杂 |
| 发起群聊 | 成员正常进群 | “无法邀请xxx,请先发送朋友验证请求” | 部分情况下能邀请进群,但对方会收到群聊提醒 | 高,不推荐 |
从表里能看出,发消息法对“单删”最敏感,系统直接弹窗告诉你对方不是你好友;拉黑则是在会话页里显示一行小字,且消息没有红色感叹号。转账法对两种异常都有效,但它的真正价值是“二次复核”——当你不确定消息法判得对不对时,用转账法再确认一次,因为正常好友走到支付验证页就停,不会真扣款。拉群聊法我会直接放弃,它会给对方推送一个建群通知,打扰程度太高,而且拉黑场景下反馈不稳定,性价比太差。
2.2 UI 自动化与协议 hook 的取舍
一搜“微信检测”很容易搜到所谓 hook 方案、协议方案。这些方案速度快,理论上能一次性拉出所有好友关系,但代价是:需要修改客户端注入代码,或者依赖非公开协议模拟请求,封号阈值很低,而且微信每次更新协议都要跟着适配。普通开发者既拿不到稳定的协议通道,又没法承担封号风险。见过太多拿 hook 方案跑检测结果把号跑没的案例,血泪教训放在这儿:天下没有免费的秒查,速度越快的方案,风险跟着你的账号走。
UI 自动化则完全不同,它只是模拟人类操作:打开聊天页、输入消息、读屏幕上的提示文案、再点返回。全程不碰微信的数据接口,微信眼里你就是一个“发消息特别有耐心的人”。缺点也很明显,慢,一千个好友可能要跑半小时以上。但对个人整理好友列表来说,半小时换一个安全的结果,完全值得。这套思路的另一个好处是,它不依赖任何非公开技术,微信更新了,最多改一下控件定位或者文案关键词,不会整体失效。
2.3 技术栈:为什么选 uiautomator2 而不是 Appium
UI 自动化的方案里,Appium 是很多人先想到的,它强在跨平台、企业级测试,但为了一个检测脚本去起 Appium Server、配 DesiredCapabilities,实在有点杀鸡用牛刀。我一般会用 uiautomator2,它是针对安卓的轻量自动化库,pip 装完直接连设备,支持用文本、类名、控件属性定位页面元素,写起来像在操作一个虚拟手指。先确认环境可用:
pip install -U uiautomator2 python -c "import uiautomator2; print(uiautomator2.__version__)"参数说明:第一行安装库本身,第二行验证安装结果。如果第二行报模块找不到,多半是 pip 装到了别的 Python 环境,检查一下当前解释器和 pip 是否一致。设备方面,准备一台安卓手机或者模拟器,开启开发者选项和 USB 调试,微信要能正常登录。iOS 不是不能做,但控件遍历和输入控制麻烦得多,不在这次讨论范围内。
3. 环境准备和控件定位:用 Python 控制微信的第一步
环境准备是这类脚本里看起来最枯燥、实际上最容易翻车的一步。很多人代码逻辑写好了,结果卡在“连不上设备”或者“控件定位不到”。第三节把每一步拆开,照着做就能把基础打好。
3.1 三行命令装好环境
设备连上电脑后,先确认 adb 能看到设备,然后让 uiautomator2 初始化手机上的自动化组件:
adb devices python -m uiautomator2 init逻辑说明:adb devices用来确认设备已被识别,看到device状态才算正常,如果是unauthorized,要在手机上允许 USB 调试授权。接下来python -m uiautomator2 init会在手机上安装自动化运行所需的组件,首次执行时手机会有安装确认提示,点允许即可。参数说明:如果后面u2.connect()连不上,先回来检查这两步;模拟器用户用adb connect 127.0.0.1:端口号替代 USB 线连接。
3.2 连接设备并打开微信
初始化完成后,Python 里这样连接设备并拉起微信:
import uiautomator2 as u2 import time d = u2.connect("emulator-5554") # 换成你的设备序列号 d.app_start("com.tencent.mm") # com.tencent.mm 是微信的包名 time.sleep(3)代码说明:u2.connect()里的参数是设备序列号,可以从adb devices的输出里复制;app_start通过包名启动应用,比用坐标点击桌面图标可靠得多;time.sleep(3)给微信留出启动时间。参数说明:如果手机上没有打开 USB 调试授权,或者 init 没跑成功,这一行就会抛连接异常,报错信息里通常会讲清楚是连接被拒还是组件缺失。
3.3 定位聊天输入框:别死记 resourceId
很多新手写控件定位时喜欢抄网上的resourceId,比如com.tencent.mm:id/xxx,然后微信一更新就废了。正确的做法是用文本和结构特征定位。聊天页输入框的特征是:它是当前屏幕上位置最低的那个文本框,顶部搜索框不算。基于这个特征写一个通用的查找函数:
def find_chat_input(d): """微信聊天页里取最低处的 EditText,避开顶部搜索框""" target = None max_bottom = -1 for edit in d(className="android.widget.EditText"): info = edit.info bottom = info["bounds"]["bottom"] if bottom > max_bottom: max_bottom = bottom target = edit return target代码说明:遍历当前页面所有EditText,比较它们底部坐标,返回最靠下的那一个。聊天页里输入框一定在屏幕底部区域,而搜索框在顶部,用“最低”这个特征可以避开版本差异。参数说明:如果返回None,说明当前页面可能不在聊天界面,或者弹窗盖住了输入框,先手动看一眼手机屏幕再调。想可视化确认控件结构时,可以启动 weditor:
python -m weditor它会自动打开一个浏览器页面,左侧是手机实时截屏,右侧是控件树,点控件能看到它的文本、类名、坐标。这个工具是调试定位问题的利器,强烈建议在写脚本前先花十分钟把目标页面控件看一遍。
3.4 聊天页弹窗的通用清理
微信启动时经常弹“隐私保护指引”“青少年模式”之类的窗口,弹窗会挡住后面的所有点击。检测逻辑跑之前,先做一次弹窗清理:
if d(textContains="隐私保护提示").exists(timeout=2): d(text="同意").click() time.sleep(1)代码说明:textContains是模糊匹配,比text=全匹配更抗版本变化;exists(timeout=2)的意思是等待最多 2 秒,窗口出现就处理,不出现就跳过。参数说明:弹窗文案随微信版本变化,建议在 weditor 里确认当前版本的按钮文本,把关键词维护成一个列表。处理完弹窗再进入检测流程,能省掉后面大量的定位失败排查。
4. 核心检测循环:发消息与转账拦截的组合判定
这一章是整个工具的心脏。检测循环的基本单元是:搜索好友、进入聊天页、执行探测动作、读取系统反馈、记录结果、返回列表。把单次动作做对了,批量只是加一个循环的问题。我按“单删检测”“拉黑检测”两条线分别讲,最后给出合并的全量遍历代码。
4.1 单删检测:发一条消息,读“朋友验证”弹窗
单删检测用发消息法。给好友发一条正常的问候,如果对方删了你,屏幕上会立刻弹灰色提示“对方开启了朋友验证,你还不是他朋友”;如果关系正常,这条消息会安静地送达。代码如下:
def check_single_deleted(d): """发一条消息,根据弹窗判断是否被删除""" edit = find_chat_input(d) if edit is None: return "unknown" edit.set_text("你好") d(text="发送").click() # 情况1:被删除,弹窗提示 if d(textContains="开启了朋友验证").exists(timeout=3): d(text="确定").click() if d(text="确定").exists(timeout=1) else None return "deleted" # 情况2:被拉黑,会话内提示 if d(textContains="被对方拒收").exists(timeout=2): return "blocked" # 情况3:消息正常抵达 return "normal"代码说明:先定位输入框,写入“你好”,点击发送,然后依次检查两类异常文案。注意“被删除”和“被拉黑”的反馈形态完全不同:被删除是弹窗,需要点掉;被拉黑是会话页内部的一行小字,没有弹窗,所以分别用textContains去匹配。参数说明:timeout=3是等待弹窗出现的最大秒数,微信弹窗通常 0.5 秒内就出现,给 3 秒是为了兼容慢速手机;如果 3 秒没出现,基本可以判定消息发出去了。不要自动撤回,因为“撤回一条消息”的系统提醒对正常好友来说比收到“你好”更像骚扰,被删和拉黑的好友本来就收不到消息,更没必要撤。
4.2 拉黑检测:从会话内提示到转账复核
消息法已经能识别拉黑,但存在一个盲区:部分被拉黑的好友会屏蔽一切消息发送,连“被拒收”的提示都不稳定出现。这种情况要用转账法复核。转账金额固定填 0.01,流程是:进入聊天页、点加号、选转账、填金额、点转账,然后根据页面反馈判断,关键是一旦走到支付验证页立即取消,不会造成实际扣款:
def check_blocked_by_transfer(d): """用0.01元转账做二次复核,正常好友会进入支付验证页""" # 点击输入框右侧的加号 plus_btn = d(text="+") if not plus_btn.exists(timeout=2): return "unknown" plus_btn.click() time.sleep(1) d(text="转账").click() time.sleep(1) # 填入金额,点击转账 edit = find_chat_input(d) if edit is not None: edit.set_text("0.01") d(text="转账").click() time.sleep(1) # 先判断异常:关系提示文案 if d(textContains="好友关系是否正常").exists(timeout=2): d(text="确定").click() if d(text="确定").exists(timeout=1) else None return "invalid" if d(textContains="开启了朋友验证").exists(timeout=2): d(text="确认").click() if d(text="确认").exists(timeout=1) else None return "invalid" # 再判断正常:走到支付密码/指纹,立即返回 if d(textContains="输入支付密码") or d(textContains="确认支付"): d.press("back") return "normal" return "unknown"代码说明:这里有三处“转账”文本,分别出现在“加号菜单”“金额页按键”“确认弹窗按键”,不同微信版本可能多一步确认弹窗。核心判断逻辑是:先看有没有“好友关系”相关提示,有就是异常;再看有没有支付验证,有就是正常。参数说明:转账金额固定0.01,是微信允许的最小金额,验证页取消后不会产生账单。如果弹出的是“对方未开通微信支付”这类提示,把它归入“invalid”并记录备注,因为无论对方是删除你还是没绑卡,都不适合继续用转账法测。整套逻辑里最怕的是顺序反了——先看到“确认支付”就当成异常,那所有正常好友都会被误判成拉黑,这正是下一章要讲的典型误判。
4.3 全量遍历:白名单、限频与断点续跑
单次检测逻辑跑通后,剩下的就是循环。但批量遍历必须考虑三件事:白名单、频率控制、断点续跑。白名单避免误测重要联系人和家人;频率控制决定账号安全;断点续跑保证跑到一半中断不用从头再来:
import random, json def batch_scan(d, friend_names, whitelist, cache_path="scan_cache.json"): results = {} if os.path.exists(cache_path): results = json.load(open(cache_path, encoding="utf-8")) for idx, name in enumerate(friend_names): if name in whitelist or name in results: continue # 搜索并进入聊天页 d(text="搜索").click() time.sleep(0.8) search_input = find_chat_input(d) if search_input is None: continue search_input.set_text(name) time.sleep(1.2) match = d(text=name) if match.exists(timeout=2): match.click() time.sleep(0.8) # 执行探测:先消息法,再对结果做转账复核 status = check_single_deleted(d) if status == "blocked": status = check_blocked_by_transfer(d) results[name] = status json.dump(results, open(cache_path, "w", encoding="utf-8"), ensure_ascii=False) # 返回列表页,准备下一个 d.press("back") time.sleep(random.uniform(3, 7)) # 每30人暂停2分钟,降低风控概率 if (idx + 1) % 30 == 0: time.sleep(120) print(f"{name}: {status}")代码说明:每次循环先查白名单和本地缓存,命中就跳过,实现断点续跑。搜索好友、点进聊天页、执行检测、返回,每一步之间留足页面加载时间。拉黑消息会用转账复核,避免消息法盲区。参数说明:random.uniform(3, 7)是单次间隔的随机秒数,比固定 sleep 更像真人;每 30 人停 2 分钟是常见经验值,账号权重低时建议改成每 20 人停 3 分钟。cache_path是 JSON 缓存文件,脚本中断后重新运行会自动跳过已检测的人。这里要说明一个容易被忽略的点:从通讯录进入聊天页而不是用搜索,能减少一次搜索对数据库的查询压力,但对界面跳转的稳定性要求更高,两种方式都能跑,搜索法代码更直观,通讯录法更适合大名单。
5. 常见问题:封号风险、元素定位与误判排查
批量跑起来之后,问题往往不是逻辑本身,而是环境、频率和文案变化带来的连锁反应。我按“账号安全”和“判定准确”两条线,把最常见的五个问题列出来,每条都按现象、原因、解决三个层次讲。
5.1 封号风险与频率失控的两个典型
现象一:检测跑到第 200 人,屏幕上突然弹出滑块验证,再往后操作都提示“操作过于频繁”。
原因很直接:操作节奏太固定,或者单日检测量超出账号日常行为模型。微信对“高频、长时间、无变化地操作同一页面”这件事非常敏感,脚本的点击间隔如果一直是精确的 3 秒,反而比 2 秒更可疑。解决方法是让节奏更随机:单次间隔用random.uniform(3, 7),每 30 人休息 5 分钟而不是 2 分钟,单日上限老号控制在 500 人以内、新号控制在 200 人以内。一旦看到滑块验证,立刻停掉脚本,手动拖完滑块,当天不要再跑。这个阈值不是死数字,但大原则是在“让别人看不出你在批量操作”和“能在合理时间内跑完”之间取平衡。
现象二:换了一台新手机登录微信,跑了不到 50 人,系统提示登录环境异常,要求短信验证。
原因:新设备本身权重低,加上短时间高频操作,双重因素叠加更容易触发安全机制。同一账号在常用设备上连续跑一个月都没事,换新设备后立刻出问题,这个场景太典型了。解决方法是:优先用常用手机;如果非要用新设备,先手动登录并正常使用两三天,再跑检测;不要用任何第三方分身或多开工具跑检测脚本,那些环境的设备指纹异常会让风控阈值降得更低。
5.2 元素定位与业务误判的三个典型
现象三:d(text="发送").click()执行了,但消息并没有发出去;或者set_text("你好")之后输入框里是空的。
原因:当前页面根本不在聊天界面,或者键盘状态不对。“发送”按钮在输入法键盘上方,键盘没弹起来时这个文本可能不显示,就会匹配到别的控件。更隐蔽的是微信自动弹出的悬浮窗或广告卡片,它们会改变页面结构,导致控件遍历范围异常。解决方法是:点击输入框后先强制弹一次键盘,time.sleep(1)再set_text;执行完动作后打印一次d.dump_hierarchy(),把当前界面源码存下来,看匹配到的到底是哪个控件。不要相信网上的资源 ID,resourceId随版本变的次数太多,文本定位和结构定位才是稳定方案。
现象四:拉黑检测把所有正常好友全部标记成“异常”。
原因:判断顺序写反了。转账点完后,正常好友会进入支付密码或指纹验证页,如果代码里先匹配“确认支付”或“输入密码”并把它当成“异常”标志,那所有正常好友全都会被记错。正确的顺序必须是:先匹配“好友关系是否正常”“开启了朋友验证”这类关系异常文案,再匹配支付验证文案,支付验证文案出来了,反而是“正常”的信号,要立刻返回取消。这个逻辑一旦写反,一次全量跑完就是全军覆没。做判定的代码里可以加一行资源计数,把匹配到的界面文本一起写进日志,方便事后复核。
现象五:检测完,不少正常好友发来问号,问“你发了什么又撤回了”。
原因:自动撤回机制惹的祸。很多脚本为了降低打扰,发完探测消息后立刻撤回,但系统会显示“你撤回了一条消息”,这条提醒反而吸引了对方注意。单删和拉黑的好友本身收不到探测消息,不需要撤回;正常好友收到的是“你好”,完全在可接受范围内。所以我的做法是:不做撤回逻辑,消息内容就用正常的问候语;如果实在不想让任何正常好友看到消息,那就改用转账法做单删检测,正常好友只会走到支付验证页,无感知。撤回不是降低打扰,是在制造新的打扰。
6. 检测结果怎么用:导出名单与二次复核
检测跑完,结果不能只躺在缓存里。把它导出成表格,按状态分组,再针对关键好友做二次复核,整个工具才算闭环。
import csv, time, os rows = [ {"nickname": name, "status": status, "checked_at": time.strftime("%Y-%m-%d %H:%M:%S")} for name, status in results.items() ] with open("check_report.csv", "w", newline="", encoding="utf-8-sig") as f: writer = csv.DictWriter(f, fieldnames=["nickname", "status", "checked_at"]) writer.writeheader() writer.writerows(rows)代码说明:encoding="utf-8-sig"是为了让 Excel 直接打开不乱码,这是导出中文 CSV 时最容易忽略的细节。DictWriter按字段名写表头和数据,保持结果可读。导出后用 Excel 或 WPS 筛选status列,就能看到“deleted / blocked / normal”三个分组。对状态为blocked和deleted的好友,建议第二天用转账法复核一次,排除前一天因为网络或者弹窗导致的误判;对名单里的重要联系人,复核更要放在白天正常工作时间,确认无误后再决定是删除还是仅移出列表。
验证脚本正确性的最好方法,是准备两个小号,一个删除你、一个拉黑你,跑一遍完整流程,看脚本能不能准确区分“deleted”和“blocked”。这个小样本测试花不了十分钟,但能挡住后面几百人批量检测里的低级错误。我自己的习惯是,所有检测脚本第一行就加载白名单,没有白名单宁可报错退出也不跑;检测结果只用来整理自己的好友关系,不是为了向对方发送任何营销内容,检测出来的异常好友,该删就删,别再去二次试探。希望帮到你。
本文还有配套的精品资源,点击获取