Python找出微信上删除你好友的人脚本写法


Posted in Python onNovember 01, 2018

相信大家在微信上一定被上面的这段话刷过屏,群发消息应该算是微信上流传最广的找到删除好友的方法了。但群发消息不仅仅会把通讯录里面所有的好友骚扰一遍,而且你还得挨个删除好几百个聊天记录,回复大家的疑问和鄙视。作为一个互联网从业者,除了群发消息就不能有更高效、不打扰好友的方式么?

答案是当然有,本人的微:1613161916可以一起讨论python,还有许多Python资料可以发送。

微信在拉好友进群聊的时候,如果这个人删除了你好友的话,会提示你一下「请先发送朋友验证申请给某某,对方将你加为微信朋友后,你才能邀请其加入群聊。」有办法了,那我把微信好友拉一个大群里面,然后默默的删掉微信群不就好了么。

于是 Github 上就有一位叫 0x5e 的开发者写了这么一个 Python 脚本来代替你手动拉群和踢人。经过笔者测试,目前只能支持 Mac 的操作系统。在此之前,笔者在稀土掘金上先放出了Github的方法,这里再详细提下。

在 0x5e 的 Github 代码仓库描述里面,他具体的实现步骤是用 Python 脚本处理网页版微信的拉群、踢人操作。具体的步骤如下:

  • 下载代码文件 wdf.py
  • 打开 Terminal 输入:python 然后拖动刚才下载的 wdf.py 到 Terminal 窗后回车。命令格式类似: python wdf.py
  • 接下来按步骤扫码操作即可;

查询结果可能会引起一些心理上的不适,请小心使用。

笔者测试了两次,这个 Python 脚本还是有一些小问题,欢迎开发者们给原作者发 Pull Request 完善:

  • 两次结果稍有出入,可能是微信网页版返回数据的问题
  • 最终在微信会遗留一个只有自己的群组,需要手动删除
  • 此脚本暂时不支持查找被拉黑的情况

源码

#!/usr/bin/env python
# coding=utf-8
 
from __future__ import print_function
 
import os
import requests
import re
import time
import xml.dom.minidom
import json
import sys
import math
import subprocess
import ssl
import threading
 
DEBUG = False
 
MAX_GROUP_NUM = 2 # 每组人数
INTERFACE_CALLING_INTERVAL = 5 # 接口调用时间间隔, 间隔太短容易出现"操作太频繁", 会被限制操作半小时左右
MAX_PROGRESS_LEN = 50
 
QRImagePath = os.path.join(os.getcwd(), 'qrcode.jpg')
 
tip = 0
uuid = ''
 
base_uri = ''
redirect_uri = ''
push_uri = ''
 
skey = ''
wxsid = ''
wxuin = ''
pass_ticket = ''
deviceId = 'e000000000000000'
 
BaseRequest = {}
 
ContactList = []
My = []
SyncKey = []
 
try:
  xrange
  range = xrange
except:
  # python 3
  pass
 
 
def responseState(func, BaseResponse):
  ErrMsg = BaseResponse['ErrMsg']
  Ret = BaseResponse['Ret']
  if DEBUG or Ret != 0:
    print('func: %s, Ret: %d, ErrMsg: %s' % (func, Ret, ErrMsg))
 
  if Ret != 0:
    return False
 
  return True
 
 
 
def getUUID():
  global uuid
 
  url = 'https://login.weixin.qq.com/jslogin'
  params = {
    'appid': 'wx782c26e4c19acffb',
    'fun': 'new',
    'lang': 'zh_CN',
    '_': int(time.time()),
  }
 
  r= myRequests.get(url=url, params=params)
  r.encoding = 'utf-8'
  data = r.text
 
  # print(data)
 
  # window.QRLogin.code = 200; window.QRLogin.uuid = "oZwt_bFfRg==";
  regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"'
  pm = re.search(regx, data)
 
  code = pm.group(1)
  uuid = pm.group(2)
 
  if code == '200':
    return True
 
  return False
 
 
def showQRImage():
  global tip
 
  url = 'https://login.weixin.qq.com/qrcode/' + uuid
  params = {
    't': 'webwx',
    '_': int(time.time()),
  }
 
  r = myRequests.get(url=url, params=params)
 
  tip = 1
 
  f = open(QRImagePath, 'wb')
  f.write(r.content)
  f.close()
  time.sleep(1)
 
  if sys.platform.find('darwin') >= 0:
    subprocess.call(['open', QRImagePath])
  elif sys.platform.find('linux') >= 0:
    subprocess.call(['xdg-open', QRImagePath])
  else:
    os.startfile(QRImagePath)
 
  print('请使用微信扫描二维码以登录')
 
 
