前言
之前也在网上看过很多抢红包的脚本思路,大多都很片面,考虑到的点并不全,所以在学习他们的思路时我也从中总结和整理了一下,最后将自己的一些想法和使用过程中可能会遇到的问题都融合到了脚本里面,使一个简单的脚本变得更加“人性化”。
设计思路
首先,考虑到一个抢红包的软件,那么它需要应对几个场景,但是在考虑应对场景前,我们要想到无论什么样的场景,发现红包—打开红包—判断是否可抢—抢红包—退出红包界面这样一个操作流程都是一致的,所以我们先写一个公共方法:
def Main_Process(driver):
'''
点开红包——判断是否还有——抢红包——回到聊天框——删除当前红包
:param driver:
:return: 输出本次抢到的红包金额,如果抢失败则为0
在这个方法里,我们不仅要去做抢红包的过程,还可以去获取这次抢了多少钱,用于累计得出总共抢了多少。
WebDriverWait(driver, 10, 0.1).until(EC.presence_of_element_located((MobileBy.ID, 'com.tencent.mm:id/ahs')))
driver.find_element_by_id("com.tencent.mm:id/ahs").click() # 点开红包
WebDriverWait(driver, 10, 0.1).until(EC.presence_of_element_located((MobileBy.ID, 'com.tencent.mm:id/f47')))
money = 0
# sleep(0.5)
try:
# driver.tap([(350, 987), (550, 1187)], 0) #开启红包
driver.find_element_by_id("com.tencent.mm:id/f4f").click()
WebDriverWait(driver, 3, 0.1).until(EC.presence_of_element_located((MobileBy.ID, 'com.tencent.mm:id/eyq')))
money = driver.find_element_by_id("com.tencent.mm:id/eyq").text
print("本次抢到{}元!".format(money))
WebDriverWait(driver, 10, 0.1).until(EC.presence_of_element_located((MobileBy.ID, 'com.tencent.mm:id/ei')))
driver.find_element_by_id("com.tencent.mm:id/ei").click() # 返回聊天框
except:
print("已被抢光。。。")
driver.find_element_by_id("com.tencent.mm:id/f4e").click() # 返回聊天框
以上这段代码中,try的作用是用于跳过复杂的判断,因为所有可领取红包有一个前提,那就是红包页面上会有一个“开”的元素,所以这里我们只需去定位这个元素,如果它没有被定位到,那么说明这个红包不可抢,因此我们在except中给出没有发现这个元素该做出的逻辑,并关闭当前红包也难。然后为了避免一个无效的红包被脚本反复捕捉到影响效率,我们在这段代码之后做了一个删除无效红包的操作。
# 抢完后删除红包
sleep(0.3)
TouchAction(driver).long_press(driver.find_element_by_id("com.tencent.mm:id/ahs")).perform() # 长按红包
loc_dels = ("xpath", '//*[@text="删除"]')
ele = WebDriverWait(driver, 10, 0.2).until(EC.presence_of_element_located(loc_dels))
ele.click()
loc_dels = ("xpath", '//*[@text="删除"]')
ele = WebDriverWait(driver, 10, 0.2).until(EC.presence_of_element_located(loc_dels))
ele.click()
print('已抢红包删除成功!')
return money
以上就是整个抢红包的过程,我们把它完成了,接下来我们要开始考虑各种场景的处理。
- 针对单个微信群 ,此场景一般用于当预先知道某个群会高频发出现红包的情况下,那我们就不用去一个一个聊天框里找红包了只需要锁定这个群,那么就能节省很多繁琐的操作,从而高效率的在当前群里领取红包。这里我们可以先写一个类专门来处理这样一个场景。
def Method_Target(driver,TargetName):
'''
只针对入参指定聊天框的红包有效
:param driver:
:param TargetName:聊天框名称(包括群名、好友昵称)
:return:
'''
try:
# 聊天列表中寻找目标聊天框并点击进入
target = driver.find_element_by_android_uiautomator('new UiSelector().text("{}")'.format(TargetName))
target.click()
except:
#若聊天列表中找不到目标聊天框变使用搜索查找的方式并点击进入
Get_Into(driver, TargetName)
这段代码中的try和except是用来处理一种异常,常规情况下,最快捷进入微信群的方法是通过点击聊天列表中的群直接进入,但是如果微信中新消息太多会快速将目标群挤出当前页面,从而无法锁定到元素,那么在这里给了个异常处理,在聊天列表中无法定位到当前群的元素时,我们采用搜索的方式进入群,也就是点击微信上的搜索按钮—输入群名称—点击群进入聊天框。但又考虑到后面一些场景可能也会遇到这样的情况,所以将这个步骤也写成了一个公共方法。
def Get_Into(driver,ChatName):
# 搜索聊天框——进入聊天框
driver.find_element_by_accessibility_id("搜索").click()
send = WebDriverWait(driver, 3, 0.1).until(EC.presence_of_element_located((MobileBy.ID, "com.tencent.mm:id/bxz")))
send.send_keys(ChatName)
ChatXpath = '//*[@resource-id="com.tencent.mm:id/ir3" and @text="{}"]'.format(ChatName)
WebDriverWait(driver, 3, 0.1).until(EC.presence_of_element_located((MobileBy.XPATH, ChatXpath))).click()
之前我们的抢红包公共方法中有一个提取抢了多少钱的功能,那么把它累计到总金额中去,这样通过脚本抢下来我们就能知道本次脚本帮助我们抢了多少红包。
total = 0
while True:
try:
# 抓取红包信息,若未抓到继续循环抓取
target = driver.find_element_by_id("com.tencent.mm:id/ahs")
except:
target = 0
if target == 0:
pass
else:
print('发现新红包!')
money = Main_Process(driver) # 抢红包的整个过程
total = total + float(money)
print("已经累计抢到红包{}元!".format(total))
加入了while循环让这个抓取红包的过程一直循环下去,直至退出程序,期间我们会不停的去抓取红包信息,如果没有定位到红包信息的元素那么会报错,所以这里我们跳过异常让它继续进行下一次循环,如果抓取到红包的元素,那么我们就去调用公共方法领取红包,最后把抢到金额累加到总金额上去,这样一来,我们这个场景的设计就算告一段落。
- 针对所有微信群 ,在通常情况下,最多用到的会是这种不知道哪个群会有发红包的行为的场景,那么针对这个场景我们再写一个单独的类。
def Method_All(driver,ForwardName,Type):
'''
针对所有聊天框的红包有效
:param driver:
:return:
这个场景中我们可以不用进入群中就能知道有没有红包,因为会在聊天列表中有一个“[微信红包]”的标识让我们可以识别到这个群里是不是有人发了红包,不管红包的内容写的什么,这个标识却可以作为我们用来识别是否出现红包的判断,因此我们只要在元素中通过text的模糊匹配找到这样的关键字,然后点击进去就可以。
target = driver.find_element_by_android_uiautomator('new UiSelector().textContains("[微信红包]")')
然后再调用公用的方法去处理抢红包的过程,只是在结束后加一段退出群聊的代码即可
driver.find_element_by_id("com.tencent.mm:id/uo").click() #退出群聊
- 屏蔽个别微信群,说完了无差别抢红包的场景后,下面就不得不说到这样一个问题,有些群里面的红包可能是群体收费发出来的,如果一不小心把孩子班级群里面的书本费给全部领取了,岂不是很尴尬?所以下面我们要针对这样个场景来做处理。
def Method_Screen(driver,ScreenName,ForwardName,Type):
'''
对入参聊天框以外的红包有效
:param driver:
:param ScreenName: 屏蔽的微信聊天框列表
:return:
这里我们需要在主主程序中给一个input让使用者以某种符号为分隔符号将需要屏蔽的群传进来,从而在后续的抢红包过程中不会去碰这些群里的红包,在这里我使用的是空格作为分隔符号。
Put = input("输入想屏蔽的群名或好友昵称以空格隔开(若没有请敲击回车跳过):")
ScreenName = Put.split(" ")
这样分隔出来后,就会以列表的方式将需要屏蔽的群传入脚本中作为判断参数。首先我们发现红包的时候通过轴定位的方式找到这个聊天框的名称,然后拿来和列表中的参数做比较,如果在列表中有,我们就进入群消息中将这个红包给删除掉,以免影响其他方法的判断。那群名称如果不在列表中,我们就走正常的抢红包流程即可。
Xpath = '//*[contains(@text, "[微信红包]")]/parent::*/parent::*/parent::' \
'android.widget.LinearLayout/android.widget.LinearLayout[1]/android.widget.LinearLayout[1]' \
'/android.view.View'
Text = driver.find_element_by_xpath(Xpath).text
if Text in ScreenName:
driver.find_element_by_android_uiautomator('new UiSelector().textContains("[微信红包]")').click()
TouchAction(driver).long_press(driver.find_element_by_id("com.tencent.mm:id/ahs")).perform() # 长按红包
loc_dels = ("xpath", '//*[@text="删除"]')
ele = WebDriverWait(driver, 10, 0.2).until(EC.presence_of_element_located(loc_dels))
ele.click()
loc_dels = ("xpath", '//*[@text="删除"]')
ele = WebDriverWait(driver, 10, 0.2).until(EC.presence_of_element_located(loc_dels))
ele.click()
sleep(0.2)
driver.find_element_by_id("com.tencent.mm:id/uo").click() # 退出群聊
- 微信消息处理, 抢红包的所有场景基本上得到了解决,那么问题来了,我们脚本需要微信一直挂在电脑上进行运行,而使用的时候我们又不可能一直坐在电脑前面守着,所以这个时候就需要做一个消息转发功能。咱们举个例子,过年了,各大群都在发红包,但是咱们过年又要打麻将或者喝酒聚会什么的,但是远程挂着微信抢红包我们就看不到微信消息,万一错过了女神的表白怎么办?所以专门在做一个消息转发功能。
def News_Forward(driver,ForwardName,Type):
'''
用于将一个微信收到的消息转发给指定微信号,并可做回复转发
应用场景:当微信号挂在电脑脚本上抢红包时,能用其他微信号来收发消息
:param driver:
:param ForwardName: 用来做转发的微信号昵称
:param Type: 消息转发功能的开关,入参:是——开启
'''
这个功能的原理是通过另外一个微信号来接收原微信号的新消息,然后通过转发用的微信再将消息回复过去。实现原理是当原微信上收到带数字的红点聊天框时(屏蔽的群消息都只是一个小红点没有数字,而屏蔽的群消息中当出现艾特自己的消息是也会有带数字的红点),我们就可以通过这个带数字的红点来定位元素,然后通过轴定位方式找到它的消息内容及发送人。
# print('开始抓取消息')
TextXpath = '//*[@resource-id="com.tencent.mm:id/iot"]/parent::*/parent::android.widget.LinearLayout/android.'\
'widget.LinearLayout/android.widget.LinearLayout[2]/android.widget.LinearLayout/android.view.View'
Text = driver.find_element_by_xpath(TextXpath).text
# print(Text)
if Text:
NameXpath = '//*[@resource-id="com.tencent.mm:id/iot"]/parent::*/parent::android.widget.LinearLayout/android'\
'.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout/android.view.View'
Name = driver.find_element_by_xpath(NameXpath).text
print('抓取到消息发送人:',Name)
news = "[转发]{}:{}".format(Name,Text)
# 将红点清掉
driver.find_element_by_android_uiautomator('new UiSelector().text("{}")'.format(Name)).click()
sleep(0.3)
driver.find_element_by_id("com.tencent.mm:id/uo").click()
抓取到这个消息后就可以将红点给清除掉了,可以通过点击进入聊天框再退出来的方式清除,或者是长按标记已读又或者拖动红点等等,选择自己觉得方便的方式。那么光获取到消息和发送人当然还不够,下面我们要讲获取到的消息做一个处理和区分。上面也提到了,当抢红包的微信号收到消息后,要实现脚本自动把消息转发给自己的另外一个号,然后在另外一个上通过特殊的格式进行回复消息时,再将这条消息通过抢红包的微信回复给发消息的人,所以这里就有两个类别的消息:一个是别人发送给自己的消息,另一个则是自己发送给自己用来回复别人的消息。但是不管是什么类别的消息,我们发消息的操作过程都是一致的,只是传的参数不同,所以这里又可以写一个公共方法:
def Search_Chat(driver,Type,ChatName,news):
'''
用于判断正常和异常情况下进入聊天框的方式发送消息
:param driver:
:param Type: normal——正常、abnormal——异常
:param ChatName: 聊天框名称
:param news: 消息内容
:return:
'''
这里还有一个问题,上面有写到一个公共类,是用来处理列表中找不到指定聊天框的异常方法,这里刚好就能用上了。由于我们要回复消息时可能这个聊天框在列表中可见又可能被挤出了可见范围,那么刚好就能运用到之前的公共方法:
以下是正常情况下的方法,从聊天列表中直接点击进入:
if Type == 'normal':
uiautomator = 'new UiSelector().text("{}")'.format(ChatName)
driver.find_element_by_android_uiautomator(uiautomator).click()
SendKeys = WebDriverWait(driver, 10, 0.1).until(
EC.presence_of_element_located((MobileBy.ID, 'com.tencent.mm:id/auj')))
SendKeys.send_keys(news)
WebDriverWait(driver, 10, 0.1).until(
EC.presence_of_element_located((MobileBy.ID, 'com.tencent.mm:id/ay5'))).click()
driver.find_element_by_id("com.tencent.mm:id/uo").click() # 退出聊天框
以下是异常情况下的处理:
elif Type == 'abnormal':
# 搜索聊天框——进入聊天框
Get_Into(driver, ChatName)
# 发送消息——退出聊天框
SendKeys = WebDriverWait(driver, 10, 0.1).until(
EC.presence_of_element_located((MobileBy.ID, 'com.tencent.mm:id/auj')))
SendKeys.send_keys(news)
WebDriverWait(driver, 10, 0.1).until(
EC.presence_of_element_located((MobileBy.ID, 'com.tencent.mm:id/ay5'))).click()
driver.find_element_by_id("com.tencent.mm:id/uo").click() # 退出聊天框
WebDriverWait(driver, 3, 0.1).until(EC.presence_of_element_located((MobileBy.ID, 'com.tencent.mm:id/aib'))).click()
接下来我们要处理消息分类了,先来处理回复给别人的消息,这里我才用了两个中文冒号这种特殊的方式来分割回复人和消息本体,也就是当我手上这个微信号收到的消息是来自我的另外一个微信号,且内容是以“XXX::XXXXX”这样的内容出现时,就将它分割成了一个[XXX, XXXXX]的列表。然后取第一个作为回复目标的text抓取元素,第二个作为回复用的消息。
if Name == ForwardName:
# 小号发给大号的消息用于回复 格式——接收人昵称::回复内容
ReplyText = Text.split("::")
try:
Search_Chat(driver, 'normal', ReplyText[0], ReplyText[1])
except:
Search_Chat(driver,'abnormal', ReplyText[0], ReplyText[1])
print("消息回复成功!")
然后再考虑另外一个类别,就比较简单了:
else:
# 进入小号的聊天框转发消息,此循环是保证在当前页面找不到小号的时候进行下滑寻找小号的聊天框
try:
Search_Chat(driver, 'normal', ForwardName, news)
except:
Search_Chat(driver, 'abnormal',ForwardName, news)
print("消息转发成功!")
最后的实际效果如下:
目前这个地方有一点小小的瑕疵,因为是通过聊天列表中获取的消息内容,所以,当内容过多时,后面会以…省略掉,但是进入聊天框内后,消息文本从元素中无法获取,应该是被微信做了隐私保护处理,只能看到元素的属性,无法获取里面的聊天内容,所以暂时就先这样吧,后期如果有更好的处理方法欢迎相互交流。
打包处理
整个代码写好后,就需要在主程序中去做调试了,这里我是通过各种if函数和input的方式来获取参数和控制流这里就不用过多赘述。最后就是打包,要将这套代码打包出来脱离现有的环境变量依赖,并通过一个exe的应用程序就能运行起来。这里在网上查了很久的资料,始终无法解决将Appium和夜神模拟器这种外置程序的依赖一同打包进去的办法,所以也只好暂时作罢,在运行的时候只好手动安装好这些依赖软件并运行。下面说说打包的操作,通过python的pyinstaller模块来打包非常的简单,网上也有教程,先通过pip命令来安装pyinstaller。然后在脚本所在牡蛎下运行cmd,键入pyinstaller -F 主程序文件.py
运行完以后会在当前文件夹中生成一个.spec后缀名的文件,进入修改一下配置,下面是所需要修改的地方:
# 项目主程序py及项目其他的包,主程序放在最前面
a = Analysis(['demo.py','red_envelopes.py'],
pathex=['E:\\StaroEnki\\XXX\\project\\newhope'], #项目所在绝对路径
exe = EXE(
name='微信红包大师', #exe程序的名称
icon=r'E:\StaroEnki\XXX\project\newhope\tubiao.ico' #图标绝对路径)
修改完后保存文件,并在刚才大概的cmd下再次键入pyinstaller -F 生成的文件.spec
结束后就可以在生成的dist目录下看到打包成功的exe文件了
好了这次分享就到这了,欢迎大家相互交流,如果你们有什么更好的处理方法都可以给我建议,谢谢大家!