1. 项目概述当微信好友列表变成“单向好友”的修罗场不知道你有没有过这样的经历某天心血来潮想给一个很久没联系的朋友发条消息结果屏幕上弹出一个冷冰冰的红色感叹号提示“消息已发出但被对方拒收了”。那一刻心里多少会有点不是滋味。更让人头疼的是微信不像QQ没有“单向好友”的明确标识你根本不知道通讯录里哪些人已经默默把你删了。这个项目就是源于这种略带尴尬和清理强迫症的现实需求——用Python写一个脚本自动找出那些已经删除我的微信好友并一键将他们从我的列表中清除。听起来有点像“魔法打败魔法”其实原理并不复杂。核心思路就是利用微信网页版或桌面版的接口模拟人工操作向好友列表中的所有人发起一个特定的、不会打扰到对方的检测请求比如转账0.01元或在某些条件下发送一条特定消息然后根据服务器的反馈来判断好友关系状态。如果对方已删除你这个操作会失败并返回特定错误码如果还是好友则会正常阻止你进行下一步比如提示你输入支付密码。脚本会记录下所有失败的好友最后批量执行删除操作。这个项目非常适合有一定Python基础并对自动化、爬虫或微信生态感兴趣的朋友练手。它不仅能帮你清理通讯录更重要的是你能在这个过程中学到如何分析网络请求、处理登录态Cookie/Token、模拟用户行为以及处理各种边界情况。当然我必须强调所有操作都应基于个人账号用于学习目的并严格遵守相关平台的使用条款避免对他人造成骚扰或触发账号风控。2. 核心思路与技术选型解析2.1 为什么不能直接调用官方API首先我们要明确一点微信没有对外提供用于检测“单向好友”的官方API。所有市面上类似的功能无论是第三方工具还是自己写的脚本本质上都是在模拟用户操作。因此我们的技术路线就锁定在了“自动化测试”或“网页爬虫”的范畴。我们需要一个能控制浏览器或者能发送HTTP请求的工具来扮演“我们”这个用户。2.2 核心方案对比Selenium vs. Playwright vs. 直接请求目前主流的方案有三种各有优劣基于Selenium的浏览器自动化原理通过WebDriver驱动真实的浏览器如Chrome、Firefox完全模拟人的点击、输入等操作。优点行为最接近真人不易被简单的反爬机制识别调试直观可以实时看到浏览器界面。缺点速度慢资源占用高要开浏览器稳定性依赖浏览器和驱动版本代码相对冗长。适用场景适合初学者理解流程或者对付那些反爬极其严格、操作链路复杂的场景。基于Playwright的浏览器自动化原理可以看作是Selenium的“现代升级版”。它支持Chromium、Firefox、WebKit三大内核API更现代优雅性能更好。优点速度比Selenium快自动等待机制更智能减少“翻车”概率录制生成代码的功能强大。缺点较新社区资源相对Selenium少一点本质上还是操作浏览器重量级。适用场景追求更稳定、更快速浏览器自动化方案的选择。基于直接HTTP请求抓包分析原理使用抓包工具如Fiddler、Charles、浏览器开发者工具分析微信网页版或桌面版客户端与服务器通信的接口。然后用Python的requests库直接构造这些HTTP请求模拟登录、获取好友列表、发起检测、执行删除等操作。优点速度极快效率最高资源消耗极小代码执行稳定。缺点技术门槛较高需要逆向分析接口接口一旦变更脚本容易失效需要处理复杂的登录态Cookie、Token维护。适用场景追求极致效率和稳定性且有一定网络协议分析能力的开发者。注意微信的接口和风控策略经常变化直接请求的方式失效风险最高。浏览器自动化方案因为模拟的是真实用户行为相对更“耐用”。2.3 本项目技术栈选择考虑到项目的学习目的和普适性我将选择“Selenium Chrome”作为主要方案进行详细拆解。因为它最直观能让每一步操作都看得见对于理解整个流程非常有帮助。等掌握了这个你完全可以自己尝试迁移到Playwright或探索直接请求的方式。所需的核心Python库selenium浏览器自动化核心库。webdriver-manager自动管理ChromeDriver的版本避免手动下载和路径配置的麻烦强烈推荐。time/datetime用于操作中的等待和日志记录。可选pandas用于将检测结果导出为Excel或CSV文件。可选logging用于记录更规范的运行日志。3. 环境准备与核心工具详解3.1 Python环境搭建如果你还没有Python环境建议直接安装Anaconda它集成了Python和很多常用的科学计算库环境管理也方便。也可以去Python官网下载安装包。安装后在命令行输入python --version确认安装成功。我个人的开发环境是Python 3.8版本不要太老即可。3.2 安装依赖库打开你的命令行CMD、Terminal或PowerShell创建一个新的项目目录然后安装我们需要的库# 创建项目目录并进入 mkdir wechat_friend_checker cd wechat_friend_checker # 安装selenium和webdriver-manager pip install selenium webdriver-manager # 可选安装pandas用于数据处理 pip install pandaswebdriver-manager是这个项目的“神器”。以前用Selenium你需要根据Chrome浏览器的版本去官网下载对应版本的ChromeDriver并配置系统路径非常麻烦。现在你只需要在代码中写上from webdriver_manager.chrome import ChromeDriverManager它就会自动检测你的Chrome版本并下载匹配的驱动省心省力。3.3 微信登录态保持的难点与对策这是整个项目第一个也是最大的一个坑。微信网页版wx.qq.com为了安全登录状态非常“脆弱”。扫码登录每次都需要用手机微信扫码无法用账号密码。掉线频繁页面长时间无操作或频繁进行非常规操作比如我们的批量检测很容易被强制下线。无法多开一个微信账号在网页版只能有一个登录会话。我们的对策一次执行尽量完成规划好脚本争取在单次登录的有效期内完成所有好友的检测和清理。这要求我们的脚本要足够健壮网络等待要合理。使用“保留登录态”的客户端相比网页版微信桌面客户端Windows/Mac版的登录态更持久。我们可以通过自动化操作桌面客户端来实现。但桌面客户端的UI自动化比网页更复杂元素定位困难。一个折中的思路是在网页版扫码登录后通过某些方法如使用浏览器插件导出Cookie尝试延长会话但这涉及更复杂的技术且可能违反使用条款风险较高。因此本项目仍以网页版为基础强调单次会话内的操作效率。人工介入准备最稳妥的办法是在运行脚本前手动在浏览器中登录微信网页版并确保登录状态有效。然后让Selenium接管这个已经登录的浏览器实例。这需要用到Selenium的“用户数据目录”功能我们后面会详细讲。4. 实操步骤从登录到批量删除下面我将分步拆解整个脚本的编写过程。请跟随步骤在自己的环境中尝试。4.1 第一步启动并接管已登录的Chrome浏览器为了避免每次运行脚本都要扫码我们可以让Selenium使用一个指定的Chrome用户数据目录。这个目录里保存了你的浏览历史、Cookie、缓存等。如果你之前在这个目录对应的Chrome里登录过微信Selenium就能直接接管一个已经登录的状态。操作流程找到你的Chrome用户数据目录。Windows通常在C:\Users\你的用户名\AppData\Local\Google\Chrome\User Data。复制这个路径并指定一个具体的配置文件如Profile 1。我们最好复制一份出来专门给脚本用避免影响你日常使用的浏览器。在代码中通过ChromeOptions来指定这个目录。from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time # 配置Chrome选项 chrome_options webdriver.ChromeOptions() # 指定用户数据目录这里‘./chrome_profile’是你项目目录下新建的文件夹 chrome_options.add_argument(r--user-data-dir./chrome_profile) # 指定配置文件目录默认是‘Default’你可以新建一个如‘Profile 1’ chrome_options.add_argument(r--profile-directoryDefault) # 可选隐藏“Chrome正受到自动测试软件控制”的提示但有些网站能检测到 # chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) # chrome_options.add_experimental_option(useAutomationExtension, False) # 使用webdriver-manager自动管理驱动 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionschrome_options) # 首次运行需要手动打开微信网页版并扫码登录 print(请确保浏览器已打开并手动访问 https://wx.qq.com 完成扫码登录。) print(登录成功后回到此命令行窗口按回车键继续...) input() # 尝试访问微信网页版此时应该已经是登录状态 driver.get(https://wx.qq.com) time.sleep(5) # 等待页面加载 # 检查是否成功进入微信主界面可以通过查找特定元素来判断比如消息输入框 try: WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, div.input)) ) print(登录状态确认成功) except: print(未检测到登录状态请检查。) driver.quit() exit()关键点第一次运行你需要手动完成扫码登录。之后只要不清理./chrome_profile目录理论上可以保持一段时间免登录。微信可能会定期要求重新扫码这是无法避免的。4.2 第二步获取所有好友列表登录成功后微信主界面左侧是聊天列表我们需要点击通讯录图标进入通讯录页面。# 假设我们已经成功登录driver是浏览器对象 # 1. 点击通讯录按钮 # 需要先找到通讯录按钮的元素。通过浏览器开发者工具F12查看元素。 # 通常它的类名或id会变这里用CSS选择器示例实际需要你根据当前页面调整 try: contact_button WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, a[title通讯录])) ) contact_button.click() print(已进入通讯录页面) time.sleep(3) # 等待通讯录加载 except Exception as e: print(f点击通讯录失败: {e}) driver.save_screenshot(error_contact.png) # 出错时截图 driver.quit()进入通讯录后好友列表通常在一个可以滚动的容器里。我们需要模拟滚动加载出所有好友。# 2. 定位好友列表的滚动容器 friend_list_container driver.find_element(By.CSS_SELECTOR, div.contact-list) # 这只是示例实际选择器需调整 # 3. 滚动加载所有好友 last_height driver.execute_script(return arguments[0].scrollHeight, friend_list_container) friend_set set() # 用集合存储好友昵称或唯一标识防重复 while True: # 滚动到底部 driver.execute_script(arguments[0].scrollTop arguments[0].scrollHeight, friend_list_container) time.sleep(2) # 等待新内容加载 # 获取当前滚动后的所有好友项 friend_items driver.find_elements(By.CSS_SELECTOR, div.contact-item) # 示例选择器 for item in friend_items: # 提取好友信息比如昵称。注意有些可能是群聊或公众号需要过滤。 name_element item.find_element(By.CSS_SELECTOR, span.nickname) # 示例 friend_name name_element.text.strip() if friend_name and friend_name not in [文件传输助手, 微信团队]: # 过滤非好友 friend_set.add(friend_name) # 计算新的滚动高度 new_height driver.execute_script(return arguments[0].scrollHeight, friend_list_container) if new_height last_height: break # 高度不再变化说明已滚动到底 last_height new_height print(f共获取到 {len(friend_set)} 个可能的好友/群聊。需要进一步筛选和检测。) # 注意这里获取的‘friend_set’可能包含群聊。纯好友列表通常在‘通讯录’页有单独标签需要更精确的定位。实操心得获取精确的好友列表是难点。微信网页版结构经常变化CSS_SELECTOR需要你手动用开发者工具去查看和调整。不要指望一段代码永远有效。重点学会如何定位元素的方法By.ID, By.CLASS_NAME, By.XPATH等。4.3 第三步设计“单向好友”检测策略我们不能真的给每个好友发消息或转账来测试。这里介绍两种相对“温和”的检测思路策略A通过“转账”功能检测推荐更可靠原理向好友发起转账金额设为0.01元如果对方已删除你在输入密码前就会弹出“你不是收款方好友”的提示。如果还是好友则会进入输入密码页面。 步骤在通讯录列表右键或点击好友项查看是否有“转账”菜单项。网页版可能没有直接入口。更可行的是点击好友进入聊天窗口。在聊天窗口的输入框上方工具栏通常有“转账”图标。点击“转账”输入金额0.01点击“转账”按钮。检测弹出的页面或提示框。关键判断如果出现“请确认你和他的好友关系是否正常”或类似提示则为被删除。如果弹出输入密码框则为正常好友。无论结果如何关闭转账窗口。策略B通过“发送名片”功能检测次选原理在聊天窗口点击“”号或更多功能选择“发送名片”然后选择另一个好友比如“文件传输助手”。如果对方已删除你点击“发送”时可能会提示“发送失败”。但此方法可能不如转账检测准确。我们将采用策略A。关键在于如何精准地捕捉到那个提示信息。def check_friend_status(driver, friend_name): 检测指定好友的状态。 返回True (仍是好友) / False (已被删除) / None (检测失败) # 首先需要找到并点击这个好友进入聊天窗口。这里简化逻辑假设我们能通过好友名定位到元素并点击。 # 注意这是一个复杂且易变的操作实际代码需要大量异常处理和元素定位。 # 此处仅展示核心检测逻辑的伪代码思路。 try: # 1. 在通讯录列表中找到该好友项并点击进入聊天界面 # ... (省略复杂的查找和点击代码) ... # time.sleep(2) # 2. 在聊天窗口找到“转账”按钮并点击 # transfer_button WebDriverWait(driver, 5).until( # EC.element_to_be_clickable((By.CSS_SELECTOR, button.transfer-button)) # ) # transfer_button.click() # time.sleep(2) # 3. 在转账页面输入金额0.01 # amount_input driver.find_element(By.CSS_SELECTOR, input.amount-input) # amount_input.send_keys(0.01) # time.sleep(1) # 4. 点击确认转账按钮 # confirm_button driver.find_element(By.CSS_SELECTOR, button.confirm-transfer) # confirm_button.click() # time.sleep(2) # 等待响应 # 5. 检测结果 # 方案一检测是否存在“好友关系”错误提示 # error_elements driver.find_elements(By.XPATH, //*[contains(text(), 好友关系)]) # if error_elements: # print(f检测到 {friend_name} 可能已删除你。) # # 关闭错误提示和转账窗口 # close_buttons driver.find_elements(By.CSS_SELECTOR, button.dialog-close) # for btn in close_buttons: # try: # btn.click() # except: # pass # time.sleep(1) # return False # 方案二检测是否进入了输入密码页面判断密码输入框是否存在 # password_input driver.find_elements(By.CSS_SELECTOR, input.password-input) # if password_input: # print(f{friend_name} 仍是好友。) # # 关闭转账窗口不输入密码 # driver.find_element(By.CSS_SELECTOR, button.cancel-transfer).click() # time.sleep(1) # return True # 6. 如果以上都没匹配返回None表示检测失败 # return None # 由于实际元素定位极其复杂且易变这里返回一个模拟结果用于演示 print(f[模拟检测] 对好友 {friend_name} 的检测逻辑已执行。) # 模拟一个随机结果 import random result random.choice([True, False, None]) status_map {True: 仍是好友, False: 已删除你, None: 检测失败} print(f模拟结果为: {status_map[result]}) return result except Exception as e: print(f检测好友 {friend_name} 时发生异常: {e}) driver.save_screenshot(ferror_check_{friend_name}.png) return None finally: # 无论检测结果如何确保回到通讯录列表以便检测下一个好友 # 点击通讯录按钮返回 # driver.find_element(By.CSS_SELECTOR, a[title通讯录]).click() # time.sleep(2) pass4.4 第四步执行批量删除操作检测出被删除的好友后我们需要在通讯录中删除他们。删除操作一般需要进入好友详情页 - 找到“删除”按钮 - 点击确认。def delete_friend(driver, friend_name): 删除指定好友。 注意此操作不可逆 print(f准备删除好友: {friend_name}) # 再次强调以下为伪代码逻辑实际元素选择器需要你手动分析 try: # 1. 在通讯录列表中找到该好友项并点击进入其详情页不是聊天页 # friend_item ... 定位到好友项 # 可能需要右键菜单或点击更多按钮三个点 # more_button friend_item.find_element(By.CSS_SELECTOR, button.more) # more_button.click() # time.sleep(1) # 2. 在弹出菜单或详情页中找到“删除”选项并点击 # delete_option WebDriverWait(driver, 5).until( # EC.element_to_be_clickable((By.XPATH, //*[contains(text(), 删除)])) # ) # delete_option.click() # time.sleep(2) # 3. 在确认弹窗中点击“确定”或“删除”按钮 # confirm_button driver.find_element(By.CSS_SELECTOR, button.dialog-confirm) # confirm_button.click() # time.sleep(2) # 4. 等待删除完成可能有一个提示 print(f[模拟操作] 已执行删除 {friend_name} 的逻辑。) # 在实际脚本中这里可以记录日志 return True except Exception as e: print(f删除好友 {friend_name} 失败: {e}) driver.save_screenshot(ferror_delete_{friend_name}.png) return False4.5 第五步主流程整合与优化将以上步骤串联起来并加入必要的等待、异常处理和日志记录。def main(): driver None try: # 初始化驱动使用用户数据目录 # ... (初始化代码见4.1) ... driver webdriver.Chrome(serviceservice, optionschrome_options) driver.get(https://wx.qq.com) # ... (等待登录确认) ... # 进入通讯录 # ... (代码见4.2) ... # 获取好友列表这里用模拟列表代替 # real_friend_list list(friend_set) # 实际应从页面获取 simulated_friend_list [好友A, 好友B, 测试好友C, 可能已删除的D] # 模拟数据 deleted_friends [] failed_checks [] print(f开始检测 {len(simulated_friend_list)} 个好友...) for index, friend in enumerate(simulated_friend_list): print(f\n--- 正在处理第 {index1}/{len(simulated_friend_list)} 个: {friend} ---) status check_friend_status(driver, friend) if status is False: print(f确认 {friend} 已删除我加入待删除列表。) # 询问是否删除实际运行时可改为自动或配置 # 这里先模拟直接加入列表 deleted_friends.append(friend) elif status is None: print(f对 {friend} 的检测失败稍后重试或手动检查。) failed_checks.append(friend) # 如果是True则跳过 # 每次检测后稍作停顿避免操作过快 time.sleep(1) print(f\n 检测完成 ) print(f正常好友: {len(simulated_friend_list) - len(deleted_friends) - len(failed_checks)}) print(f待删除好友 ({len(deleted_friends)}) {deleted_friends}) print(f检测失败 ({len(failed_checks)}) {failed_checks}) # 执行删除谨慎 if deleted_friends: confirm input(f\n确定要删除以上 {len(deleted_friends)} 个好友吗(输入 yes 确认): ) if confirm.lower() yes: for friend in deleted_friends: success delete_friend(driver, friend) if success: print(f已删除: {friend}) time.sleep(1.5) # 删除操作间隔 else: print(已取消删除操作。) # 可以将结果保存到文件 import csv with open(friend_check_result.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([好友昵称, 状态]) for friend in simulated_friend_list: status 正常 if friend in deleted_friends: status 已删除你 elif friend in failed_checks: status 检测失败 writer.writerow([friend, status]) print(结果已保存到 friend_check_result.csv) except Exception as e: print(f主流程运行出错: {e}) if driver: driver.save_screenshot(main_error.png) finally: if driver: print(脚本执行完毕浏览器将在10秒后关闭...) time.sleep(10) driver.quit() if __name__ __main__: main()5. 避坑指南与高级技巧写这个脚本99%的时间都在“填坑”。下面是我在实践和与网友交流中总结出的关键问题和解决方案。5.1 元素定位失败永恒的难题微信网页版的DOM结构不固定类名经常变化。对策1多用相对定位和XPath。不要依赖绝对的class名可以尝试用XPath通过文本内容、层级关系来定位。例如//span[text()转账]。对策2结合多种定位方式。在find_element时使用EC.presence_of_element_located配合By.XPATH和By.CSS_SELECTOR轮流尝试。对策3增加智能等待。不要只用time.sleep多用WebDriverWait配合EC如element_to_be_clickable,visibility_of_element_located。设置合理的超时时间如10-20秒。对策4及时截图。每次定位失败时用driver.save_screenshot(error.png)截图方便你事后用开发者工具分析页面结构调整选择器。5.2 登录态失效与风控现象突然跳转到登录页或者操作无响应。对策降低操作频率在关键操作点击、转账之间加入随机延时time.sleep(random.uniform(1, 3))模拟真人操作。限制检测数量一次不要检测太多好友可以分批次进行比如每次脚本只处理50-100个。准备人工接管脚本设计成“断点续传”模式。将好友列表和检测状态保存到文件如JSON。如果中途掉线重新登录后可以从上次中断的地方继续。使用更稳定的客户端考虑研究微信桌面客户端的自动化如使用pyautogui进行图像识别点击或使用UIAutomation等库其会话更持久但自动化难度更大。5.3 区分好友、群聊和公众号通讯录列表里三者混在一起。我们的检测方法只对个人好友有效。对策在获取列表时通过元素特征进行过滤。通常群聊头像右下角有多个小人图标公众号有特定的标识。可以通过查找元素内部特定的子元素如特定的class或img的src来判断。如果无法精确过滤就在检测函数里增加判断如果点击后无法找到“转账”按钮则跳过标记为“非个人好友”。5.4 删除操作的确认弹窗不同版本的微信删除确认弹窗的提示文本和按钮位置可能不同。对策使用模糊匹配的XPath。例如确认按钮可以用//button[contains(text(), 确定) or contains(text(), 删除)]。同样删除后可能还有成功提示需要等待其消失后再进行下一步。5.5 代码的健壮性与可维护性异常处理每个可能失败的操作点击、查找、输入都要用try...except包裹记录错误并决定是重试、跳过还是终止。配置化将选择器、等待时间、检测批次大小等写成配置文件如config.py或config.json方便后期调整而不用改核心代码。日志系统使用Python的logging模块替代print可以输出不同级别INFO, WARNING, ERROR的日志到文件和控制台方便复盘。6. 伦理、风险与替代方案在兴奋地运行脚本之前我们必须冷静讨论一下这件事的边界。合规性风险大规模、自动化的操作违反微信用户协议的可能性极高。你的账号可能因“异常操作”被限制功能如禁止拉群、禁止打招呼甚至临时或永久封禁。切勿用于商业用途或骚扰他人。伦理考量虽然对方先删除了你但用自动化工具“报复性”删除本质上仍是一种自动化批量操作。请确保这只是用于管理个人通讯录的纯粹技术实践。隐私风险你的脚本会接触到好友列表信息。请确保代码和生成的数据文件如结果CSV妥善保管不要泄露。更温和的手动替代方案 如果你觉得上述操作风险太高这里有一个完全合规的手动检测“笨办法”虽然慢但绝对安全在微信中发起一个“群聊”但不要真的建群。在“选择联系人”界面你可以勾选所有你想检测的人。如果你勾选了某个已删除你的好友他的头像会显示为蓝色并且无法勾选或者勾选后提示“你无法邀请未添加你为好友的用户”。通过这个方式你可以快速目测筛选出“单向好友”。记住这些名字然后去通讯录手动删除即可。这个项目最大的价值不在于结果而在于过程。它强迫你去理解Web自动化、处理动态页面、应对反爬策略、编写健壮的代码。当你终于看到脚本自动滑动着好友列表一个个进行检测时那种成就感远大于清理掉几个“僵尸好友”。把它当作一次有趣的技术探险享受解决一个个棘手问题的乐趣吧。最后再次提醒运行需谨慎且用且珍惜。
Python自动化检测微信单向好友:Selenium实战与避坑指南
1. 项目概述当微信好友列表变成“单向好友”的修罗场不知道你有没有过这样的经历某天心血来潮想给一个很久没联系的朋友发条消息结果屏幕上弹出一个冷冰冰的红色感叹号提示“消息已发出但被对方拒收了”。那一刻心里多少会有点不是滋味。更让人头疼的是微信不像QQ没有“单向好友”的明确标识你根本不知道通讯录里哪些人已经默默把你删了。这个项目就是源于这种略带尴尬和清理强迫症的现实需求——用Python写一个脚本自动找出那些已经删除我的微信好友并一键将他们从我的列表中清除。听起来有点像“魔法打败魔法”其实原理并不复杂。核心思路就是利用微信网页版或桌面版的接口模拟人工操作向好友列表中的所有人发起一个特定的、不会打扰到对方的检测请求比如转账0.01元或在某些条件下发送一条特定消息然后根据服务器的反馈来判断好友关系状态。如果对方已删除你这个操作会失败并返回特定错误码如果还是好友则会正常阻止你进行下一步比如提示你输入支付密码。脚本会记录下所有失败的好友最后批量执行删除操作。这个项目非常适合有一定Python基础并对自动化、爬虫或微信生态感兴趣的朋友练手。它不仅能帮你清理通讯录更重要的是你能在这个过程中学到如何分析网络请求、处理登录态Cookie/Token、模拟用户行为以及处理各种边界情况。当然我必须强调所有操作都应基于个人账号用于学习目的并严格遵守相关平台的使用条款避免对他人造成骚扰或触发账号风控。2. 核心思路与技术选型解析2.1 为什么不能直接调用官方API首先我们要明确一点微信没有对外提供用于检测“单向好友”的官方API。所有市面上类似的功能无论是第三方工具还是自己写的脚本本质上都是在模拟用户操作。因此我们的技术路线就锁定在了“自动化测试”或“网页爬虫”的范畴。我们需要一个能控制浏览器或者能发送HTTP请求的工具来扮演“我们”这个用户。2.2 核心方案对比Selenium vs. Playwright vs. 直接请求目前主流的方案有三种各有优劣基于Selenium的浏览器自动化原理通过WebDriver驱动真实的浏览器如Chrome、Firefox完全模拟人的点击、输入等操作。优点行为最接近真人不易被简单的反爬机制识别调试直观可以实时看到浏览器界面。缺点速度慢资源占用高要开浏览器稳定性依赖浏览器和驱动版本代码相对冗长。适用场景适合初学者理解流程或者对付那些反爬极其严格、操作链路复杂的场景。基于Playwright的浏览器自动化原理可以看作是Selenium的“现代升级版”。它支持Chromium、Firefox、WebKit三大内核API更现代优雅性能更好。优点速度比Selenium快自动等待机制更智能减少“翻车”概率录制生成代码的功能强大。缺点较新社区资源相对Selenium少一点本质上还是操作浏览器重量级。适用场景追求更稳定、更快速浏览器自动化方案的选择。基于直接HTTP请求抓包分析原理使用抓包工具如Fiddler、Charles、浏览器开发者工具分析微信网页版或桌面版客户端与服务器通信的接口。然后用Python的requests库直接构造这些HTTP请求模拟登录、获取好友列表、发起检测、执行删除等操作。优点速度极快效率最高资源消耗极小代码执行稳定。缺点技术门槛较高需要逆向分析接口接口一旦变更脚本容易失效需要处理复杂的登录态Cookie、Token维护。适用场景追求极致效率和稳定性且有一定网络协议分析能力的开发者。注意微信的接口和风控策略经常变化直接请求的方式失效风险最高。浏览器自动化方案因为模拟的是真实用户行为相对更“耐用”。2.3 本项目技术栈选择考虑到项目的学习目的和普适性我将选择“Selenium Chrome”作为主要方案进行详细拆解。因为它最直观能让每一步操作都看得见对于理解整个流程非常有帮助。等掌握了这个你完全可以自己尝试迁移到Playwright或探索直接请求的方式。所需的核心Python库selenium浏览器自动化核心库。webdriver-manager自动管理ChromeDriver的版本避免手动下载和路径配置的麻烦强烈推荐。time/datetime用于操作中的等待和日志记录。可选pandas用于将检测结果导出为Excel或CSV文件。可选logging用于记录更规范的运行日志。3. 环境准备与核心工具详解3.1 Python环境搭建如果你还没有Python环境建议直接安装Anaconda它集成了Python和很多常用的科学计算库环境管理也方便。也可以去Python官网下载安装包。安装后在命令行输入python --version确认安装成功。我个人的开发环境是Python 3.8版本不要太老即可。3.2 安装依赖库打开你的命令行CMD、Terminal或PowerShell创建一个新的项目目录然后安装我们需要的库# 创建项目目录并进入 mkdir wechat_friend_checker cd wechat_friend_checker # 安装selenium和webdriver-manager pip install selenium webdriver-manager # 可选安装pandas用于数据处理 pip install pandaswebdriver-manager是这个项目的“神器”。以前用Selenium你需要根据Chrome浏览器的版本去官网下载对应版本的ChromeDriver并配置系统路径非常麻烦。现在你只需要在代码中写上from webdriver_manager.chrome import ChromeDriverManager它就会自动检测你的Chrome版本并下载匹配的驱动省心省力。3.3 微信登录态保持的难点与对策这是整个项目第一个也是最大的一个坑。微信网页版wx.qq.com为了安全登录状态非常“脆弱”。扫码登录每次都需要用手机微信扫码无法用账号密码。掉线频繁页面长时间无操作或频繁进行非常规操作比如我们的批量检测很容易被强制下线。无法多开一个微信账号在网页版只能有一个登录会话。我们的对策一次执行尽量完成规划好脚本争取在单次登录的有效期内完成所有好友的检测和清理。这要求我们的脚本要足够健壮网络等待要合理。使用“保留登录态”的客户端相比网页版微信桌面客户端Windows/Mac版的登录态更持久。我们可以通过自动化操作桌面客户端来实现。但桌面客户端的UI自动化比网页更复杂元素定位困难。一个折中的思路是在网页版扫码登录后通过某些方法如使用浏览器插件导出Cookie尝试延长会话但这涉及更复杂的技术且可能违反使用条款风险较高。因此本项目仍以网页版为基础强调单次会话内的操作效率。人工介入准备最稳妥的办法是在运行脚本前手动在浏览器中登录微信网页版并确保登录状态有效。然后让Selenium接管这个已经登录的浏览器实例。这需要用到Selenium的“用户数据目录”功能我们后面会详细讲。4. 实操步骤从登录到批量删除下面我将分步拆解整个脚本的编写过程。请跟随步骤在自己的环境中尝试。4.1 第一步启动并接管已登录的Chrome浏览器为了避免每次运行脚本都要扫码我们可以让Selenium使用一个指定的Chrome用户数据目录。这个目录里保存了你的浏览历史、Cookie、缓存等。如果你之前在这个目录对应的Chrome里登录过微信Selenium就能直接接管一个已经登录的状态。操作流程找到你的Chrome用户数据目录。Windows通常在C:\Users\你的用户名\AppData\Local\Google\Chrome\User Data。复制这个路径并指定一个具体的配置文件如Profile 1。我们最好复制一份出来专门给脚本用避免影响你日常使用的浏览器。在代码中通过ChromeOptions来指定这个目录。from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time # 配置Chrome选项 chrome_options webdriver.ChromeOptions() # 指定用户数据目录这里‘./chrome_profile’是你项目目录下新建的文件夹 chrome_options.add_argument(r--user-data-dir./chrome_profile) # 指定配置文件目录默认是‘Default’你可以新建一个如‘Profile 1’ chrome_options.add_argument(r--profile-directoryDefault) # 可选隐藏“Chrome正受到自动测试软件控制”的提示但有些网站能检测到 # chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) # chrome_options.add_experimental_option(useAutomationExtension, False) # 使用webdriver-manager自动管理驱动 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionschrome_options) # 首次运行需要手动打开微信网页版并扫码登录 print(请确保浏览器已打开并手动访问 https://wx.qq.com 完成扫码登录。) print(登录成功后回到此命令行窗口按回车键继续...) input() # 尝试访问微信网页版此时应该已经是登录状态 driver.get(https://wx.qq.com) time.sleep(5) # 等待页面加载 # 检查是否成功进入微信主界面可以通过查找特定元素来判断比如消息输入框 try: WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, div.input)) ) print(登录状态确认成功) except: print(未检测到登录状态请检查。) driver.quit() exit()关键点第一次运行你需要手动完成扫码登录。之后只要不清理./chrome_profile目录理论上可以保持一段时间免登录。微信可能会定期要求重新扫码这是无法避免的。4.2 第二步获取所有好友列表登录成功后微信主界面左侧是聊天列表我们需要点击通讯录图标进入通讯录页面。# 假设我们已经成功登录driver是浏览器对象 # 1. 点击通讯录按钮 # 需要先找到通讯录按钮的元素。通过浏览器开发者工具F12查看元素。 # 通常它的类名或id会变这里用CSS选择器示例实际需要你根据当前页面调整 try: contact_button WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, a[title通讯录])) ) contact_button.click() print(已进入通讯录页面) time.sleep(3) # 等待通讯录加载 except Exception as e: print(f点击通讯录失败: {e}) driver.save_screenshot(error_contact.png) # 出错时截图 driver.quit()进入通讯录后好友列表通常在一个可以滚动的容器里。我们需要模拟滚动加载出所有好友。# 2. 定位好友列表的滚动容器 friend_list_container driver.find_element(By.CSS_SELECTOR, div.contact-list) # 这只是示例实际选择器需调整 # 3. 滚动加载所有好友 last_height driver.execute_script(return arguments[0].scrollHeight, friend_list_container) friend_set set() # 用集合存储好友昵称或唯一标识防重复 while True: # 滚动到底部 driver.execute_script(arguments[0].scrollTop arguments[0].scrollHeight, friend_list_container) time.sleep(2) # 等待新内容加载 # 获取当前滚动后的所有好友项 friend_items driver.find_elements(By.CSS_SELECTOR, div.contact-item) # 示例选择器 for item in friend_items: # 提取好友信息比如昵称。注意有些可能是群聊或公众号需要过滤。 name_element item.find_element(By.CSS_SELECTOR, span.nickname) # 示例 friend_name name_element.text.strip() if friend_name and friend_name not in [文件传输助手, 微信团队]: # 过滤非好友 friend_set.add(friend_name) # 计算新的滚动高度 new_height driver.execute_script(return arguments[0].scrollHeight, friend_list_container) if new_height last_height: break # 高度不再变化说明已滚动到底 last_height new_height print(f共获取到 {len(friend_set)} 个可能的好友/群聊。需要进一步筛选和检测。) # 注意这里获取的‘friend_set’可能包含群聊。纯好友列表通常在‘通讯录’页有单独标签需要更精确的定位。实操心得获取精确的好友列表是难点。微信网页版结构经常变化CSS_SELECTOR需要你手动用开发者工具去查看和调整。不要指望一段代码永远有效。重点学会如何定位元素的方法By.ID, By.CLASS_NAME, By.XPATH等。4.3 第三步设计“单向好友”检测策略我们不能真的给每个好友发消息或转账来测试。这里介绍两种相对“温和”的检测思路策略A通过“转账”功能检测推荐更可靠原理向好友发起转账金额设为0.01元如果对方已删除你在输入密码前就会弹出“你不是收款方好友”的提示。如果还是好友则会进入输入密码页面。 步骤在通讯录列表右键或点击好友项查看是否有“转账”菜单项。网页版可能没有直接入口。更可行的是点击好友进入聊天窗口。在聊天窗口的输入框上方工具栏通常有“转账”图标。点击“转账”输入金额0.01点击“转账”按钮。检测弹出的页面或提示框。关键判断如果出现“请确认你和他的好友关系是否正常”或类似提示则为被删除。如果弹出输入密码框则为正常好友。无论结果如何关闭转账窗口。策略B通过“发送名片”功能检测次选原理在聊天窗口点击“”号或更多功能选择“发送名片”然后选择另一个好友比如“文件传输助手”。如果对方已删除你点击“发送”时可能会提示“发送失败”。但此方法可能不如转账检测准确。我们将采用策略A。关键在于如何精准地捕捉到那个提示信息。def check_friend_status(driver, friend_name): 检测指定好友的状态。 返回True (仍是好友) / False (已被删除) / None (检测失败) # 首先需要找到并点击这个好友进入聊天窗口。这里简化逻辑假设我们能通过好友名定位到元素并点击。 # 注意这是一个复杂且易变的操作实际代码需要大量异常处理和元素定位。 # 此处仅展示核心检测逻辑的伪代码思路。 try: # 1. 在通讯录列表中找到该好友项并点击进入聊天界面 # ... (省略复杂的查找和点击代码) ... # time.sleep(2) # 2. 在聊天窗口找到“转账”按钮并点击 # transfer_button WebDriverWait(driver, 5).until( # EC.element_to_be_clickable((By.CSS_SELECTOR, button.transfer-button)) # ) # transfer_button.click() # time.sleep(2) # 3. 在转账页面输入金额0.01 # amount_input driver.find_element(By.CSS_SELECTOR, input.amount-input) # amount_input.send_keys(0.01) # time.sleep(1) # 4. 点击确认转账按钮 # confirm_button driver.find_element(By.CSS_SELECTOR, button.confirm-transfer) # confirm_button.click() # time.sleep(2) # 等待响应 # 5. 检测结果 # 方案一检测是否存在“好友关系”错误提示 # error_elements driver.find_elements(By.XPATH, //*[contains(text(), 好友关系)]) # if error_elements: # print(f检测到 {friend_name} 可能已删除你。) # # 关闭错误提示和转账窗口 # close_buttons driver.find_elements(By.CSS_SELECTOR, button.dialog-close) # for btn in close_buttons: # try: # btn.click() # except: # pass # time.sleep(1) # return False # 方案二检测是否进入了输入密码页面判断密码输入框是否存在 # password_input driver.find_elements(By.CSS_SELECTOR, input.password-input) # if password_input: # print(f{friend_name} 仍是好友。) # # 关闭转账窗口不输入密码 # driver.find_element(By.CSS_SELECTOR, button.cancel-transfer).click() # time.sleep(1) # return True # 6. 如果以上都没匹配返回None表示检测失败 # return None # 由于实际元素定位极其复杂且易变这里返回一个模拟结果用于演示 print(f[模拟检测] 对好友 {friend_name} 的检测逻辑已执行。) # 模拟一个随机结果 import random result random.choice([True, False, None]) status_map {True: 仍是好友, False: 已删除你, None: 检测失败} print(f模拟结果为: {status_map[result]}) return result except Exception as e: print(f检测好友 {friend_name} 时发生异常: {e}) driver.save_screenshot(ferror_check_{friend_name}.png) return None finally: # 无论检测结果如何确保回到通讯录列表以便检测下一个好友 # 点击通讯录按钮返回 # driver.find_element(By.CSS_SELECTOR, a[title通讯录]).click() # time.sleep(2) pass4.4 第四步执行批量删除操作检测出被删除的好友后我们需要在通讯录中删除他们。删除操作一般需要进入好友详情页 - 找到“删除”按钮 - 点击确认。def delete_friend(driver, friend_name): 删除指定好友。 注意此操作不可逆 print(f准备删除好友: {friend_name}) # 再次强调以下为伪代码逻辑实际元素选择器需要你手动分析 try: # 1. 在通讯录列表中找到该好友项并点击进入其详情页不是聊天页 # friend_item ... 定位到好友项 # 可能需要右键菜单或点击更多按钮三个点 # more_button friend_item.find_element(By.CSS_SELECTOR, button.more) # more_button.click() # time.sleep(1) # 2. 在弹出菜单或详情页中找到“删除”选项并点击 # delete_option WebDriverWait(driver, 5).until( # EC.element_to_be_clickable((By.XPATH, //*[contains(text(), 删除)])) # ) # delete_option.click() # time.sleep(2) # 3. 在确认弹窗中点击“确定”或“删除”按钮 # confirm_button driver.find_element(By.CSS_SELECTOR, button.dialog-confirm) # confirm_button.click() # time.sleep(2) # 4. 等待删除完成可能有一个提示 print(f[模拟操作] 已执行删除 {friend_name} 的逻辑。) # 在实际脚本中这里可以记录日志 return True except Exception as e: print(f删除好友 {friend_name} 失败: {e}) driver.save_screenshot(ferror_delete_{friend_name}.png) return False4.5 第五步主流程整合与优化将以上步骤串联起来并加入必要的等待、异常处理和日志记录。def main(): driver None try: # 初始化驱动使用用户数据目录 # ... (初始化代码见4.1) ... driver webdriver.Chrome(serviceservice, optionschrome_options) driver.get(https://wx.qq.com) # ... (等待登录确认) ... # 进入通讯录 # ... (代码见4.2) ... # 获取好友列表这里用模拟列表代替 # real_friend_list list(friend_set) # 实际应从页面获取 simulated_friend_list [好友A, 好友B, 测试好友C, 可能已删除的D] # 模拟数据 deleted_friends [] failed_checks [] print(f开始检测 {len(simulated_friend_list)} 个好友...) for index, friend in enumerate(simulated_friend_list): print(f\n--- 正在处理第 {index1}/{len(simulated_friend_list)} 个: {friend} ---) status check_friend_status(driver, friend) if status is False: print(f确认 {friend} 已删除我加入待删除列表。) # 询问是否删除实际运行时可改为自动或配置 # 这里先模拟直接加入列表 deleted_friends.append(friend) elif status is None: print(f对 {friend} 的检测失败稍后重试或手动检查。) failed_checks.append(friend) # 如果是True则跳过 # 每次检测后稍作停顿避免操作过快 time.sleep(1) print(f\n 检测完成 ) print(f正常好友: {len(simulated_friend_list) - len(deleted_friends) - len(failed_checks)}) print(f待删除好友 ({len(deleted_friends)}) {deleted_friends}) print(f检测失败 ({len(failed_checks)}) {failed_checks}) # 执行删除谨慎 if deleted_friends: confirm input(f\n确定要删除以上 {len(deleted_friends)} 个好友吗(输入 yes 确认): ) if confirm.lower() yes: for friend in deleted_friends: success delete_friend(driver, friend) if success: print(f已删除: {friend}) time.sleep(1.5) # 删除操作间隔 else: print(已取消删除操作。) # 可以将结果保存到文件 import csv with open(friend_check_result.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([好友昵称, 状态]) for friend in simulated_friend_list: status 正常 if friend in deleted_friends: status 已删除你 elif friend in failed_checks: status 检测失败 writer.writerow([friend, status]) print(结果已保存到 friend_check_result.csv) except Exception as e: print(f主流程运行出错: {e}) if driver: driver.save_screenshot(main_error.png) finally: if driver: print(脚本执行完毕浏览器将在10秒后关闭...) time.sleep(10) driver.quit() if __name__ __main__: main()5. 避坑指南与高级技巧写这个脚本99%的时间都在“填坑”。下面是我在实践和与网友交流中总结出的关键问题和解决方案。5.1 元素定位失败永恒的难题微信网页版的DOM结构不固定类名经常变化。对策1多用相对定位和XPath。不要依赖绝对的class名可以尝试用XPath通过文本内容、层级关系来定位。例如//span[text()转账]。对策2结合多种定位方式。在find_element时使用EC.presence_of_element_located配合By.XPATH和By.CSS_SELECTOR轮流尝试。对策3增加智能等待。不要只用time.sleep多用WebDriverWait配合EC如element_to_be_clickable,visibility_of_element_located。设置合理的超时时间如10-20秒。对策4及时截图。每次定位失败时用driver.save_screenshot(error.png)截图方便你事后用开发者工具分析页面结构调整选择器。5.2 登录态失效与风控现象突然跳转到登录页或者操作无响应。对策降低操作频率在关键操作点击、转账之间加入随机延时time.sleep(random.uniform(1, 3))模拟真人操作。限制检测数量一次不要检测太多好友可以分批次进行比如每次脚本只处理50-100个。准备人工接管脚本设计成“断点续传”模式。将好友列表和检测状态保存到文件如JSON。如果中途掉线重新登录后可以从上次中断的地方继续。使用更稳定的客户端考虑研究微信桌面客户端的自动化如使用pyautogui进行图像识别点击或使用UIAutomation等库其会话更持久但自动化难度更大。5.3 区分好友、群聊和公众号通讯录列表里三者混在一起。我们的检测方法只对个人好友有效。对策在获取列表时通过元素特征进行过滤。通常群聊头像右下角有多个小人图标公众号有特定的标识。可以通过查找元素内部特定的子元素如特定的class或img的src来判断。如果无法精确过滤就在检测函数里增加判断如果点击后无法找到“转账”按钮则跳过标记为“非个人好友”。5.4 删除操作的确认弹窗不同版本的微信删除确认弹窗的提示文本和按钮位置可能不同。对策使用模糊匹配的XPath。例如确认按钮可以用//button[contains(text(), 确定) or contains(text(), 删除)]。同样删除后可能还有成功提示需要等待其消失后再进行下一步。5.5 代码的健壮性与可维护性异常处理每个可能失败的操作点击、查找、输入都要用try...except包裹记录错误并决定是重试、跳过还是终止。配置化将选择器、等待时间、检测批次大小等写成配置文件如config.py或config.json方便后期调整而不用改核心代码。日志系统使用Python的logging模块替代print可以输出不同级别INFO, WARNING, ERROR的日志到文件和控制台方便复盘。6. 伦理、风险与替代方案在兴奋地运行脚本之前我们必须冷静讨论一下这件事的边界。合规性风险大规模、自动化的操作违反微信用户协议的可能性极高。你的账号可能因“异常操作”被限制功能如禁止拉群、禁止打招呼甚至临时或永久封禁。切勿用于商业用途或骚扰他人。伦理考量虽然对方先删除了你但用自动化工具“报复性”删除本质上仍是一种自动化批量操作。请确保这只是用于管理个人通讯录的纯粹技术实践。隐私风险你的脚本会接触到好友列表信息。请确保代码和生成的数据文件如结果CSV妥善保管不要泄露。更温和的手动替代方案 如果你觉得上述操作风险太高这里有一个完全合规的手动检测“笨办法”虽然慢但绝对安全在微信中发起一个“群聊”但不要真的建群。在“选择联系人”界面你可以勾选所有你想检测的人。如果你勾选了某个已删除你的好友他的头像会显示为蓝色并且无法勾选或者勾选后提示“你无法邀请未添加你为好友的用户”。通过这个方式你可以快速目测筛选出“单向好友”。记住这些名字然后去通讯录手动删除即可。这个项目最大的价值不在于结果而在于过程。它强迫你去理解Web自动化、处理动态页面、应对反爬策略、编写健壮的代码。当你终于看到脚本自动滑动着好友列表一个个进行检测时那种成就感远大于清理掉几个“僵尸好友”。把它当作一次有趣的技术探险享受解决一个个棘手问题的乐趣吧。最后再次提醒运行需谨慎且用且珍惜。