def waitForLogin():
  global tip, base_uri, redirect_uri, push_uri
 
  url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % (
    tip, uuid, int(time.time()))
 
  r = myRequests.get(url=url)
  r.encoding = 'utf-8'
  data = r.text
 
  # print(data)
 
  # window.code=500;
  regx = r'window.code=(\d+);'
  pm = re.search(regx, data)
 
  code = pm.group(1)
 
  if code == '201': # 已扫描
    print('成功扫描,请在手机上点击确认以登录')
    tip = 0
  elif code == '200': # 已登录
    print('正在登录...')
    regx = r'window.redirect_uri="(\S+?)";'
    pm = re.search(regx, data)
    redirect_uri = pm.group(1) + '&fun=new'
    base_uri = redirect_uri[:redirect_uri.rfind('/')]
 
    # push_uri与base_uri对应关系(排名分先后)(就是这么奇葩..)
    services = [
      ('wx2.qq.com', 'webpush2.weixin.qq.com'),
      ('qq.com', 'webpush.weixin.qq.com'),
      ('web1.wechat.com', 'webpush1.wechat.com'),
      ('web2.wechat.com', 'webpush2.wechat.com'),
      ('wechat.com', 'webpush.wechat.com'),
      ('web1.wechatapp.com', 'webpush1.wechatapp.com'),
    ]
    push_uri = base_uri
    for (searchUrl, pushUrl) in services:
      if base_uri.find(searchUrl) >= 0:
        push_uri = 'https://%s/cgi-bin/mmwebwx-bin' % pushUrl
        break
 
    # closeQRImage
    if sys.platform.find('darwin') >= 0: # for OSX with Preview
      os.system("osascript -e 'quit app \"Preview\"'")
  elif code == '408': # 超时
    pass
  # elif code == '400' or code == '500':
 
  return code
 
 
def login():
  global skey, wxsid, wxuin, pass_ticket, BaseRequest
 
  r = myRequests.get(url=redirect_uri)
  r.encoding = 'utf-8'
  data = r.text
 
  # print(data)
 
  doc = xml.dom.minidom.parseString(data)
  root = doc.documentElement
 
  for node in root.childNodes:
    if node.nodeName == 'skey':
      skey = node.childNodes[0].data
    elif node.nodeName == 'wxsid':
      wxsid = node.childNodes[0].data
    elif node.nodeName == 'wxuin':
      wxuin = node.childNodes[0].data
    elif node.nodeName == 'pass_ticket':
      pass_ticket = node.childNodes[0].data
 
  # print('skey: %s, wxsid: %s, wxuin: %s, pass_ticket: %s' % (skey, wxsid,
  # wxuin, pass_ticket))
 
  if not all((skey, wxsid, wxuin, pass_ticket)):
    return False
 
  BaseRequest = {
    'Uin': int(wxuin),
    'Sid': wxsid,
    'Skey': skey,
    'DeviceID': deviceId,
  }
 
  return True
 
 
def webwxinit():
 
  url = (base_uri + 
    '/webwxinit?pass_ticket=%s&skey=%s&r=%s' % (
      pass_ticket, skey, int(time.time())) )
  params = {'BaseRequest': BaseRequest }
  headers = {'content-type': 'application/json; charset=UTF-8'}
 
  r = myRequests.post(url=url, data=json.dumps(params),headers=headers)
  r.encoding = 'utf-8'
  data = r.json()
 
  if DEBUG:
    f = open(os.path.join(os.getcwd(), 'webwxinit.json'), 'wb')
    f.write(r.content)
    f.close()
 
 
  # print(data)
 
  global ContactList, My, SyncKey
  dic = data
  ContactList = dic['ContactList']
  My = dic['User']
  SyncKey = dic['SyncKey']
 
  state = responseState('webwxinit', dic['BaseResponse'])
  return state
 
 
