基于UI自动化与Playwright的微信好友状态检测技术实战

📅 2026/7/29 10:43:55 👁️ 阅读次数 📝 编程学习
基于UI自动化与Playwright的微信好友状态检测技术实战

1. 项目概述与核心价值

最近在技术社区和朋友圈里,时不时会看到有人讨论“如何悄无声息地检测微信里谁删了你”。这背后反映的,其实是一个普遍存在但又略显尴尬的社交需求:微信好友列表越拉越长,但其中有多少人已经单方面将你删除,你却毫不知情。手动检测?一个个发消息或者拉群太麻烦,还容易打扰到别人。于是,各种“一键检测”工具应运而生,它们号称能快速、无感地帮你找出那些“僵尸好友”。

作为一个在自动化工具和客户端逆向领域摸爬滚打多年的开发者,我决定深入探究一下这类工具的实现原理,并亲手打造一个。这不仅仅是为了解决一个具体问题,更是一次绝佳的实战机会,能让我们深入理解现代即时通讯软件的协议、客户端安全机制以及自动化测试技术。今天,我就把这次从零到一的完整实战过程,包括技术选型、核心原理、代码实现、避坑经验,毫无保留地分享出来。无论你是想自己动手实现类似功能,还是单纯对背后的技术感兴趣,这篇文章都能给你带来实实在在的收获。

2. 技术方案选型与核心思路拆解

在动手之前,我们必须明确一个核心原则:任何操作都必须在符合相关服务条款和法律法规的前提下进行。我们的目标是技术研究与学习,探讨在合法合规框架下实现自动化检测的可能性与边界。

2.1 主流实现路径分析

市面上和社区里流传的检测方法五花八门,但归根结底,技术路径主要可以归纳为以下三类:

  1. 协议模拟与逆向分析:这是最“硬核”的方法。通过抓包、逆向工程分析微信客户端与服务器之间的通信协议,然后模拟客户端行为发送特定的探测消息。这种方法技术门槛最高,需要对网络协议、加密解密、客户端结构有很深的理解,且随着客户端更新,协议可能变动,维护成本巨大。更重要的是,深度逆向和模拟协议可能触及法律红线,是我们必须坚决规避的。

  2. 官方接口的创造性使用:微信为开发者提供了丰富的开放接口,如微信公众平台、小程序、企业微信等。我们可以思考,能否利用这些合法接口间接实现检测?例如,通过服务号模板消息、小程序客服消息等渠道,向用户发送一条特殊的、对用户无打扰的提示信息,通过发送成功与否的状态来判断。这种方法的优点是完全合规,缺点是依赖用户授权(如关注服务号、使用小程序),且接口有调用频率限制,无法做到完全“无感”和批量快速。

  3. UI自动化测试技术:这是目前个人开发者实现此类工具最可行、相对安全的技术路径。其核心思路是:不触碰底层协议,而是在手机或电脑上自动操作微信客户端界面,模拟人的点击、输入等行为,通过观察界面反馈(如是否出现红色感叹号、转账时的提示信息)来判断好友状态。这种方法本质上是在操作一个“黑盒”,不涉及协议破解,风险较低。

综合考量技术可行性、合规风险和学习价值,我们将选择UI自动化测试作为本次实战的核心技术方案。具体来说,就是在电脑上通过自动化脚本控制微信PC版进行操作。

2.2 工具链选型与理由

确定了UI自动化的方向后,我们需要选择合适的工具。

  • 自动化框架PlaywrightSelenium。两者都是优秀的浏览器自动化工具。考虑到微信PC版本质上是一个基于CEF(Chromium Embedded Framework)的桌面应用,其界面与Web页面高度相似,这些工具同样能对其中的Web视图进行控制。Playwright相对较新,在API设计、执行速度和稳定性上更有优势,我们选择Playwright
  • 编程语言Python。在自动化、脚本编写和快速原型开发方面,Python拥有无与伦比的生态优势和简洁语法,社区资源丰富,适合本项目。
  • 控制对象微信PC版窗口。我们将自动化操作的对象锁定为微信PC版的窗口界面,而非手机。这样做的原因有三:一是PC屏幕大,元素定位更稳定;二是可以在同一台机器上完成开发和测试;三是避免了手机设备管理和投屏的复杂度。

重要提示:本实战指南的所有操作,均基于技术学习与研究的目的是展开。在实际自动化操作任何软件时,请务必遵守其用户协议,控制操作频率,避免对服务器造成不必要的压力,严禁用于任何恶意或骚扰用途。

