1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/55RK8C若失效,可用地址
阿里云:
服务器购买地址
https://t.aliyun.com/U/55RK8C若失效,可用地址
https://www.aliyun.com/daily-act/ecs/activity_selection?source=5176.29345612&userCode=49hts92d腾讯云:
https://curl.qcloud.com/wJpWmSfU若失效,可用地址
https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=ad201ee2ef3b771157f72ee5464b1fea&from=console华为云
https://activity.huaweicloud.com/cps.html?fromacct=64b5cf7cc11b4840bb4ed2ea0b2f4468&utm_source=V1g3MDY4NTY=&utm_medium=cps&utm_campaign=2019052.部署教程
3.代码如下
import htmlimport osimport reimport sysimport timefrom io import BytesIOimport tracebackimport ddddocrimport requestsfrom PIL import Imagefrom selenium import webdriverfrom selenium.common import NoSuchElementExceptionfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.common.action_chains import ActionChainsfrom selenium.webdriver.common.by import Bydef send(push_token, title, text):requests.get(f"https://www.pushplus.plus/send?token={push_token}&title={title}&content={text}&template=html",proxies=proxies)def hide_user(user):user = str(user)if re.match(r'\d{11}', user): # 匹配11位纯数字return user[:3] + '****' + user[7:]elif re.match(r'\S+@\S+\.\S+', user): # 匹配邮箱格式at_index = user.find('@')return user[:2] + '*' * (at_index - 2) + user[at_index:]else:return userdef captcha(element):# 通过element获取image_urlpage_source = element.parent.page_sourcep = 'background-image: url\("(.*?)"\);'re_url_result = re.findall(p, page_source)p2 = 'background-position: -(.*?)px -(.*?)px; background-size: (.*?)px (.*?)px; width: (.*?)px; height: (.*?)px; left: (.*?)px; top: (.*?)px;'re_pix_result = re.findall(p2, page_source)p3 = 'style="width: (.*?)px; height: (.*?)px; position:'re_pix2_result = re.findall(p3, page_source)background_image_url = ''block_image_url = ''for r in re_url_result:temp_url = html.unescape(r)if 'img_index=0' in temp_url:block_image_url = temp_urlelif 'img_index=1' in temp_url:background_image_url = temp_urlif len(re_pix_result) != 0:if len(re_pix_result[0]) == 8:pos_x, pos_y, pix_x, pix_y, width, height, left, top = map(int, map(float, re_pix_result[0]))else:raise Exception(f'获取验证码大小失败, re_pix_result[0]: {re_pix_result[0]}')else:raise Exception(f'获取验证码大小失败, re_pix_result: {re_pix_result}')if len(re_pix2_result) != 0:if len(re_pix2_result[0]) == 2:pic_pix_x, pic_pix_y = map(int, map(float, re_pix2_result[0]))else:raise Exception('获取验证码大小失败')else:raise Exception('获取验证码大小失败')if background_image_url == '' or block_image_url == '':raise Exception('获取验证码链接失败')# 获取网络图片# (response.content)block_response = requests.get(block_image_url, proxies=proxies)img_block = Image.open(BytesIO(block_response.content))background_response = requests.get(background_image_url, proxies=proxies)img_bg = Image.open(BytesIO(background_response.content))# pos_x, pos_y, pix_x, pix_y, width, height, left, top# (*70, *247, *345, *313, *60, *60, 25, 63)img_block = img_block.resize((pix_x, pix_y))img_block = img_block.crop((pos_x, pos_y, pos_x + width, pos_y + height))img_bg = img_bg.resize((pic_pix_x, pic_pix_y))img_block_bytes = BytesIO()img_block.save(img_block_bytes, format='PNG')img_block_bytes = img_block_bytes.getvalue()img_bg_bytes = BytesIO()img_bg.save(img_bg_bytes, format='PNG')img_bg_bytes = img_bg_bytes.getvalue()det = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)res = det.slide_match(img_block_bytes, img_bg_bytes, simple_target=True)return ((res['target'][2] + res['target'][0]) // 2) - left - 30def sign_wss(user, password, token, msgs: list, show_user_string: str):chrome_options = Options()# 浏览器不提供可视化页面. linux下如果系统不支持可视化不加这条会启动失败if not debug_flag:chrome_options.add_argument('--headless')chrome_options.add_argument('disable-infobars') # 取消显示信息栏(Chrome 正在受到自动软件的控制)chrome_options.add_argument("--disable-blink-features=AutomationControlled") # 禁用 Chrome 的自动化控制检测# chrome_options.binary_location = "C:\Program Files\Google\Chrome\Application\chrome.exe"chrome_options.add_argument('--no-sandbox')chrome_options.add_argument('--disable-dev-shm-usage')b = webdriver.Chrome(options=chrome_options)b.get('https://www.wenshushu.cn/signin')time.sleep(2)b.refresh()b.implicitly_wait(10)print("正在登陆...")b.find_element(by=By.XPATH, value='//*[contains(text(),"密码登录")]').click()time.sleep(1)b.find_element(by=By.XPATH, value='//*[@placeholder="手机号 / 邮箱"]').send_keys(user)time.sleep(1)b.find_element(by=By.XPATH, value='//*[@placeholder="密码"]').send_keys(password)time.sleep(1)b.find_element(by=By.XPATH, value='//*[@type="submit"]').click()time.sleep(1)b.refresh()time.sleep(1)try:print("关闭广告和新手任务中...")b.find_element(by=By.CLASS_NAME, value="btn_close").click()time.sleep(1)except NoSuchElementException:print("无广告或其他弹窗")passb.implicitly_wait(10)print(f"{show_user_string} 正在打卡...")# headless 模式下icondaka直接click()好像有异常,需要换个玩法# b.find_element(by=By.CLASS_NAME, value="icondaka").click()ele = b.find_element(by=By.CLASS_NAME, value="icondaka")ActionChains(b).move_to_element(ele).click(ele).perform()b.implicitly_wait(10)time.sleep(5)# 获取页面源码htm = b.page_sourcetry:if '今日已打卡' not in htm or '打卡成功' in htm:# if b.find_element(by=By.CLASS_NAME, value='tc-fg-item'):# print('发现验证码', b.page_source)# else:# print(b.page_source)print('疑似存在验证码,正在处理验证码')iframe = b.find_element(by=By.XPATH, value="//iframe[@id='tcaptcha_iframe_dy']")b.switch_to.frame(iframe)# 获取验证码滑块和背景图element_block = b.find_element(by=By.CLASS_NAME, value='tc-fg-item')# element_background_image = b.find_element(by=By.CLASS_NAME, value='tc-bg-img')iv = captcha(element_block)element_slider = b.find_element(by=By.CLASS_NAME, value='tc-slider-normal')# 模拟鼠标点击并拖动滑块action = ActionChains(b)action.click_and_hold(element_slider).move_by_offset(iv, 0).release().perform()print('验证码处理完成')except Exception as e:# print(b.page_source)print(traceback.format_exc())time.sleep(5)b.implicitly_wait(10)b.switch_to.default_content()htm = b.page_sourceif '今日已打卡' in htm or 'Signed in today' in htm or '' in htm:htm = htm.replace('\n', '')names = re.compile('class="m-title5">(.*?)</div>').findall(htm)values = re.compile('class="re-num m-text9">(.*?)</div>').findall(htm)result = ''for i in range(len(names)):if names[i] == '手气不好':continueresult += names[i] + ':' + values[i] + '</br>'print('%s:%s' % (names[i], values[i].strip()))msg = (show_user_string + '文叔叔签到成功,', result)else:msg = (show_user_string + '文叔叔签到失败,', htm)print(htm)msgs.append(msg)b.close()if __name__ == '__main__':sys.stdout.reconfigure(encoding='UTF-8')proxies = {'http': None,'https': None}users = os.environ.get('USER')password = os.environ.get('PASSWORD')push_token = os.environ.get('PUSH_MESSAGE')show_user = os.environ.get('SHOW_USER') # 0: 完全不显示(默认),1:显示部分(例如:131****1234),2:完全显示debug_flag = os.environ.get('DEBUG')if show_user is None:show_user = 0if users is None:exit()if password is None:exit()if push_token is None:push_token = ""msgs = []if debug_flag is None:debug_flag = Falseelse:debug_flag = Truefor user in users.split(';'):show_user_string = ''if str(show_user) == '1':show_user_string = hide_user(user)elif str(show_user) == '2':show_user_string = userretry = 0while retry < 5:success = Truetry:sign_wss(user, password, push_token, msgs, show_user_string)except Exception as e:print("签到{user}账户时出现异常:{error_message}".format(user=show_user_string, error_message=traceback.format_exc()))print(f"已重试次数: {retry + 1}")success = Falsefinally:retry = retry + 1if success:breakpush_text = ''for msg in msgs:push_text = push_text + msg[0] + msg[1]send(push_token, '文叔叔签到结果', push_text)
解析
这是一个 文叔叔(wenshushu.cn)自动签到/打卡脚本,支持:
从环境变量读取账号、密码(可多账号)
使用 Selenium + Chrome(可无头) 登录文叔叔
点击"打卡"按钮完成签到
如果出现 腾讯滑块验证码(tcaptcha),就用
ddddocr做滑块缺口匹配,计算拖动距离并拖动完成验证解析签到结果(奖励名称/数值)汇总成 HTML 文本
通过 PushPlus 推送签到结果通知
每个账号支持最多 5 次重试,提高成功率
主要方法
1) send(push_token, title, text)
推送通知到 PushPlus:
通过 GET 请求调用 PushPlus 的 send 接口
template=html,所以传入的text支持 HTML(如</br>换行)
2) hide_user(user)
账号脱敏展示(用于日志/通知):
11 位手机号:
131****1234邮箱:保留前两位 +
****+@xxx.com其他格式:原样返回
3) captcha(element)
这是脚本里最关键的"验证码识别"方法:计算滑块应该拖动的水平距离。
它做的事情可以概括成 4 步:
从页面源码解析验证码图片 URL
img_index=0:滑块小图(缺口块)img_index=1:背景大图从
background-image: url("...")中提取两张图:解析验证码布局参数
background-position / background-size / width / height / left / top等,用于裁剪与缩放这些参数决定"从滑块原图裁哪一块"以及"背景图缩放到什么尺寸"
下载两张图片并做处理
requests.get()拉取滑块图与背景图PIL.Image缩放、裁剪成可用于识别的 PNG bytes用 ddddocr 做滑块匹配
DdddOcr(det=False, ocr=False).slide_match(...)得到缺口位置最后计算出拖动距离(返回一个 x 偏移量),供 Selenium 拖拽使用
总结一句:
captcha()的输出就是"滑块要拖多少像素"。
4) sign_wss(user, password, token, msgs: list, show_user_string: str)
单个账号签到主流程(核心方法):
启动 Chrome(根据
debug_flag决定是否 headless)打开
https://www.wenshushu.cn/signin切换到"密码登录",输入账号密码,提交登录
关闭可能出现的广告弹窗(找不到就忽略)
点击 "打卡" 按钮(用 ActionChains 避免 headless 下 click 不稳定)
判断页面是否已打卡成功:
找到滑块块元素
.tc-fg-item→ 调captcha()算距离找到滑块按钮
.tc-slider-normal→ ActionChains 拖动如果疑似触发验证码:进入
iframe#tcaptcha_iframe_dy再次回到主页面,解析签到结果:
用正则提取奖励项
m-title5与数值re-num m-text9拼装 HTML 并写入
msgs列表(供最后汇总推送)关闭浏览器
脚本入口(__main__)
从环境变量读取:
USER:账号列表(;分隔多账号)PASSWORD:密码PUSH_MESSAGE:PushPlus tokenSHOW_USER:0不显示/1脱敏/2全显示DEBUG:是否开启可视化(不加 headless)对每个账号最多重试 5 次执行
sign_wss()汇总所有账号的
msgs生成一段 HTML调
send()推送《文叔叔签到结果》
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论