def webwxgetcontact():
 
  url = (base_uri + 
    '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % (
      pass_ticket, skey, int(time.time())) )
  headers = {'content-type': 'application/json; charset=UTF-8'}
 
 
  r = myRequests.post(url=url,headers=headers)
  r.encoding = 'utf-8'
  data = r.json()
 
  if DEBUG:
    f = open(os.path.join(os.getcwd(), 'webwxgetcontact.json'), 'wb')
    f.write(r.content)
    f.close()
 
  # print(data)
 
  dic = data
  MemberList = dic['MemberList']
 
  # 倒序遍历,不然删除的时候出问题..
  SpecialUsers = ["newsapp", "fmessage", "filehelper", "weibo", "qqmail", "tmessage", "qmessage", "qqsync", "floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp",
          "meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder", "weixinreminder", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts", "notification_messages", "wxitil", "userexperience_alarm"]
  for i in range(len(MemberList) - 1, -1, -1):
    Member = MemberList[i]
    if Member['VerifyFlag'] & 8 != 0: # 公众号/服务号
      MemberList.remove(Member)
    elif Member['UserName'] in SpecialUsers: # 特殊账号
      MemberList.remove(Member)
    elif Member['UserName'].find('@@') != -1: # 群聊
      MemberList.remove(Member)
    elif Member['UserName'] == My['UserName']: # 自己
      MemberList.remove(Member)
 
  return MemberList
 
 
def createChatroom(UserNames):
  MemberList = [{'UserName': UserName} for UserName in UserNames]
 
  url = (base_uri + 
    '/webwxcreatechatroom?pass_ticket=%s&r=%s' % (
      pass_ticket, int(time.time())) )
  params = {
    'BaseRequest': BaseRequest,
    'MemberCount': len(MemberList),
    'MemberList': MemberList,
    'Topic': '',
  }
  headers = {'content-type': 'application/json; charset=UTF-8'}
 
  r = myRequests.post(url=url, data=json.dumps(params),headers=headers)
  r.encoding = 'utf-8'
  data = r.json()
 
  # print(data)
 
  dic = data
  ChatRoomName = dic['ChatRoomName']
  MemberList = dic['MemberList']
  DeletedList = []
  BlockedList = []
  for Member in MemberList:
    if Member['MemberStatus'] == 4: # 被对方删除了
      DeletedList.append(Member['UserName'])
    elif Member['MemberStatus'] == 3: # 被加入黑名单
      BlockedList.append(Member['UserName'])
 
  state = responseState('createChatroom', dic['BaseResponse'])
 
  return ChatRoomName, DeletedList, BlockedList
 
 
def deleteMember(ChatRoomName, UserNames):
  url = (base_uri + 
    '/webwxupdatechatroom?fun=delmember&pass_ticket=%s' % (pass_ticket) )
  params = {
    'BaseRequest': BaseRequest,
    'ChatRoomName': ChatRoomName,
    'DelMemberList': ','.join(UserNames),
  }
  headers = {'content-type': 'application/json; charset=UTF-8'}
 
  r = myRequests.post(url=url, data=json.dumps(params),headers=headers)
  r.encoding = 'utf-8'
  data = r.json()
 
  # print(data)
 
  dic = data
 
  state = responseState('deleteMember', dic['BaseResponse'])
  return state
 
 
def addMember(ChatRoomName, UserNames):
  url = (base_uri + 
    '/webwxupdatechatroom?fun=addmember&pass_ticket=%s' % (pass_ticket) )
  params = {
    'BaseRequest': BaseRequest,
    'ChatRoomName': ChatRoomName,
    'AddMemberList': ','.join(UserNames),
  }
  headers = {'content-type': 'application/json; charset=UTF-8'}
 
  r = myRequests.post(url=url, data=json.dumps(params),headers=headers)
  r.encoding = 'utf-8'
  data = r.json()
 
  # print(data)
 
  dic = data
  MemberList = dic['MemberList']
  DeletedList = []
  BlockedList = []
  for Member in MemberList:
    if Member['MemberStatus'] == 4: # 被对方删除了
      DeletedList.append(Member['UserName'])
    elif Member['MemberStatus'] == 3: # 被加入黑名单
      BlockedList.append(Member['UserName'])
 
  state = responseState('addMember', dic['BaseResponse'])
 
  return DeletedList, BlockedList
 
 
def syncKey():
  SyncKeyItems = ['%s_%s' % (item['Key'], item['Val'])
          for item in SyncKey['List']]
  SyncKeyStr = '|'.join(SyncKeyItems)
  return SyncKeyStr
 
 
def syncCheck():
  url = push_uri + '/synccheck?'
  params = {
    'skey': BaseRequest['Skey'],
    'sid': BaseRequest['Sid'],
    'uin': BaseRequest['Uin'],
    'deviceId': BaseRequest['DeviceID'],
    'synckey': syncKey(),
    'r': int(time.time()),
  }
 
  r = myRequests.get(url=url,params=params)
  r.encoding = 'utf-8'
  data = r.text
 
  # print(data)
 
  # window.synccheck={retcode:"0",selector:"2"}
  regx = r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}'
  pm = re.search(regx, data)
 
  retcode = pm.group(1)
  selector = pm.group(2)
 
  return selector
 
 
