前言
事情是这样的,掘金推出签到抽奖活动已经有一个月的时间了坚持打卡也有31天了。
如图目前已攒钻石数为 23834。(PS:测试以及手贱已经花费掉一部分钻石,原来应该有 30000 左右的钻石才对。)
那么 23834 个钻石,每次抽奖消耗 200个钻石,一共需要抽奖多少次?
答案很简单向下取整的结果为:119 次。如果每次都在页面上点击、等待,如此往复确实是个体力活。
PS:其实这么算的答案不准备,后续我会告诉你最终的答案是 153 次。
本着能写代码就不动手的原则,充分发挥程序员的优势,写个脚本替代我抽奖。
问题分析
一句话来说:掘金抽奖的实现的方式为前端动画 + 后端接口实现。所以真正的抽奖动作是调用接口,前端显示结果这么个逻辑。
话不多说谷歌浏览器打开开发者工具看看抽奖的时候调用的是那个接口。 emm....
如图,在已登陆的情况下调用的接口 https://api.juejin.cn/growth_api/v1/lottery/draw
代码实现
如上分析抽奖只需要调用抽奖接口,那就直接一个循环抽奖即可。运行方式打开开发这模式复制粘贴 cookie 中的 session_id 。粘贴入脚本,即可抽奖。
import requests
from requests import cookies
class Juejin(object):
# 掘金抽奖 URL
lottery_url = "https://api.juejin.cn/growth_api/v1/lottery/draw"
def __init__(self, driver_cookies=None, cookie_obj=None):
self.session = requests.session()
if driver_cookies:
for cookie in driver_cookies:
cookie_obj = requests.cookies.create_cookie(
domain=cookie.get("domain"),
name=cookie.get("name"),
value=cookie.get("value")
)
self.session.cookies.set_cookie(cookie_obj)
elif cookie_obj:
self.session.cookies.set_cookie(cookie_obj)
else:
raise Exception("Cookie is Blank")
def request(self, *args, **kwargs):
response = self.session.request(*args, **kwargs)
if response.status_code != 200:
raise Exception("Request error")
return response.json()
def draw_lottery(self):
return self.request("post", self.lottery_url)
def lottery():
# session id 自行设置
session_id = ""
cookie = requests.cookies.create_cookie(
domain=".juejin.cn",
name="sessionid",
value=session_id
)
juejin = Juejin(cookie_obj=cookie)
gift = {}
num = 1
while True:
result = juejin.draw_lottery()
if result.get("err_no") == 0:
lottery_name = result.get("data", {}).get("lottery_name")
if gift.get(lottery_name):
gift[lottery_name] += 1
else:
gift[lottery_name] = 1
print(f"第{num}次-抽奖结果为:{lottery_name}")
num += 1
else:
print(result.get("err_msg"))
break
print(f"总抽奖次数为:{num-1}")
print("最终抽奖结果为:")
for k, v in gift.items():
print(f"礼物:{k} ---- 个数:{v}")
程序运行结果展示(这里测试了是十次抽奖的结果):
第1次-抽奖结果为:66矿石
第2次-抽奖结果为:66矿石
第3次-抽奖结果为:Bug
第4次-抽奖结果为:66矿石
第5次-抽奖结果为:66矿石
第6次-抽奖结果为:Bug
第7次-抽奖结果为:66矿石
第8次-抽奖结果为:66矿石
第9次-抽奖结果为:Bug
第10次-抽奖结果为:66矿石
总抽奖次数为:{10}
礼物:66矿石 ---- 个数:7
礼物:Bug ---- 个数:3
抽奖结果展示
钻石总数:23834 抽奖次数:153 次
礼物-66矿石:106 礼物-Bug:47
综上所述:抽到 66矿石概率为 69.28%,抽到Bug的概率为 30.72%。
测试 10 次的抽奖概率与测试 153 次的抽奖概率基本符合。
划重点:此次试验证明掘金抽到 T恤、抱枕、Switch 的概率基本等于零,各位小伙伴不要心存幻想了。
思考与总结
- 如何用程序实现掘金类似的抽奖算法?
实现方式很简单通过自定义区间,从而控制抽奖结果。下面是简单的代码展示:
import random
# 66矿石与Bug的比重为 7:3 [0-700)的随机数为66矿石,[700-1000)为Bug
def draw():
temp = random.randint(0, 1000)
if temp < 700:
return "66矿石"
else:
return "Bug"
- 假设我有 23834 个钻石,请问最多可以抽奖多少次 ?
# 初始化钻石数
num = 23834
# 66钻石获取概率
rate = 0.7
# 抽奖次数
count = 0
while num >= 200:
t, r = divmod(num,200)
count += t
num = (66 * t + r) * rate
print(count)
# 153.0
JS 版本
评论区小伙伴希望有 JS 版本,特意更新前端的脚本。
以下代码可以在浏览器的 console 里面运行(仅测试 chrome)。
注意:直接复制粘贴到 console 里面就可以运行。一旦运行不可停止,甚用。。。
stime = Date.parse(new Date());
result = {}
count = 0
flag = true
while (flag) {
obj = await fetch('https://api.juejin.cn/growth_api/v1/lottery/draw', {
method: 'POST',
credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8'
}
})
.then(res => {
obj = res.json()
return obj
})
if (obj.err_no === 0) {
if (obj.data.lottery_name in result) {
result[obj.data.lottery_name] += 1
} else {
result[obj.data.lottery_name] = 1
}
count ++
} else {
flag = false
}
}
etime = Date.parse(new Date())
console.log("抽奖次数为:" + count)
console.log("抽奖花费时间为:", (etime - stime)/1000 + " 秒" )
console.log("抽奖奖品明细")
Object.keys(result).forEach(key => console.log("奖品:" + key + "--数量:" + result[key] ))
// 抽奖次数为:3
// 抽奖花费时间为: 1 秒
// 抽奖奖品明细
// 奖品:66矿石--数量:2