介绍 支付宝2021集福卡活动与其余50多个频道进行合作,可以在每个不同频道获取额外1张福卡,但是这个额外领取福卡的页面指向的是一个网页,只是URL参数有所不同。在不影响相关活动的两年后,以学习态度分享一下当时的分析情况。
分析页面 活动URL为 https://render.alipay.com/p/c/17yq18lq3slc
参加活动的流程为:输入手机号 -> 获取验证码 -> 输入验证码 -> 领取福卡
每个频道的URL会带上频道的ID用于识别,例如天猫精灵为:https://render.alipay.com/p/c/17yq18lq3slc?source=JING_LING
频道的所有信息在主页面中存放,例如天猫精灵:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 { "channelList" : [ { "startTime" : 1611763200000 , "endTime" : 1613052000000 , "giftImage" : { "img" : "https://gw.alipayobjects.com/os/c/cms/img/6273f0c6-dbd6-45f7-beeb-5c199f93f390_w600_h482.png" , "width" : 648 , "height" : 477 } , "channelLogo" : { "img" : "https://gw.alipayobjects.com/os/c/cms/img/f63cace2-8f84-4807-bd3c-363f95f7a9fd_w278_h35.png" , "width" : 278 , "height" : 36 } , "channelBanner" : { "link" : "" , "img" : "" , "width" : 690 , "height" : 221 } , "title" : "天猫精灵助你更快集五福" , "endTitle" : "天猫精灵助你更快集五福" , "sourceList" : [ { "source" : "JING_LING" , "name" : "天猫精灵" } ] , "client" : "JING_LING" , "music" : "" } , ] }
里面存放着活动开始结束时间、页面中礼物的图案、合作频道的LOGO等信息。
获取验证码分析 输入手机号后,点击获取验证码
此时有两个请求是关键,一个是 POST https://rds.alipay.com/captcha.htm, 一个是 GET https://mobilegw.alipay.com/mgw.htm。
首先看第一个请求的POST参数:
1 2 3 4 5 6 7 8 9 10 { "type" : "silence" , "bizNo" : "0e41806e19cb4895814f06c0faa4402705cd13ad164448a3ace21620021ebc7a" , "mobile" : "13888888888" , "useragent" : "Mozilla/5.0 (Linux; U; Android 10; zh-CN; MI 10 Build/QKQ1.190828.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 Quark/4.3.3.145 Mobile Safari/537.36 Edg/89.0.4389.23" , "refer" : "" , "data" : "PBHksnmdBo5GVTxcIp0fPn4sgmvx1okBGMtrOVR8mt2ApwdPiZsdE1wfo5K04Qm2EkS_g5HWK0sdjlOcVWc6io3uESZREnQX_glMdV8GV8WMJddsEikuCj8zRuytiehTR2yfAl7KplMo96Lb5Ff.leQaIK1QBnVhL85umax6VTWOW79TR2ZgtRY95U5MIjiTBn4pLql7gUh3_AzRfJW.SLXGoNQODt1QNUL_HxB054baAehgJtVca2Ni.avavuK.pZ6iJQ_cRAnthNaiENIRNAEvnNwBwuFHy5ObV.wDH5brTYbt4jJxnYKB2DAafGS97dM3kQZNcl8zD248x_8xz.jIyplo5xpkqmjMIEYjJ6gI3zE2711zsTTFIjB64d34aPIJpB2su9SrG9JrqCJgudgDJCeesom2uxqG0QzR0QLivAgnzyczghviqmUe.1Sl.Vhl77SLeoFuwgqPnpPogZml.tAHbq8cLIfkNHG1ykEEts3VUosxCEJ1ic94BrY1WW5Bs_HQ3W42OX2QIVtg08sfXIDe.Capb4hfOrHIkahZLn2m0dLIWeT8KEHkXNbK1QTr.JL7HXqQxJDx5ApBtcFa4Jkb_NsGFEpv6uBaPK8x2pK6nry012AZbuN_Su85_BAm5H9qZog.4L68pdPYHZbjW2W1EZlnwQpgznv6ERt.A2y86cGlcVH60RcfEAasV4BENHbp0gID43GJYX0h9CLsBydZ5_3WcuM_4.SP.NTRmGorObJEeBvfptl3KN3ZBKuFv0tNo9XDBuvknMkZw1w2WcZhSX2Sn8A5i1s.g3h0_icrbwSPrAgOG_9tfbE5eQEagZB4M2F7EpI0T.z9JTxvf7EpfLFuQij6HSsD6FcAtkGA96shRC20tfc29wQ60pbUasxOeOexAHud_emSYvYcScrWexNtufiA3yuIJ77NEWADCaxTvMLSfDVMsFYBZu4VSU13OoB9J__hMAQqSE8ZQ8b6Wdn1FNvohi7NxE5cTiPelk8K.lNc01zt7tn4b1sGNBLxkI0QQf6Q2Brnm4JNHqA5PNjsHMpyfiXWw45" , "appid" : "blessingprod_wufu_otp" , "scene" : "DO_NOTHING" }
通过参数bizNo在js中找到相关代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Zm = function (e, t ) { return Dm (void 0 , void 0 , void 0 , function ( ) { return Im (this , function (n ) { return [2 , new Promise (function (n ) { var r = window .antcap .initValidator ({ params : { type : "silence" , appId : Fm , bizNo : e, mobile : t, useragent : navigator.userAgent , refer : document .referrer }, success : function (e ) { n (e) }, fail : function (e ) { n (e) } }); r.validate () } )] }) }) }
其中e和t是外传参数,t为手机号,继续查找Zm函数的调用:
1 2 3 4 5 6 7 8 9 case 0 : return !0 === id ? [2 ] : (id = !0 , setTimeout (function ( ) { id = !1 }, 1e3 ), 0 !== b ? [2 ] : "" === l ? (Em .fail ("\u8bf7\u8f93\u5165\u624b\u673a\u53f7" ), [2 ]) : /1\d{10}/ .test ("" + l) ? (e = ("" + V () + V ()).replace (/-/g , "" ), [4 , Zm (e, l)]) : (Em .fail ("\u8bf7\u8f93\u5165\u6b63\u786e\u7684\u624b\u673a\u53f7" ), [2 ]));
可以看到Zm
中的e = ("" + V() + V()).replace(/-/g, "")
那么 V()
作用是什么呢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function L (e, t, n ) { e = e || {}; var r = e.random || (e.rng || A)(); if (r[6 ] = 15 & r[6 ] | 64 , r[8 ] = 63 & r[8 ] | 128 , t) { n = n || 0 ; for (var a = 0 ; a < 16 ; ++a) t[n + a] = r[a]; return t } return R (r) } var V = L
其中的A呢
1 2 3 4 5 6 7 P = new Uint8Array (16 ); function A ( ) { if (!d && (d = "undefined" !== typeof crypto && crypto.getRandomValues && crypto.getRandomValues .bind (crypto) || "undefined" !== typeof msCrypto && "function" === typeof msCrypto.getRandomValues && msCrypto.getRandomValues .bind (msCrypto), !d)) throw new Error ("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported" ); return d (P) }
其中提到了一个开源库,一看是在JS中生成UUID的库,再结合bizNo是64位字符的形式,一个V()则是32个字符(除了-),再结合e = ("" + V() + V()).replace(/-/g, "")
,把V()的”-“都去除掉
所以可以大胆猜测V()
就是这个库的uuidv4()
1 2 import { v4 as uuidv4 } from 'uuid' ;uuidv4 ();
即
1 e = ("" + uuidv4 () + uuidv4 ()).replace (/-/g , "" )
可以看一下实际情况,其中的A返回了d(P),经过断点可以发现d为 getRandomValues()
在控制台输出 crypto.getRandomValues(new Uint8Array(16));
[85, 86, 174, 111, 221, 244, 132, 185, 204, 36, 173, 125, 124, 231, 26, 172]
然后返回到L函数中,最终这个数组传入到R()函数中,经过断点,R()为:
1 2 3 4 5 6 7 function I (e ) { var t = arguments .length > 1 && void 0 !== arguments [1 ] ? arguments [1 ] : 0 , n = (M[e[t + 0 ]] + M[e[t + 1 ]] + M[e[t + 2 ]] + M[e[t + 3 ]] + "-" + M[e[t + 4 ]] + M[e[t + 5 ]] + "-" + M[e[t + 6 ]] + M[e[t + 7 ]] + "-" + M[e[t + 8 ]] + M[e[t + 9 ]] + "-" + M[e[t + 10 ]] + M[e[t + 11 ]] + M[e[t + 12 ]] + M[e[t + 13 ]] + M[e[t + 14 ]] + M[e[t + 15 ]]).toLowerCase (); if (!z (n)) throw TypeError ("Stringified UUID is invalid" ); return n }
其中M是什么,再看一下:
1 2 for (var z = j, M = [], D = 0 ; D < 256 ; ++D) M.push ((D + 256 ).toString (16 ).substr (1 ));
这个逻辑就很清晰了,把数字转为16进制并转为字符串,为了补齐到两位,先加256(16进制为100),然后砍掉第一位。最终D为00到ff的256个16进制字符串数组。
最终在I函数中,将传进来的16个数的数组进行转换并添加”-“进行拼接,最终形成一个UUID。这样也验证了我们之前猜测的正确性,这个的确是生成UUID的函数。
也就是 bizNo 为两个UUID去除”-“的结果。
接着我们看data参数,这个参数很长,可能是一段数据经过一些加密算法最终得到的结果。
"data": "PBHksnmdBo5GVTxcIp0fPn4sgmvx1okBGMtrOVR8mt2ApwdPiZsdE1wfo5K04Qm2EkS_g5HWK0sdjlOcVWc6io3uESZREnQX_glMdV8GV8WMJddsEikuCj8zRuytiehTR2yfAl7KplMo96Lb5Ff.leQaIK1QBnVhL85umax6VTWOW79TR2ZgtRY95U5MIjiTBn4pLql7gUh3_AzRfJW.SLXGoNQODt1QNUL_HxB054baAehgJtVca2Ni.avavuK.pZ6iJQ_cRAnthNaiENIRNAEvnNwBwuFHy5ObV.wDH5brTYbt4jJxnYKB2DAafGS97dM3kQZNcl8zD248x_8xz.jIyplo5xpkqmjMIEYjJ6gI3zE2711zsTTFIjB64d34aPIJpB2su9SrG9JrqCJgudgDJCeesom2uxqG0QzR0QLivAgnzyczghviqmUe.1Sl.Vhl77SLeoFuwgqPnpPogZml.tAHbq8cLIfkNHG1ykEEts3VUosxCEJ1ic94BrY1WW5Bs_HQ3W42OX2QIVtg08sfXIDe.Capb4hfOrHIkahZLn2m0dLIWeT8KEHkXNbK1QTr.JL7HXqQxJDx5ApBtcFa4Jkb_NsGFEpv6uBaPK8x2pK6nry012AZbuN_Su85_BAm5H9qZog.4L68pdPYHZbjW2W1EZlnwQpgznv6ERt.A2y86cGlcVH60RcfEAasV4BENHbp0gID43GJYX0h9CLsBydZ5_3WcuM_4.SP.NTRmGorObJEeBvfptl3KN3ZBKuFv0tNo9XDBuvknMkZw1w2WcZhSX2Sn8A5i1s.g3h0_icrbwSPrAgOG_9tfbE5eQEagZB4M2F7EpI0T.z9JTxvf7EpfLFuQij6HSsD6FcAtkGA96shRC20tfc29wQ60pbUasxOeOexAHud_emSYvYcScrWexNtufiA3yuIJ77NEWADCaxTvMLSfDVMsFYBZu4VSU13OoB9J__hMAQqSE8ZQ8b6Wdn1FNvohi7NxE5cTiPelk8K.lNc01zt7tn4b1sGNBLxkI0QQf6Q2Brnm4JNHqA5PNjsHMpyfiXWw45"
前面的Zm函数中只有:
type: "silence",
appId: Fm,
bizNo: e,
mobile: t,
useragent: navigator.userAgent,
refer: document.referrer
这几个参数,而最终的post请求则多了
"data": "PBHksnmdBo5GVTxcIp0fPn4sgmvx1okBGMtrOVR8mt2ApwdPiZsdE1wfo5K04Qm2EkS_g5HWK0sdjlOcVWc6io3uESZREnQX_glMdV8GV8WMJddsEikuCj8zRuytiehTR2yfAl7KplMo96Lb5Ff.leQaIK1QBnVhL85umax6VTWOW79TR2ZgtRY95U5MIjiTBn4pLql7gUh3_AzRfJW.SLXGoNQODt1QNUL_HxB054baAehgJtVca2Ni.avavuK.pZ6iJQ_cRAnthNaiENIRNAEvnNwBwuFHy5ObV.wDH5brTYbt4jJxnYKB2DAafGS97dM3kQZNcl8zD248x_8xz.jIyplo5xpkqmjMIEYjJ6gI3zE2711zsTTFIjB64d34aPIJpB2su9SrG9JrqCJgudgDJCeesom2uxqG0QzR0QLivAgnzyczghviqmUe.1Sl.Vhl77SLeoFuwgqPnpPogZml.tAHbq8cLIfkNHG1ykEEts3VUosxCEJ1ic94BrY1WW5Bs_HQ3W42OX2QIVtg08sfXIDe.Capb4hfOrHIkahZLn2m0dLIWeT8KEHkXNbK1QTr.JL7HXqQxJDx5ApBtcFa4Jkb_NsGFEpv6uBaPK8x2pK6nry012AZbuN_Su85_BAm5H9qZog.4L68pdPYHZbjW2W1EZlnwQpgznv6ERt.A2y86cGlcVH60RcfEAasV4BENHbp0gID43GJYX0h9CLsBydZ5_3WcuM_4.SP.NTRmGorObJEeBvfptl3KN3ZBKuFv0tNo9XDBuvknMkZw1w2WcZhSX2Sn8A5i1s.g3h0_icrbwSPrAgOG_9tfbE5eQEagZB4M2F7EpI0T.z9JTxvf7EpfLFuQij6HSsD6FcAtkGA96shRC20tfc29wQ60pbUasxOeOexAHud_emSYvYcScrWexNtufiA3yuIJ77NEWADCaxTvMLSfDVMsFYBZu4VSU13OoB9J__hMAQqSE8ZQ8b6Wdn1FNvohi7NxE5cTiPelk8K.lNc01zt7tn4b1sGNBLxkI0QQf6Q2Brnm4JNHqA5PNjsHMpyfiXWw45",
"scene": "DO_NOTHING"
两个参数,我们尝试搜索"DO_NOTHING"
,然后在另外一个js文件中找到了这个原始发出的请求代码:
1 2 3 4 5 6 u ({ method : "POST" , contentType : "application/json" , url : r && decodeURIComponent (r) || "https://rds.alipay.com/captcha.htm" , data : JSON .stringify (n) }
然后去找n:
1 2 3 4 5 6 try { t = antcap.fnGetRdsData () } catch (c) {} var n = u ({}, d.cfg .params , { data : t }, c.params );
最终t = antcap.fnGetRdsData()
,在网上进行查找后,发现 antcap.fnGetRdsData()
是支付宝验证码的组件 ,而本次获取手机验证码时,没有进行验证的操作
1 2 3 4 5 6 n.scene = { click : "CLICK_BUTTON" , slide : "STRIKE_BUTTON" , drag : "SHOOT" , silence : "DO_NOTHING" }[c.type ];
"DO_NOTHING"
属于silence
,可见本次无需验证,所以就尝试去除data参数,是否可以重放此api,结果是成功的,如果不成功,也可以按照上文的思路,把这个变成黑盒,作为函数直接进行调用即可。
继续回到我们的这个HTTP请求 这个请求收到的响应则为:
1 2 3 4 5 6 7 8 9 10 { "data" : { "result" : "pass" , "extra" : { "token" : "2b4c9e20c673410fb7583e9019073b8cbaee909f93fa4e7a87d6b05083d5cc75" } } , "message" : "Analyze success." , "success" : true }
从这个发送的请求与相应的内容来看,这个接口负责检查用户目前UA环境和验证情况是否正常,正常则返回一个token供其他接口使用。
接着看第二个接口发送的参数:
Name
Value
_fli_online
true
operationType
alipay.tradecsa.biz.blessingprod.wufu2021.sendVerifyCode
requestData
[{“mobile”:”13888888888”,”source”:”JING_LING”,”rdsBizNo”:”972450e015d0446db73a43b75d6d50c179fac497577345a7899035bb086d000b”,”rdsToken”:”6e2ab8060f5c4881b07566cfa6da7c6aa1ccb55465b640f2b5a64c983faae214”}]
_
1612182118381
callback
jsonp1612181787797
值得注意的是,上个接口获取的Token
在这里作为rdsToken
进行了请求,callback
请求jsonp
的方式为了解决跨域问题。
接着收到的响应:
jsonp1612181787797({
"resultStatus": 1000,
"result": {
"code": "5101",
"resultView": "人气太旺了,请稍后再试",
"success": true
}
})
其中json的"result"->"success"
代表着获取验证码是否成功,如果失败的话,resultView
会给出失败原因。
领取福卡分析 接着我们将收到的验证码输入,并点击“立即领取”,会有一个请求格外显眼:GET https://mobilegw.alipay.com/mgw.htm
这和我们获取验证码结果的请求URL是一样的。
发出的参数为:
Name
Value
_fli_online
true
operationType
alipay.tradecsa.biz.blessingprod.wufu2021.outPrize
requestData
[{“mobile”:”13888888888”,”source”:”JING_LING”,”ackCode”:”123445”}]
_
1612356116283
callback
jsonp1612356024360
可以发现 operationType
由 alipay.tradecsa.biz.blessingprod.wufu2021.sendVerifyCode
变成了 alipay.tradecsa.biz.blessingprod.wufu2021.outPrize
其中 requestData
参数有手机号、频道ID、验证码。
接受的响应为:
jsonp1612356828462({
"resultStatus": 1000,
"result": {
"code": "5101",
"hasPrized": false,
"hasUserId": false,
"prizeList": [
{
"collected": false,
"collectionNum": 0,
"needCollectionNum": 0,
"prizeDescImageUrl": "https://gw.alipayobjects.com/mdn/rms_44d4e1/afts/img/A*8Z2ERZpmZ0cAAAAAAAAAAAAAARQnAQ",
"prizeDownDescText": "万能福卡"
},
{
"collected": false,
"collectionNum": 0,
"needCollectionNum": 0,
"prizeDescImageUrl": "https://gw.alipayobjects.com/mdn/rms_44d4e1/afts/img/A*WNaQQ5tNJpQAAAAAAAAAAAAAARQnAQ",
"prizeDownDescText": "10元红包"
}
],
"resultView": "人气太旺了,请稍后再试",
"success": true
}
})
其中hasPrized
应该是是否抽中现金红包的提示,如果hasUserId
为true
,则会有logonId
参数为用户邮箱,success
是是否成功领取福的关键。
利用分析结果编写批量领取脚本 使用Python编写代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 import requestsimport osimport codecsimport sysimport timeimport jsonimport reclass WebRequests : def __init__ (self ): self.dirPath = '' self.getCaptchaUrl = 'https://rds.alipay.com/captcha.htm' self.getResultUrl = 'https://mobilegw.alipay.com/mgw.htm' self.operationType = { 'sendVerifyCode' : 'alipay.tradecsa.biz.blessingprod.wufu2021.sendVerifyCode' , 'outPrize' : 'alipay.tradecsa.biz.blessingprod.wufu2021.outPrize' } self.s = requests.Session() self.headers = { 'User-Agent' : 'Mozilla/5.0 (Linux; U; Android 10; zh-CN; MI 9 Build/QKQ1.190828.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 Quark/4.3.3.145 Mobile Safari/537.36 Edg/89.0.4389.6' , 'DNT' : '1' } def loads_jsonp (self, _jsonp ): try : return json.loads(re.match (".*?({.*}).*" , _jsonp, re.S).group(1 )) except : raise ValueError('Invalid Input' ) def getCaptcha (self, mobile, source ): digits = 32 hex = codecs.encode(os.urandom(digits), 'hex' ).decode() data = { 'appid' : "blessingprod_wufu_otp" , 'bizNo' : hex , 'mobile' : mobile, 'refer' : "" , 'scene' : "DO_NOTHING" , 'type' : "silence" , 'useragent' : "Mozilla/5.0 (Linux; U; Android 10; zh-CN; MI 8 UD Build/QKQ1.190828.002) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 Quark/4.3.3.145 Mobile Safari/537.36 Edg/89.0.4389.6" } self.s.options(self.getCaptchaUrl) try : r = self.s.post(self.getCaptchaUrl, json=data, headers=self.headers) rdsToken = json.loads(r.content)['data' ]['extra' ]['token' ] requestData = [{"mobile" : mobile, "source" : source, "rdsBizNo" : hex , "rdsToken" : rdsToken}] getResultData = { '_fli_online' : True , 'operationType' : self.operationType['sendVerifyCode' ], 'requestData' : str (requestData), '_' : int (round (time.time() * 1000 )), 'callback' : 'jsonp' + str (int (round (time.time() * 1000 ))) } re = self.s.get(self.getResultUrl, params=getResultData, headers=self.headers) re_json = self.loads_jsonp(re.text) if re_json['result' ]['success' ] == True : return {"code" : 1000 , "info" : f'成功获取验证码,请注意查收' } else : resultView = re_json['result' ]['resultView' ] return {"code" : 1001 , "info" : f'获取验证码失败,原因为{resultView} ' } except Exception as e: return {"code" : 1001 , "info" : f'获取验证码失败,原因为 {e} ' } def getResult (self, mobile, source, ackCode ): requestData = [ {"mobile" : mobile, "source" : source, "ackCode" : str (ackCode)}] getResultData = { '_fli_online' : True , 'operationType' : self.operationType['outPrize' ], 'requestData' : str (requestData), '_' : int (round (time.time() * 1000 )), 'callback' : 'jsonp' + str (int (round (time.time() * 1000 ))) } try : re = self.s.get(self.getResultUrl, params=getResultData, headers=self.headers) re_json = self.loads_jsonp(re.text) if re_json['result' ]['success' ] == True : return {"code" : 1000 , "info" : f'成功领取' } else : resultView = re_json['result' ]['resultView' ] return {"code" : 1001 , "info" : f'领取失败,原因为 {resultView} ' } except Exception as e: return {"code" : 1001 , "info" : f'领取失败,原因为 {e} ' } def getSiteNum (self ): path = os.path.join(self.dirPath, "site.json" ) with open (path, 'r' , encoding='utf8' )as fp: json_data = json.load(fp) return len (json_data['channelList' ]) def getSiteInfo (self, num ): path = os.path.join(self.dirPath, "site.json" ) with open (path, 'r' , encoding='utf8' )as fp: json_data = json.load(fp) length = len (json_data['channelList' ]) if num > length: print (f"站点的长度为{length} ,{num} 已经超出这个长度" ) return None return json_data['channelList' ][num-1 ] def getAllSiteInfo (self ): path = os.path.join(self.dirPath, "site.json" ) with open (path, 'r' , encoding='utf8' )as fp: json_data = json.load(fp) return json_data['channelList' ] def getSiteName (self, siteInfo ): return siteInfo['sourceList' ][0 ]['name' ] def getSiteSource (self, siteInfo ): return siteInfo['sourceList' ][0 ]['source' ] def addSuccessSite (self, siteInfo ): path = os.path.join(self.dirPath, "success.json" ) add = self.isSuccessSite(siteInfo) if add == False : with open (path, 'r+' , encoding='utf8' )as fp: json_data = json.load(fp) with open (path, 'w' , encoding='utf8' )as fp: json_data['channelList' ].append(siteInfo) fp.write(json.dumps(json_data, indent=4 )) def isSuccessSite (self, siteInfo ): path = os.path.join(self.dirPath, "success.json" ) with open (path, 'r+' , encoding='utf8' )as fp: json_data = json.load(fp) if siteInfo in list (json_data['channelList' ]): return True else : return False def main (path ): webRequests = WebRequests() webRequests.dirPath = path print (f"总共有{webRequests.getSiteNum()} 个站点可以领取福卡" ) for i in range (1 , webRequests.getSiteNum()+1 ): siteInfo = webRequests.getSiteInfo(i) siteName = webRequests.getSiteName(siteInfo) print (f"{i} :{siteName} " ) startSite = int (input ("您要从第几个站点开始向后领取?" )) mobile = input ("请输入您的手机号:" ) for i in range (startSite, webRequests.getSiteNum()+1 ): siteInfo = webRequests.getSiteInfo(i) siteName = webRequests.getSiteName(siteInfo) siteSource = webRequests.getSiteSource(siteInfo) if webRequests.isSuccessSite(siteInfo): print (f"{i} :{siteName} 已经成功领取,跳过" ) continue print (f"{i} :{siteName} 正在领取中" ) result = webRequests.getCaptcha(mobile, siteSource) print (result['info' ]) if result['code' ] == 1001 : if str (result['info' ]).find("验证码发送过频繁" ) != -1 : print ("验证码需等待60s后才能获取,正在等待.." ) time.sleep(60 ) result = webRequests.getCaptcha(mobile, siteSource) if str (result['info' ]).find("人气太旺啦,稍候再试试" ) != -1 : print ("您的手机号在近期已经获得了多次支付宝验证码,已被支付宝限制,24小时内无法再获得验证码,程序终止。" ) break elif str (result['info' ]).find("人气太旺啦,稍候再试试" ) == -1 and str (result['info' ]).find("验证码发送过频繁" ) == -1 : continue ackCode = input ("请输入验证码:" ) result = webRequests.getResult(mobile, siteSource, ackCode) print (result['info' ]) if result['code' ] == 1000 or result['info' ].find("已经领取过奖品" ) != -1 : webRequests.addSuccessSite(siteInfo) print ("验证码需等待60s后才能获取,正在等待.." ) time.sleep(60 ) input ("程序已结束,您可以关闭此程序了" ) if __name__ == '__main__' : path = os.path.dirname(os.path.realpath(sys.argv[0 ])) main(path)