def webwxsync():
  global SyncKey
 
  url = base_uri + '/webwxsync?lang=zh_CN&skey=%s&sid=%s&pass_ticket=%s' % (
    BaseRequest['Skey'], BaseRequest['Sid'], quote_plus(pass_ticket))
  params = {
    'BaseRequest': BaseRequest,
    'SyncKey': SyncKey,
    'rr': ~int(time.time()),
  }
  headers = {'content-type': 'application/json; charset=UTF-8'}
 
  r = myRequests.post(url=url, data=json.dumps(params))
  r.encoding = 'utf-8'
  data = r.json()
 
  # print(data)
 
  dic = data
  SyncKey = dic['SyncKey']
 
  state = responseState('webwxsync', dic['BaseResponse'])
  return state
 
 
def heartBeatLoop():
  while True:
    selector = syncCheck()
    if selector != '0':
      webwxsync()
    time.sleep(1)
 
 
def main():
  global myRequests
 
  if hasattr(ssl, '_create_unverified_context'):
    ssl._create_default_https_context = ssl._create_unverified_context
 
  headers = {'User-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36'}
  myRequests = requests.Session()
  myRequests.headers.update(headers)
 
 
  if not getUUID():
    print('获取uuid失败')
    return
 
  print('正在获取二维码图片...')
  showQRImage()
 
  while waitForLogin() != '200':
    pass
 
  os.remove(QRImagePath)
 
  if not login():
    print('登录失败')
    return
 
  if not webwxinit():
    print('初始化失败')
    return
 
  MemberList = webwxgetcontact()
 
  print('开启心跳线程')
  threading.Thread(target=heartBeatLoop)
 
  MemberCount = len(MemberList)
  print('通讯录共%s位好友' % MemberCount)
 
  ChatRoomName = ''
  result = []
  d = {}
  for Member in MemberList:
    d[Member['UserName']] = (Member['NickName'], Member['RemarkName'])
  print('开始查找...')
  group_num = int(math.ceil(MemberCount / float(MAX_GROUP_NUM)))
  for i in range(0, group_num):
    UserNames = []
    for j in range(0, MAX_GROUP_NUM):
      if i * MAX_GROUP_NUM + j >= MemberCount:
        break
      Member = MemberList[i * MAX_GROUP_NUM + j]
      UserNames.append(Member['UserName'])
 
    # 新建群组/添加成员
    if ChatRoomName == '':
      (ChatRoomName, DeletedList, BlockedList) = createChatroom(
        UserNames)
    else:
      (DeletedList, BlockedList) = addMember(ChatRoomName, UserNames)
 
    # todo BlockedList 被拉黑列表
 
    DeletedCount = len(DeletedList)
    if DeletedCount > 0:
      result += DeletedList
 
    # 删除成员
    deleteMember(ChatRoomName, UserNames)
 
    # 进度条
    progress = MAX_PROGRESS_LEN * (i + 1) / group_num
    print('[', '#' * int(progress), '-' * int(MAX_PROGRESS_LEN - progress), ']', end=' ')
    print('新发现你被%d人删除' % DeletedCount)
    for i in range(DeletedCount):
      if d[DeletedList[i]][1] != '':
        print('%s(%s)' % (d[DeletedList[i]][0],d[DeletedList[i]][1]))
      else:
        print(d[DeletedList[i]][0])
 
    if i != group_num - 1:
      print('正在继续查找,请耐心等待...')
      # 下一次进行接口调用需要等待的时间
      time.sleep(INTERFACE_CALLING_INTERVAL)
  # todo 删除群组
 
  print('\n结果汇总完毕,20s后可重试...')
  resultNames = []
  for r in result:
    if d[r][1] != '':
      resultNames.append('%s(%s)' % (d[r][0],d[r][1]))
    else:
      resultNames.append(d[r][0])
 
  print('---------- 被删除的好友列表(共%d人) ----------' % len(result))
  # 过滤emoji
  resultNames = list(map(lambda x: re.sub(r'<span.+/span>', '', x), resultNames))
  if len(resultNames):
    print('\n'.join(resultNames))
  else:
    print("无")
  print('---------------------------------------------')
 
 
# windows下编码问题修复
# http://blog.csdn.net/heyuxuanzee/article/details/8442718
 
class UnicodeStreamFilter:
 
  def __init__(self, target):
    self.target = target
    self.encoding = 'utf-8'
    self.errors = 'replace'
    self.encode_to = self.target.encoding
 
  def write(self, s):
    if type(s) == str:
      try:
        s = s.decode('utf-8')
      except:
        pass
    s = s.encode(self.encode_to, self.errors).decode(self.encode_to)
    self.target.write(s)
 