3. 核心原理与实操环境搭建

3.1 检测原理深度剖析

我们的自动化脚本将模拟以下人工操作流程,并解析其反馈:

  1. 发起转账:这是目前公认干扰最小、最可靠的检测方法之一。向目标好友发起转账(不输入密码,不真正转出)。
  2. 解析反馈:在输入转账金额的界面,微信会进行实时校验。如果对方仍是你的好友,你会正常进入输入密码界面(或提示你输入密码)。如果对方已将你删除,在点击转账按钮后,微信通常会直接弹出一个提示框,内容类似于“你不是收款方的好友……”
  3. 逻辑判断:自动化脚本的核心就是捕捉这个提示框的出现。如果出现,则判定为“被删除”;如果顺利进入下一步,则判定为“正常好友”。

这个方法之所以有效,是因为“转账”功能涉及真实的资金往来,微信必须在此刻进行严格的好友关系校验,这个校验结果是实时且准确的。

3.2 开发环境准备

接下来,我们一步步搭建开发环境。

步骤一:安装Python与Playwright

确保你的电脑上安装了Python(3.7及以上版本)。然后通过pip安装Playwright。

# 安装playwright库 pip install playwright # 安装Playwright所需的浏览器驱动(主要是Chromium,微信PC版基于此) playwright install chromium

步骤二:启动微信PC版并登录

手动启动微信PC版,并扫码登录你的账号。确保微信窗口处于打开状态。

步骤三:定位微信窗口

这是关键一步。我们需要获取微信PC版窗口的调试端口,以便Playwright能够连接并控制它。微信PC版默认不开放调试端口,我们需要通过启动参数来开启。

  1. 完全退出微信。
  2. 找到微信的快捷方式或可执行文件,在其“属性”的“目标”一栏中,末尾添加以下启动参数(注意前面有个空格):
    --remote-debugging-port=9222
    • 例如,原本是"C:\Program Files\Tencent\WeChat\WeChat.exe",修改后为:"C:\Program Files\Tencent\WeChat\WeChat.exe" --remote-debugging-port=9222
  3. 使用修改后的快捷方式启动微信。此时,微信会开放一个本地调试服务,地址通常是http://127.0.0.1:9222

我们可以写一个简单的Python脚本来测试连接:

import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 连接到已经打开的、开启了调试端口的浏览器/应用 browser = await p.chromium.connect_over_cds('http://127.0.0.1:9222') # 获取第一个页面(通常是微信的主界面) pages = browser.contexts[0].pages if browser.contexts else [] if pages: page = pages[0] print(f"成功连接到微信页面,标题:{await page.title()}") # 这里可以尝试进行一些简单操作,例如点击通讯录 # await page.click('这里需要替换为通讯录按钮的选择器') else: print("未找到页面,请检查微信是否已启动并登录。") await browser.close() asyncio.run(main())

运行这个脚本,如果能看到成功连接的提示,说明环境搭建成功。

4. 自动化脚本设计与核心环节实现

环境就绪后,我们开始设计核心的检测脚本。整个流程可以分为:获取好友列表、遍历好友执行转账检测、记录结果。

4.1 获取好友列表

在微信PC版中,好友列表通常在一个可滚动的<div>容器内,每个好友对应一个列表项。我们可以通过Playwright定位这些元素来获取好友的备注名或微信号。

async def get_friend_list(page): """获取当前通讯录列表中的所有好友名称""" friend_list = [] # 首先确保在通讯录页面。可以通过点击侧边栏的‘通讯录’图标实现。 # 这里需要你通过开发者工具(F12)手动定位‘通讯录’图标的选择器,例如: # await page.click('[aria-label="通讯录"]') # 定位好友列表容器 # 同样需要手动定位,例如: # list_container = page.locator('.friend-list-container') list_container = page.locator('你通过工具查看到的好友列表容器的CSS选择器') # 获取所有好友项 friend_items = list_container.locator('.friend-item') # 替换为实际的选择器 count = await friend_items.count() for i in range(count): item = friend_items.nth(i) # 提取好友显示名称,可能需要定位内部的某个span或div name_element = item.locator('.display-name') # 替换为实际的选择器 if await name_element.count() > 0: name = await name_element.text_content() friend_list.append(name.strip()) # 注意:微信列表是虚拟滚动,可能需要滚动才能加载更多元素 # 可以加入滚动逻辑:if i % 10 == 0: await page.mouse.wheel(0, 300) return friend_list