if sys.stdout.encoding == 'cp936':
  sys.stdout = UnicodeStreamFilter(sys.stdout)
 
if __name__ == '__main__':
 
  print('本程序的查询结果可能会引起一些心理上的不适,请小心使用...')
  print('1小时内只能使用一次,否则会因操作繁忙阻止建群')
  main()
  print('回车键退出...')
  input()
Python 相关文章推荐
Python二分法搜索算法实例分析
May 11 Python
Python+selenium实现截图图片并保存截取的图片
Jan 05 Python
Ubuntu下使用Python实现游戏制作中的切分图片功能
Mar 30 Python
Python内存读写操作示例
Jul 18 Python
python利用thrift服务读取hbase数据的方法
Dec 27 Python
Python3模拟curl发送post请求操作示例
May 03 Python
详解Python并发编程之从性能角度来初探并发编程
Aug 23 Python
python智联招聘爬虫并导入到excel代码实例
Sep 09 Python
Python任务调度模块APScheduler使用
Apr 15 Python
解决pyinstaller打包运行程序时出现缺少plotly库问题
Jun 02 Python
keras 多任务多loss实例
Jun 22 Python
使用Python下载抖音各大V视频的思路详解
Feb 06 Python
[原创]Python入门教程4. 元组基本操作
Oct 31 #Python
对numpy中数组转置的求解以及向量内积计算方法
Oct 31 #Python
Ubuntu下Python2与Python3的共存问题
Oct 31 #Python
对numpy中向量式三目运算符详解
Oct 31 #Python
对numpy中的where方法嵌套使用详解
Oct 31 #Python
在Python中使用defaultdict初始化字典以及应用方法
Oct 31 #Python
在ubuntu16.04中将python3设置为默认的命令写法
Oct 31 #Python
You might like
php实现utf-8和GB2312编码相互转换函数代码
2013/02/07 PHP
php断点续传之文件分割合并详解
2016/12/13 PHP
PHP 实现手机端APP支付宝支付功能
2018/06/07 PHP
Laravel中如何轻松容易的输出完整的SQL语句
2020/07/26 PHP
javascript 屏蔽鼠标键盘的几段代码
2008/01/02 Javascript
js计算精度问题小结
2013/04/22 Javascript
鼠标划过实现延迟加载并隐藏层的js代码
2013/10/11 Javascript
7个让JavaScript变得更好的注意事项
2015/01/28 Javascript
JavaScript中通过prototype属性共享属性和方法的技巧实例
2015/03/13 Javascript
Nodejs实现批量下载妹纸图
2015/05/28 NodeJs
readonly和disabled属性的区别
2015/07/26 Javascript
javascript动画之磁性吸附效果篇
2016/12/09 Javascript
使用JavaScript实现表格编辑器(实例讲解)
2017/08/02 Javascript
nodejs 如何手动实现服务器
2018/08/20 NodeJs
微信小程序Echarts覆盖正常组件问题解决
2019/07/13 Javascript
VUE动态生成word的实现
2020/07/26 Javascript
[53:15]Mineski vs iG 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
[09:13]DOTA2-DPC中国联赛 正赛 Ehome vs Magma 选手采访 1月19日
2021/03/11 DOTA
Python编程中使用Pillow来处理图像的基础教程
2015/11/20 Python
Python使用Tkinter实现机器人走迷宫
2018/01/22 Python
Django之无名分组和有名分组的实现
2019/04/16 Python
用python3 urllib破解有道翻译反爬虫机制详解
2019/08/14 Python
Python 在OpenCV里实现仿射变换—坐标变换效果
2019/08/30 Python
基于PyTorch的permute和reshape/view的区别介绍
2020/06/18 Python
HTML5 Video标签的属性、方法和事件汇总介绍
2015/04/24 HTML / CSS
html5的pushstate以及监听浏览器返回事件的实现
2020/08/11 HTML / CSS
StubHub西班牙:购买和出售全球活动门票
2017/06/05 全球购物
英国Office鞋店德国网站:在线购买鞋子、靴子和运动鞋
2018/12/19 全球购物
英文版销售经理个人求职信
2013/11/20 职场文书
生物科学系大学生的自我评价
2013/12/20 职场文书
益达广告词
2014/03/14 职场文书
2014年党员自我评价材料
2014/09/22 职场文书
祖国在我心中演讲稿(小学生)
2014/09/23 职场文书
2014年小学德育工作总结
2014/12/05 职场文书
2015年健康教育工作总结
2015/04/10 职场文书
MySQL读取JSON转换的方式
2022/03/18 MySQL