实操心得:微信PC版的DOM结构并不对外公开,且可能随版本更新而变化。因此,所有选择器(如.friend-list-container)都需要你使用浏览器开发者工具(在微信窗口按F12)手动审查元素来获取。这是UI自动化中最耗时但也最核心的一步。选择器要尽量选择具有唯一性、稳定性的属性,例如>async def check_single_friend(page, friend_name): """检测单个好友状态""" try: # 1. 点击搜索框,输入好友名 search_box = page.locator('#searchInput') # 替换为实际搜索框选择器 await search_box.click() await search_box.fill(friend_name) await page.wait_for_timeout(1000) # 等待搜索结果出现 # 2. 在搜索结果中点击该好友,打开聊天窗口 # 定位搜索结果列表中的对应项并点击 search_result_item = page.locator(f'.search-result-item:has-text("{friend_name}")').first if await search_result_item.count() == 0: print(f"未找到好友: {friend_name}") return friend_name, "NOT_FOUND" await search_result_item.click() await page.wait_for_timeout(1500) # 等待聊天窗口加载 # 3. 在聊天窗口中找到‘转账’按钮并点击 # 需要先点击聊天输入框旁的‘+’号,弹出菜单 more_btn = page.locator('.chat-toolbox-more') # 替换为实际选择器 await more_btn.click() await page.wait_for_timeout(500) # 在弹出菜单中点击‘转账’ transfer_menu_item = page.locator('text=转账').first # 使用文本定位 await transfer_menu_item.click() await page.wait_for_timeout(1000) # 等待转账界面弹出 # 4. 关键判断:是否存在“你不是收款方好友”的提示 # 定位提示框的文本或元素 error_dialog = page.locator('text=你不是收款方好友').or_(page.locator('text=对方不是你的朋友')) # 设置一个较短的超时,快速判断元素是否存在 try: await error_dialog.wait_for(state='visible', timeout=2000) # 如果找到了错误提示框,说明被删除 print(f"{friend_name}: 疑似已删除") # 关闭提示框和转账窗口 close_btn = page.locator('.dialog-close') # 替换为实际关闭按钮选择器 await close_btn.click() await page.wait_for_timeout(500) return friend_name, "DELETED" except Exception as e: # 没有等到错误提示框,说明可能是正常好友 print(f"{friend_name}: 好友关系正常") # 也需要关闭转账窗口(点击取消或右上角X) cancel_btn = page.locator('button:has-text("取消")').first await cancel_btn.click() await page.wait_for_timeout(500) return friend_name, "NORMAL" except Exception as e: print(f"检测好友 {friend_name} 时发生错误: {e}") return friend_name, "ERROR" finally: # 5. 清理:关闭聊天窗口,回到主界面,清空搜索框 close_chat_btn = page.locator('.chat-header-close') # 替换为实际选择器 await close_chat_btn.click() await page.wait_for_timeout(500) # 清空搜索框 search_box = page.locator('#searchInput') await search_box.click() await page.keyboard.press('Control+KeyA') await page.keyboard.press('Delete') await page.wait_for_timeout(300)

4.3 主流程整合与优化

将上述函数整合,并添加一些优化措施,如延迟控制、结果保存等。

import asyncio import csv from datetime import datetime from playwright.async_api import async_playwright async def main(): results = [] async with async_playwright() as p: browser = await p.chromium.connect_over_cds('http://127.0.0.1:9222') if not browser.contexts: print("未找到任何页面,请检查连接。") return page = browser.contexts[0].pages[0] print("正在获取好友列表...") # 这里调用之前定义的 get_friend_list 函数 friends = await get_friend_list(page) # 假设我们已经有了这个函数 # 为了测试,可以先使用一个小的好友列表 # friends = ["张三", "李四", "测试好友"] total = len(friends) print(f"共找到 {total} 个好友,开始检测...") for idx, friend in enumerate(friends, 1): print(f"[{idx}/{total}] 正在检测: {friend}") name, status = await check_single_friend(page, friend) results.append((name, status)) # 非常重要的延迟,避免操作过快被微信识别为异常行为 await page.wait_for_timeout(3000) # 每个好友检测间隔3秒 # 保存结果到CSV文件 filename = f"wechat_check_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" with open(filename, 'w', newline='', encoding='utf-8-sig') as f: writer = csv.writer(f) writer.writerow(['好友昵称', '状态']) writer.writerows(results) print(f"检测完成!结果已保存至: {filename}") print("统计:") deleted = [r for r in results if r[1] == 'DELETED'] print(f" 疑似被删除: {len(deleted)} 人") for d in deleted: print(f" - {d[0]}") await browser.close() if __name__ == '__main__': asyncio.run(main())

5. 常见问题、风险规避与排查技巧实录

在实际开发和运行过程中,你一定会遇到各种各样的问题。下面是我踩过坑后总结出来的经验。

5.1 元素定位失败(最常见问题)

  • 现象:脚本报错,提示找不到某个元素(TimeoutErrorElement not found)。
  • 原因与排查
    1. 选择器失效:微信更新导致DOM结构变化。这是最主要的原因。
      • 解决:重新使用开发者工具(F12)审查元素,更新脚本中的CSS选择器或文本定位。尽量使用包含唯一标识的稳定属性。
    2. 页面未加载完成:操作执行得太快,元素还没渲染出来。
      • 解决:在关键操作后增加await page.wait_for_timeout(毫秒数)或使用await page.wait_for_selector(‘选择器’)等待元素出现。
    3. 页面状态不对:例如,你以为在聊天窗口,但实际上焦点还在搜索框。
      • 解决:在脚本中增加更多的状态检查和日志输出。例如,在执行转账前,先判断当前页面标题或某个特定元素是否存在。
  • 技巧:编写一个通用的safe_clicksafe_fill函数,内部包含重试和等待逻辑,可以大大提高脚本的健壮性。
async def safe_click(page, selector, max_retries=3): for i in range(max_retries): try: await page.wait_for_selector(selector, state='visible', timeout=5000) await page.click(selector) return True except Exception as e: print(f"点击 {selector} 失败,第{i+1}次重试。错误: {e}") await page.wait_for_timeout(1000) print(f"点击 {selector} 彻底失败。") return False

5.2 操作频率过快导致异常

  • 现象:脚本运行一段时间后,微信界面可能卡住、无响应,或者弹出安全验证(如滑块验证)。
  • 原因:微信有完善的反自动化机制,高频、规律的操作极易被识别为非人类行为。
  • 规避策略
    1. 增加随机延迟:不要使用固定的间隔。可以在操作之间加入await page.wait_for_timeout(2000 + random.randint(-500, 1000))这样的随机等待。
    2. 模拟人类操作:在点击、输入等操作中,加入微小的移动轨迹变化,可以使用page.mouse.move(x, y)模拟更自然的鼠标移动。
    3. 控制检测规模:一次不要检测太多好友,可以分批次、分天进行。我们的示例脚本中设置了3秒的固定间隔,这已经比较保守,但你仍然可以根据情况调整。
    4. 准备“熔断”机制:脚本中应监控是否出现验证码页面。一旦发现,立即暂停脚本,并通知用户进行手动干预。

5.3 法律与合规风险重申

这是最重要的一部分,必须单独强调。

  1. 用户协议:微信用户协议明确禁止任何形式的自动化登录、爬取、干扰服务正常运行的行为。本实战项目仅供个人学习和技术研究,严禁用于任何商业用途、批量骚扰或对微信服务造成压力
  2. 隐私边界:你检测的只能是你自己账号的好友列表。任何尝试获取非本人好友信息的行为都是非法的。
  3. 账号风险:频繁的自动化操作可能导致你的微信账号被限制功能(如无法拉群、无法转账),甚至被封号。请使用不重要的“小号”进行测试和学习。
  4. 技术伦理:我们学习技术是为了创造价值,解决问题,而不是制造问题。请将此类技术应用于正当的自动化测试、辅助工具开发等场景。

5.4 脚本的优化与扩展方向

如果你已经成功实现了基础功能,可以考虑以下方向进行优化:

  • 多线程/异步优化:虽然我们用了异步asyncio,但UI操作本质是顺序的。真正的并行检测需要操作多个微信客户端实例,复杂度激增,且风险更大,不推荐。
  • 状态持久化与断点续查:将已检测的好友状态和进度保存到文件或数据库。如果脚本中途中断,下次可以从断点处继续,避免重复检测。
  • 更友好的结果展示:将CSV结果生成可视化图表,或集成到简单的本地Web页面中查看。
  • 异常状态细化:除了“删除”,还有“被拉黑”、“对方账号异常”等状态,可以通过解析不同的提示文本来进行更精细的判断。

整个项目从环境搭建到脚本调试,最耗时的部分无疑是元素定位和应对微信客户端的更新变化。这要求开发者有足够的耐心和前端调试能力。通过这个实战,你学到的绝不仅仅是如何检测微信好友,更是如何系统性地分析一个桌面应用、设计自动化方案以及处理各种边界情况的能力。技术永远在更新,但解决问题的思路和方法论是相通的。