def __init__(self): INFO("欢迎执行JD全自动退会程序,如有使用问题请加TG群https://t.me/jdMemberCloseAccount进行讨论") # 初始化基础配置 self.config = get_config() self.selenium_cfg = get_config()["selenium"] self.shop_cfg = get_config()["shop"] self.sms_captcha_cfg = get_config()["sms_captcha"] self.image_captcha_cfg = get_config()["image_captcha"] self.ocr_cfg = self.sms_captcha_cfg["ocr"] # 初始化selenium配置 self.browser = get_browser(self.config) self.wait = WebDriverWait(self.browser, self.selenium_cfg["timeout"]) self.wait_check = WebDriverWait(self.browser, self.selenium_cfg["check_wait"]) # 初始化短信验证码配置 if not self.sms_captcha_cfg["is_ocr"]: if not self.sms_captcha_cfg["jd_wstool"]: from utils.listener import SmsSocket self.sms = SmsSocket() elif self.sms_captcha_cfg["is_ocr"]: if self.ocr_cfg["type"] == "": WARN("当前已开启OCR模式,但是并未选择OCR类型,请在config.yaml补充ocr.type") sys.exit(1) if self.ocr_cfg["type"] == "baidu": from captcha.baidu_ocr import BaiduOCR self.baidu_ocr = BaiduOCR(self.ocr_cfg) elif self.ocr_cfg["type"] == "aliyun": from captcha.aliyun_ocr import AliYunOCR self.aliyun_ocr = AliYunOCR(self.ocr_cfg) elif self.ocr_cfg["type"] == "easyocr": from captcha.easy_ocr import EasyOCR self.easy_ocr = EasyOCR() # 初始化图形验证码配置 if self.image_captcha_cfg["type"] == "cjy": self.cjy = ChaoJiYing(self.image_captcha_cfg) elif self.image_captcha_cfg["type"] == "tj": self.tj = TuJian(self.image_captcha_cfg) elif self.image_captcha_cfg["type"] == "local": pass elif self.image_captcha_cfg["type"] == "yolov4": self.JDyolo = JDyolocaptcha(self.image_captcha_cfg) else: WARN("请在config.yaml中补充image_captcha.type") sys.exit(1)
def __init__(self): INFO("欢迎执行JD全自动退会程序,如有使用问题请加TG群https://t.me/jdMemberCloseAccount进行讨论") INFO("↓ " * 30) # 检查版本 INFO("开始检查项目是否有更新") check_version(logger) # 检查配置 INFO("开始检查项目配置完整性") verify_configuration(logger) # 初始化基础配置 self.config = get_config() self.selenium_cfg = get_config()["selenium"] self.shop_cfg = get_config()["shop"] self.sms_captcha_cfg = get_config()["sms_captcha"] self.image_captcha_cfg = get_config()["image_captcha"] self.ocr_cfg = self.sms_captcha_cfg["ocr"] self.debug = self.config["debug"] # 初始化selenium配置 self.browser = get_browser(self.config) self.wait = WebDriverWait(self.browser, self.selenium_cfg["timeout"]) self.wait_check = WebDriverWait(self.browser, self.selenium_cfg["check_wait"]) # 初始化短信验证码配置 if not self.sms_captcha_cfg["is_ocr"]: if not self.sms_captcha_cfg["jd_wstool"]: from utils.listener import SmsSocket self.sms = SmsSocket() elif self.sms_captcha_cfg["is_ocr"]: self.ocr_type = self.ocr_cfg["type"] if self.ocr_type == "": WARN("当前已开启OCR模式,但是并未选择OCR类型,请在config.yaml补充ocr.type") sys.exit(1) if self.ocr_type == "baidu": from captcha.baidu_ocr import BaiduOCR self.baidu_ocr = BaiduOCR(self.ocr_cfg, self.debug) elif self.ocr_type == "aliyun": from captcha.aliyun_ocr import AliYunOCR self.aliyun_ocr = AliYunOCR(self.ocr_cfg, self.debug) elif self.ocr_type == "easyocr": from captcha.easy_ocr import EasyOCR self.easy_ocr = EasyOCR(self.debug) elif self.ocr_type == "baidu_fanyi": from captcha.baidu_fanyi import BaiduFanYi self.baidu_fanyi = BaiduFanYi(self.ocr_cfg, self.debug) self.ws_conn_url = self.sms_captcha_cfg["ws_conn_url"] self.ws_timeout = self.sms_captcha_cfg["ws_timeout"] # 初始化图形验证码配置 if self.image_captcha_cfg["type"] == "cjy": self.cjy = ChaoJiYing(self.image_captcha_cfg) elif self.image_captcha_cfg["type"] == "tj": self.tj = TuJian(self.image_captcha_cfg) elif self.image_captcha_cfg["type"] == "local": pass elif self.image_captcha_cfg["type"] == "yolov4": self.JDyolo = JDyolocaptcha(self.image_captcha_cfg) else: WARN("请在config.yaml中补充image_captcha.type") sys.exit(1) # 初始化店铺变量 # 错误店铺页面数量 self.wrong_store_page_count = 0 # 黑名单店铺缓存 self.black_list_shops = [] # 会员关闭最大数量 self.member_close_max_number = self.shop_cfg["member_close_max_number"] # 注销成功店铺数量 self.member_close_count = 0 # 需要跳过的店铺 self.need_skip_shops = [] # 指定注销的店铺 self.specify_shops = [] # 页面失效打不开的店铺 self.failure_store = [] # 云端数据执行状态 self.add_remote_shop_data = self.shop_cfg["add_remote_shop_data"]
class JDMemberCloseAccount(object): """ 京东全自动退店铺会员 """ def __init__(self): INFO("欢迎执行JD全自动退会程序,如有使用问题请加TG群https://t.me/jdMemberCloseAccount进行讨论") INFO("↓ " * 30) # 检查版本 INFO("开始检查项目是否有更新") check_version(logger) # 检查配置 INFO("开始检查项目配置完整性") verify_configuration(logger) # 初始化基础配置 self.config = get_config() self.selenium_cfg = get_config()["selenium"] self.shop_cfg = get_config()["shop"] self.sms_captcha_cfg = get_config()["sms_captcha"] self.image_captcha_cfg = get_config()["image_captcha"] self.ocr_cfg = self.sms_captcha_cfg["ocr"] self.debug = self.config["debug"] # 初始化selenium配置 self.browser = get_browser(self.config) self.wait = WebDriverWait(self.browser, self.selenium_cfg["timeout"]) self.wait_check = WebDriverWait(self.browser, self.selenium_cfg["check_wait"]) # 初始化短信验证码配置 if not self.sms_captcha_cfg["is_ocr"]: if not self.sms_captcha_cfg["jd_wstool"]: from utils.listener import SmsSocket self.sms = SmsSocket() elif self.sms_captcha_cfg["is_ocr"]: self.ocr_type = self.ocr_cfg["type"] if self.ocr_type == "": WARN("当前已开启OCR模式,但是并未选择OCR类型,请在config.yaml补充ocr.type") sys.exit(1) if self.ocr_type == "baidu": from captcha.baidu_ocr import BaiduOCR self.baidu_ocr = BaiduOCR(self.ocr_cfg, self.debug) elif self.ocr_type == "aliyun": from captcha.aliyun_ocr import AliYunOCR self.aliyun_ocr = AliYunOCR(self.ocr_cfg, self.debug) elif self.ocr_type == "easyocr": from captcha.easy_ocr import EasyOCR self.easy_ocr = EasyOCR(self.debug) elif self.ocr_type == "baidu_fanyi": from captcha.baidu_fanyi import BaiduFanYi self.baidu_fanyi = BaiduFanYi(self.ocr_cfg, self.debug) self.ws_conn_url = self.sms_captcha_cfg["ws_conn_url"] self.ws_timeout = self.sms_captcha_cfg["ws_timeout"] # 初始化图形验证码配置 if self.image_captcha_cfg["type"] == "cjy": self.cjy = ChaoJiYing(self.image_captcha_cfg) elif self.image_captcha_cfg["type"] == "tj": self.tj = TuJian(self.image_captcha_cfg) elif self.image_captcha_cfg["type"] == "local": pass elif self.image_captcha_cfg["type"] == "yolov4": self.JDyolo = JDyolocaptcha(self.image_captcha_cfg) else: WARN("请在config.yaml中补充image_captcha.type") sys.exit(1) # 初始化店铺变量 # 错误店铺页面数量 self.wrong_store_page_count = 0 # 黑名单店铺缓存 self.black_list_shops = [] # 会员关闭最大数量 self.member_close_max_number = self.shop_cfg["member_close_max_number"] # 注销成功店铺数量 self.member_close_count = 0 # 需要跳过的店铺 self.need_skip_shops = [] # 指定注销的店铺 self.specify_shops = [] # 页面失效打不开的店铺 self.failure_store = [] # 云端数据执行状态 self.add_remote_shop_data = self.shop_cfg["add_remote_shop_data"] def get_code_pic(self, name='code_pic.png'): """ 获取验证码图像 :param name: :return: """ # 确定验证码的左上角和右下角坐标 code_img = self.wait.until( EC.presence_of_element_located( (By.XPATH, "//div[@id='captcha_modal']//div"))) location = code_img.location size = code_img.size _range = (int(location['x']), int(location['y']), (int(location['x']) + int(size['width'])), (int(location['y']) + int(size['height']))) # 将整个页面截图 self.browser.save_screenshot(name) # 获取浏览器大小 window_size = self.wait.until( EC.presence_of_element_located((By.XPATH, "//div[@id='root']"))) width, height = window_size.size['width'], window_size.size['height'] # 图片根据窗口大小resize,避免高分辨率影响坐标 i = Image.open(name) new_picture = i.resize((width, height)) new_picture.save(name) # 剪裁图形验证码区域 code_pic = new_picture.crop(_range) code_pic.save(name) time.sleep(2) return code_img def get_shop_cards(self): """ 获取加入店铺列表 :return: 返回店铺列表 """ url = "https://api.m.jd.com/client.action?functionId=getWalletReceivedCardList_New&clientVersion=10.2.0&bui" \ "ld=90900&client=android&partner=xiaomi001&oaid=e02a70327f315862&eid=eidA24e181233bsdmxzC3hIpQF2nJhWG" \ "GLb/1JscxFOzBjvkqrXbFQyAXZmstKs0K6bUwkQ0D3s1/7MzLZ7JDdhztfcdZur9xPTxU1ahqtHWYb54/yNK&sdkVersion=30&l" \ "ang=zh_CN&harmonyOs=0&networkType=wifi&uts=0f31TVRjBSto8DL4K0ee85ZRt0rmw128U%2B6PiicSyj%2Bq9U2tA0gWy" \ "YjW29QZLyq5ebqz%2BLY0DD03RA0Pz%2B8PPqt%2FzmMyvdLqzrHQ4H1TLZ3qP0jDbUcDGjUcS0cJFuP%2F4Wb8%2Bi8BajbDrNw" \ "9yU5V6OumYiQALp8Jxh82E9QhngZT7ybL1zuXSzO%2BLvCgdg6BockZnd9hKMTFq4pY4oMMsg%3D%3D&uemps=0-0&ext=%7B%22" \ "prstate%22%3A%220%22%7D&ef=1&ep=%7B%22hdid%22%3A%22JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw%3D%22" \ "%2C%22ts%22%3A1634992423397%2C%22ridx%22%3A-1%2C%22cipher%22%3A%7B%22area%22%3A%22CJDpCJKmCP80CNG4EP" \ "81DNG0Cq%3D%3D%22%2C%22d_model%22%3A%22JJSmCNdAC1DN%22%2C%22wifiBssid%22%3A%22YzYmEWU5CzO1CJS0CzdrEN" \ "qmDwPvCNZsENZuCzu3D2S%3D%22%2C%22osVersion%22%3A%22CJO%3D%22%2C%22d_brand%22%3A%22WQvrb21f%22%2C%22s" \ "creen%22%3A%22CtS2DsenCNqm%22%2C%22uuid%22%3A%22C2HrYtvrCJZsZNu1ZJC4YG%3D%3D%22%2C%22aid%22%3A%22C2H" \ "rYtvrCJZsZNu1ZJC4YG%3D%3D%22%2C%22openudid%22%3A%22C2HrYtvrCJZsZNu1ZJC4YG%3D%3D%22%7D%2C%22ciphertyp" \ "e%22%3A5%2C%22version%22%3A%221.2.0%22%2C%22appname%22%3A%22com.jingdong.app.mall%22%7D&" page_num = 8 var_name = locals() var_name[ "sign_page1"] = "st=1634992661020&sign=83a87e33d52a73c3abf01217af277d7c&sv=101" var_name[ "sign_page2"] = "st=1634992678131&sign=4da2fffa2375fd0f6f261ac70fcaad00&sv=102" var_name[ "sign_page3"] = "st=1634992682728&sign=83815a83dedef47c5f908269aca3926c&sv=100" var_name[ "sign_page4"] = "st=1634992686855&sign=f781c2707f70c8ffc98b28e091a56542&sv=121" var_name[ "sign_page5"] = "st=1634992688025&sign=15680ac47fb873561fc9f38ff2411a5e&sv=122" var_name[ "sign_page6"] = "st=1635177469421&sign=f9180d4e3989a78d07bf2dd4a276508c&sv=102" var_name[ "sign_page7"] = "st=1635177470330&sign=de73d5da876afa061c61068d987c5f40&sv=100" var_name[ "sign_page8"] = "st=1635177471053&sign=3305e1cf5833274f46169b4b8a811f4e&sv=100" headers = { 'Host': 'api.m.jd.com', 'cookie': self.config["cookie"], 'charset': 'UTF-8', 'accept-encoding': 'br,gzip,deflate', 'user-agent': self.config["user-agent"][1], 'cache-control': 'no-cache', 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'content-length': '60' } card_list = [] urllib3.disable_warnings() for i in range(1, page_num + 1): body = "body=%7B%22pageNum%22%3A{}%2C%22pageSize%22%3A10%2C%22v%22%3A%225.0%22%2C%22" \ "version%22%3A1580659200%7D&".format(str(i)) resp = requests.request("POST", url + var_name.get("sign_page" + str(i)), headers=headers, data=body, verify=False) if resp.content: ret = json.loads(resp.text) if ret["code"] == "0": if ret["message"] == "用户未登录": WARN( "config.yaml中的cookie值有误,请确保pt_key和pt_pin都存在,如都存在请检查cookie是否失效" ) sys.exit(1) elif ret["message"] == "响应成功": if len(ret["result"]["cardList"]) == 0: break card_list.extend(ret["result"]["cardList"]) else: ERROR(ret) break else: ERROR("获取卡包列表接口返回None,请检查网络") break return card_list def refresh_cache(self): """ 利用待领卡接口刷新卡包列表缓存 :return: """ url = "https://api.m.jd.com/client.action?functionId=getWalletUnreceivedCardList_New&clientVersion=10.2.0&bu" \ "ild=90900&client=android&partner=xiaomi001&oaid=e02a70327f315862&eid=eidA24e181233bsdmxzC3hIpQF2nJhWG" \ "GLb/1JscxFOzBjvkqrXbFQyAXZmstKs0K6bUwkQ0D3s1/7MzLZ7JDdhztfcdZur9xPTxU1ahqtHWYb54/yNK&sdkVersion=30&la" \ "ng=zh_CN&harmonyOs=0&networkType=wifi&uts=0f31TVRjBSto8DL4K0ee85ZRt0rmw1282OyO9rnqi1tOb%2F8sm56Ob%2B2" \ "cXRa7tHz7%2Brbnij%2FrCELTlgkV7kZeS2bYJHn1VmbuhkPZ%2FEdKSyksnAupmrbGMSyCNb4zYaLOIo4Ctbtqd6Z9k3de%2BrTH" \ "Uc0aeSTgZ%2FZ47Z%2Fe5b%2F%2Bt24iEsGelW3oJAs9OMvTYGqyA5dS%2BPKX5oHybFC4iYH2FA%3D%3D&uemps=0-0&ext=%7B%" \ "22prstate%22%3A%220%22%7D&ef=1&ep=%7B%22hdid%22%3A%22JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw%3D%2" \ "2%2C%22ts%22%3A1635004927990%2C%22ridx%22%3A-1%2C%22cipher%22%3A%7B%22area%22%3A%22CJDpCJKmCP80CNG4EP" \ "81DNG0Cq%3D%3D%22%2C%22d_model%22%3A%22JJSmCNdAC1DN%22%2C%22wifiBssid%22%3A%22YzYmEWU5CzO1CJS0CzdrENq" \ "mDwPvCNZsENZuCzu3D2S%3D%22%2C%22osVersion%22%3A%22CJO%3D%22%2C%22d_brand%22%3A%22WQvrb21f%22%2C%22scr" \ "een%22%3A%22CtS2DsenCNqm%22%2C%22uuid%22%3A%22C2HrYtvrCJZsZNu1ZJC4YG%3D%3D%22%2C%22aid%22%3A%22C2HrYt" \ "vrCJZsZNu1ZJC4YG%3D%3D%22%2C%22openudid%22%3A%22C2HrYtvrCJZsZNu1ZJC4YG%3D%3D%22%7D%2C%22ciphertype%22" \ "%3A5%2C%22version%22%3A%221.2.0%22%2C%22appname%22%3A%22com.jingdong.app.mall%22%7D&st=1635004961154&" \ "sign=398298f4fbaf3e8218626e5c447c73f6&sv=100" body = "body=%7B%22pageNum%22%3A1%2C%22pageSize%22%3A10%2C%22v%22%3A%225.0%22%2C%22version%22%3A1580659200%7D&" headers = { 'Host': 'api.m.jd.com', 'cookie': self.config["cookie"], 'charset': 'UTF-8', 'accept-encoding': 'br,gzip,deflate', 'user-agent': self.config["user-agent"][1], 'cache-control': 'no-cache', 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'content-length': '102' } urllib3.disable_warnings() resp = requests.request("POST", url, headers=headers, data=body, verify=False) ret = json.loads(resp.text) if ret["code"] == "0": return True else: ERROR(ret) return False def close_member(self, card, flag=0): """ 进行具体店铺注销页面的注销操作 card: 具体店铺数据对象 flag: 乱码页面挂载状态 """ # 页面链接 page_link = "https://shopmember.m.jd.com/member/memberCloseAccount?venderId=" + card[ "brandId"] # 检查手机尾号是否正确 phone = self.wait.until( EC.presence_of_element_located( (By.XPATH, "//div[text()='手机号']/following-sibling::div[1]"))).text if "*" not in phone[:4]: if flag == 0: if "AARm5gnNkBWoE8tQA5n" in phone: INFO("当前店铺绑定手机号为%s,明显为无效号码,挂载到新标签页" % phone) self.browser.execute_script( 'window.open("{}")'.format(page_link)) self.browser.switch_to.window( self.browser.current_window_handle) self.wrong_store_page_count += 1 else: INFO("当前店铺绑定手机号为%s,明显为无效号码,程序加入黑名单后自动跳过" % phone) else: INFO("当前店铺绑定手机号为%s,明显为无效号码,程序加入黑名单后自动跳过" % phone) # 加入黑名单缓存 if card not in self.black_list_shops: self.record_black_list(card) return False elif self.shop_cfg['phone_tail_number'] and phone[ -4:] not in self.shop_cfg['phone_tail_number']: INFO("当前店铺绑定手机号为%s,尾号≠配置中设置的尾号,程序加入黑名单后自动跳过" % phone) # 加入黑名单缓存 if card not in self.black_list_shops: self.record_black_list(card) return False # 发送短信验证码 self.wait.until( EC.presence_of_element_located( (By.XPATH, "//button[text()='发送验证码']")), "发送短信验证码超时 " + card["brandName"]).click() # 判断是否发送成功,发送失败为黑店,直接跳过 self.wait_check.until( EC.presence_of_element_located((By.XPATH, "//div[text()='发送成功']")), f'发送失败,黑店【{card["brandName"]}】跳过') # 验证码 sms_code = "" # ocr识别投屏验证码 if self.sms_captcha_cfg["is_ocr"]: if len(self.ocr_cfg["ocr_range"]) != 4: WARN("请在config.yaml中配置 ocr_range") sys.exit(1) else: _range = (self.ocr_cfg["ocr_range"]) ocr_delay_time = self.ocr_cfg["ocr_delay_time"] INFO("刚发短信,%d秒后识别验证码" % ocr_delay_time) time.sleep(ocr_delay_time) if self.ocr_type == "baidu": INFO("开始调用百度OCR识别") sms_code = self.baidu_ocr.baidu_ocr(_range, ocr_delay_time) elif self.ocr_type == "aliyun": INFO("开始调用阿里云OCR识别") sms_code = self.aliyun_ocr.aliyun_ocr( _range, ocr_delay_time) elif self.ocr_type == "easyocr": INFO("开始调用EasyOCR识别") sms_code = self.easy_ocr.easy_ocr(_range, ocr_delay_time) elif self.ocr_type == "baidu_fanyi": INFO("开始调用百度翻译识别") sms_code = self.baidu_fanyi.baidu_fanyi( _range, ocr_delay_time) INFO("验证码识别结果为:", sms_code) else: try: if self.sms_captcha_cfg["jd_wstool"]: recv = asyncio.get_event_loop().run_until_complete( ws_conn(self.ws_conn_url, self.ws_timeout)) else: recv = self.sms.get_code() if recv == "": INFO("等待websocket推送短信验证码超时,即将跳过", card["brandName"]) return False else: sms_code = json.loads(recv)["sms_code"] INFO("验证码监听结果为:", sms_code) except OSError: WARN( "WebSocket监听时发生了问题,请检查是否开启外部jd_wstool工具或者使用内置的jd_wstool或者5201端口是否开放" ) sys.exit(1) except Exception as e: WARN(e.__class__, e.args) sys.exit(1) # 输入短信验证码 self.wait.until( EC.presence_of_element_located( (By.XPATH, "//input[@type='number']")), "输入短信验证码超时 " + card["brandName"]).send_keys(sms_code) time.sleep(1) # 点击注销按钮 self.wait.until( EC.presence_of_element_located((By.XPATH, "//div[text()='注销会员']")), "点击注销按钮超时 " + card["brandName"]).click() # 利用打码平台识别图形验证码并模拟点击 def auto_identify_captcha_click(): # 分割图形验证码 code_img = self.get_code_pic() img = open('code_pic.png', 'rb').read() pic_str, pic_id = "", "" if self.image_captcha_cfg["type"] == "cjy": # 调用超级鹰API接口识别点触验证码 INFO("开始调用超级鹰识别验证码") resp = self.cjy.post_pic(img, self.image_captcha_cfg["cjy_kind"]) if "pic_str" in resp and resp["pic_str"] == "": INFO("超级鹰验证失败,原因为:", resp["err_str"]) else: pic_str = resp["pic_str"] pic_id = resp["pic_id"] elif self.image_captcha_cfg["type"] == "tj": # 调用图鉴API接口识别点触验证码 INFO("开始调用图鉴识别验证码") resp = self.tj.post_pic(img, self.image_captcha_cfg["tj_type_id"]) pic_str = resp["result"] pic_id = resp["id"] # 处理要点击的坐标 all_list = [] xy_list = [] x = int(pic_str.split(',')[0]) xy_list.append(x) y = int(pic_str.split(',')[1]) xy_list.append(y) all_list.append(xy_list) # 循环遍历点击图片 for i in all_list: x = i[0] y = i[1] ActionChains(self.browser).move_to_element_with_offset( code_img, x, y).click().perform() time.sleep(1) # 图形验证码坐标点击错误尝试重试 # noinspection PyBroadException try: WebDriverWait(self.browser, 3).until( EC.presence_of_element_located( (By.XPATH, "//p[text()='验证失败,请重新验证']"))) INFO("验证码坐标识别出错,将上报平台处理") # 上报错误的图片到平台 if self.image_captcha_cfg["type"] == "cjy": self.cjy.report_error(pic_id) elif self.image_captcha_cfg["type"] == "tj": self.tj.report_error(pic_id) return False except Exception as _: return True # 本地识别图形验证码并模拟点击 def local_auto_identify_captcha_click(): for _ in range(4): cpc_img = self.wait.until( EC.presence_of_element_located( (By.XPATH, '//*[@id="cpc_img"]'))) zoom = cpc_img.size['height'] / 170 cpc_img_path_base64 = self.wait.until( EC.presence_of_element_located( (By.XPATH, '//*[@id="cpc_img"]'))).get_attribute('src').replace( "data:image/jpeg;base64,", "") pcp_show_picture_path_base64 = self.wait.until( EC.presence_of_element_located(( By.XPATH, '//*[@class="pcp_showPicture"]'))).get_attribute('src') # 正在识别验证码 if self.image_captcha_cfg["type"] == "local": INFO("正在通过本地引擎识别") res = JDcaptcha_base64(cpc_img_path_base64, pcp_show_picture_path_base64) else: INFO("正在通过深度学习引擎识别") res = self.JDyolo.JDyolo(cpc_img_path_base64, pcp_show_picture_path_base64) if res[0]: ActionChains(self.browser).move_to_element_with_offset( cpc_img, int(res[1][0] * zoom), int(res[1][1] * zoom)).click().perform() # 图形验证码坐标点击错误尝试重试 # noinspection PyBroadException try: WebDriverWait(self.browser, 3).until( EC.presence_of_element_located( (By.XPATH, "//p[text()='验证失败,请重新验证']"))) time.sleep(1) return False except Exception as _: return True else: INFO("识别未果") self.wait.until( EC.presence_of_element_located( (By.XPATH, '//*[@class="jcap_refresh"]'))).click() time.sleep(1) return False # 识别点击,如果有一次失败将再次尝试一次,再失败就跳过 if self.image_captcha_cfg["type"] in ["local", "yolov4"]: if not local_auto_identify_captcha_click(): INFO("验证码位置点击错误,尝试再试一次") if not local_auto_identify_captcha_click(): INFO("验证码位置点击错误,跳过店铺") return False else: if not auto_identify_captcha_click(): INFO("验证码位置点击错误,尝试再试一次") if not auto_identify_captcha_click(): INFO("验证码位置点击错误,跳过店铺") return False # 解绑成功页面 self.wait_check.until( EC.presence_of_element_located( (By.XPATH, "//div[text()='解绑会员成功']")), f'解绑失败,黑店【{card["brandName"]}】跳过') time.sleep(1) self.member_close_count += 1 self.remove_black_list(card) if card["brandName"] in self.specify_shops: self.specify_shops.remove(card["brandName"]) INFO("👌 本次运行已成功注销店铺会员数量为:", self.member_close_count) return True def record_black_list(self, card): """ 记录黑名单店铺 :param card: :return: """ if card not in self.black_list_shops: self.black_list_shops.append(card) if card["brandName"] not in self.need_skip_shops: self.need_skip_shops.append(card["brandName"]) def remove_black_list(self, card): """ 移除黑名单店铺 :param card: :return: """ if card in self.black_list_shops: self.black_list_shops.remove(card) if card["brandName"] in self.need_skip_shops: self.need_skip_shops.remove(card["brandName"]) def get_cloud_shop_ids(self): """ 获取云端店铺列表 :return: """ if not self.add_remote_shop_data: return True, [] url = "https://gitee.com/yqchilde/Scripts/raw/main/jd/shop.json" try: resp = requests.get(url, timeout=60).json() if "该内容无法显示" in resp: return self.get_cloud_shop_ids() INFO("获取到云端商铺信息 %d 条" % len(resp)) self.add_remote_shop_data = False return False, resp except Exception as e: ERROR("获取云端列表发生了一点小问题:", e.args) def main(self): # 打开京东 self.browser.get("https://m.jd.com/") # 检查Cookie配置 if self.config["cookie"] == "": WARN("请先在 config.yaml 里配置好cookie") sys.exit(1) # 写入Cookie self.browser.delete_all_cookies() for cookie in self.config['cookie'].split(";", 1): self.browser.add_cookie({ "name": cookie.split("=")[0].strip(" "), "value": cookie.split("=")[1].strip(";"), "domain": ".jd.com" }) self.browser.refresh() # 设置黑名单店铺名字数组 if len(self.shop_cfg["skip_shops"]) > 0: self.need_skip_shops = self.shop_cfg["skip_shops"] # 指定注销店铺配置优先级最高,且self.specify_shops需浅拷贝 if len(self.shop_cfg["specify_shops"]) > 0: INFO("👀 发现已配置指定店铺,优先指定店铺,不执行需要跳过店铺") self.specify_shops = copy.copy(self.shop_cfg["specify_shops"]) self.need_skip_shops = [] # 检查列表接口缓存 while True: # 执行一遍刷新接口 self.refresh_cache() state, card_list = self.get_cloud_shop_ids() if state: # 获取店铺列表 card_list = self.get_shop_cards() if len(card_list) == 0: INFO("🎉 本次运行获取到的店铺数为0个,判断为没有需要注销的店铺,即将退出程序") sys.exit(0) # 如果剩下的卡包 if len(self.shop_cfg["specify_shops"]) > 0 and len( self.specify_shops) == 0: INFO("👋 指定店铺已全部注销完毕,程序即将退出") sys.exit(0) # 如果剩下的卡包全部都是黑名单中的,直接就结束 # 每次比较新一轮的数量对比上一轮,即新的列表集合是否是旧的子集 card_list_new = [item['brandId'] for item in card_list] card_list_black = [ item['brandId'] for item in self.black_list_shops ] if set(card_list_new) <= set(card_list_black): INFO("芜湖,剩下的店铺全部都在程序黑名单中") INFO("本次运行记录的黑名单店铺名字为", self.need_skip_shops) INFO("🤔 剩下的店铺都是疑难杂症,请配置到黑名单中或联系客服解决,程序即将退出") sys.exit(0) # 如果乱码的有,先乱码等待 if self.wrong_store_page_count > 0: # 二次缓存中已经在黑名单的店铺,那就直接切换标签页进行处理 wait_refresh_time = self.shop_cfg["wait_refresh_time"] loop_for_wait_time = int(wait_refresh_time * 60) while loop_for_wait_time: print( "\r[%s] [INFO] 挂载乱码店铺中(总时间为%s分钟),页面还需等待: %s秒" % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), wait_refresh_time, str(loop_for_wait_time)), end='') time.sleep(1) loop_for_wait_time -= 1 print("\n[%s] [INFO] 开始刷新页面进行再次尝试乱码页面" % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) now_handle = self.browser.current_window_handle for handles in self.browser.window_handles: if now_handle != handles: self.browser.switch_to.window(handles) self.browser.refresh() time.sleep(3) vender_id = self.browser.current_url[ self.browser.current_url.rfind("venderId=") + 9:] for card in self.black_list_shops: if card["brandId"] == vender_id: INFO("开始从新标签页注销问题店铺", card["brandName"]) if self.close_member( card, self.wrong_store_page_count): self.wrong_store_page_count -= 1 self.browser.close() continue INFO("🧐 本轮运行获取到", len(card_list), "家店铺会员信息") for idx, card in enumerate(card_list): # 判断本次运行数是否达到设置 if self.member_close_max_number != 0 and self.member_close_count >= self.member_close_max_number: INFO("已注销店铺数达到配置中允许注销的最大次数,程序退出") sys.exit(0) # 非指定店铺名字跳过 if len(self.shop_cfg["specify_shops"]) > 0: if card["brandName"] not in self.shop_cfg["specify_shops"]: INFO("发现非指定注销的店铺,跳过", card["brandName"]) continue # 判断该店铺是否要跳过 if card["brandName"] in self.need_skip_shops: INFO("发现指定需要跳过的店铺,跳过", card["brandName"]) self.record_black_list(card) continue try: # 打开注销页面 if "shopName" in card: INFO("开始注销第 %d 家 -> 店铺名: %s 品牌会员名: %s" % (idx + 1, card["shopName"], card["brandName"])) else: INFO("开始注销第 %d 家 -> 店铺名: %s 品牌会员名: %s" % (idx + 1, "未知店铺", card["brandName"])) self.browser.get( "https://shopmember.m.jd.com/member/memberCloseAccount?venderId=" + card["brandId"]) # 检查当前店铺退会链接是否失效 # noinspection PyBroadException try: WebDriverWait(self.browser, 1).until( EC.presence_of_element_located( (By.XPATH, "//p[text()='网络请求失败']"))) # 云端列表失效页面无需黑名单处理 if not state: INFO("非当前店铺会员,跳过") continue INFO("当前店铺退会链接已失效(缓存导致),执行清除卡包列表缓存策略后跳过") if card["brandName"] in self.failure_store: self.record_black_list(card) self.failure_store.remove(card["brandName"]) INFO("当前店铺页面仍然失效,程序加入黑名单后自动跳过") continue else: self.failure_store.append(card["brandName"]) self.refresh_cache() continue except Exception as _: pass # 注销具体店铺操作 if not self.close_member(card): continue except Exception as e: ERROR("发生了一点小问题:", e.args) if self.debug: import traceback traceback.print_exc() INFO("本轮店铺已执行完,即将开始获取下一轮店铺")
class JDMemberCloseAccount(object): """ 京东退店铺会员 1. 全自动(超级鹰验证) 2. 半自动(手动点击图形验证码) """ def __init__(self): INFO("欢迎执行JD全自动退会程序,如有使用问题请加TG群https://t.me/jdMemberCloseAccount进行讨论") # 初始化基础配置 self.config = get_config() self.selenium_cfg = get_config()["selenium"] self.shop_cfg = get_config()["shop"] self.sms_captcha_cfg = get_config()["sms_captcha"] self.image_captcha_cfg = get_config()["image_captcha"] self.ocr_cfg = self.sms_captcha_cfg["ocr"] # 初始化selenium配置 self.browser = get_browser(self.config) self.wait = WebDriverWait(self.browser, self.selenium_cfg["timeout"]) self.wait_check = WebDriverWait(self.browser, self.selenium_cfg["check_wait"]) # 初始化短信验证码配置 if not self.sms_captcha_cfg["is_ocr"]: if not self.sms_captcha_cfg["jd_wstool"]: from utils.listener import SmsSocket self.sms = SmsSocket() elif self.sms_captcha_cfg["is_ocr"]: if self.ocr_cfg["type"] == "": WARN("当前已开启OCR模式,但是并未选择OCR类型,请在config.yaml补充ocr.type") sys.exit(1) if self.ocr_cfg["type"] == "baidu": from captcha.baidu_ocr import BaiduOCR self.baidu_ocr = BaiduOCR(self.ocr_cfg) elif self.ocr_cfg["type"] == "aliyun": from captcha.aliyun_ocr import AliYunOCR self.aliyun_ocr = AliYunOCR(self.ocr_cfg) elif self.ocr_cfg["type"] == "easyocr": from captcha.easy_ocr import EasyOCR self.easy_ocr = EasyOCR() # 初始化图形验证码配置 if self.image_captcha_cfg["type"] == "cjy": self.cjy = ChaoJiYing(self.image_captcha_cfg) elif self.image_captcha_cfg["type"] == "tj": self.tj = TuJian(self.image_captcha_cfg) elif self.image_captcha_cfg["type"] == "local": pass elif self.image_captcha_cfg["type"] == "yolov4": self.JDyolo = JDyolocaptcha(self.image_captcha_cfg) else: WARN("请在config.yaml中补充image_captcha.type") sys.exit(1) def get_code_pic(self, name='code_pic.png'): """ 获取验证码图像 :param name: :return: """ # 确定验证码的左上角和右下角坐标 code_img = self.wait.until( EC.presence_of_element_located( (By.XPATH, "//div[@id='captcha_modal']//div"))) location = code_img.location size = code_img.size _range = (int(location['x']), int(location['y']), (int(location['x']) + int(size['width'])), (int(location['y']) + int(size['height']))) # 将整个页面截图 self.browser.save_screenshot(name) # 获取浏览器大小 window_size = self.wait.until( EC.presence_of_element_located((By.XPATH, "//div[@id='root']"))) width, height = window_size.size['width'], window_size.size['height'] # 图片根据窗口大小resize,避免高分辨率影响坐标 i = Image.open(name) new_picture = i.resize((width, height)) new_picture.save(name) # 剪裁图形验证码区域 code_pic = new_picture.crop(_range) code_pic.save(name) time.sleep(2) return code_img def get_shop_cards(self): """ 获取加入店铺列表 :return: 返回店铺列表 """ url = "https://api.m.jd.com/client.action?functionId=getWalletReceivedCardList_New&clientVersion=9.5.2&build" \ "=87971&client=android&d_brand=Xiaomi&d_model=M2007J3SC&osVersion=11&screen=2266*1080&partner=xiaomi001" \ "&oaid=e02a70327f315862&openudid=3dab9a16bd95e38a&eid=eidA24e181233bsdmxzC3hIpQF2nJhWGGLb" \ "%2F1JscxFOzBjvkqrXbFQyAXZmstKs0K6bUwkQ0D3s1%2F7MzLZ7JDdhztfcdZur9xPTxU1ahqtHWYb54%2FyNK&sdkVersion=30" \ "&lang=zh_CN&uuid=3dab9a16bd95e38a&aid=3dab9a16bd95e38a&area=13_1000_40488_54442&networkType=wifi" \ "&wifiBssid=c609e931512437a8806ae06b86d3977b&uts=0f31TVRjBSsu47QjbY5aZUsO5LYa1B%2F3wqL7JjlFB60vmw6" \ "%2F8Xbj74d3sWoT4CTQgX7X0M07W75JvIfz5eu7NxdNJ73NSVbgTHkdsiVZ770PEn0MWPywxr4glUdddS6uxIQ5VfPG65uoUmlB6" \ "%2BBwwDqO1Nfxv8%2Bdm15xR%2BFG4fJWb6wCFO7DFMtnoOMo2CQ8mYoECYG3qT%2Bso7P%2FKLgQcg%3D%3D&uemps=0-0&st" \ "=1620105615175&sign=65996ece830b41aabdaba32c9d782d07&sv=100" payload = "body=%7B%22v%22%3A%224.1%22%2C%22version%22%3A1580659200%7D&" headers = { 'Host': 'api.m.jd.com', 'cookie': self.config["cookie"], 'charset': 'UTF-8', 'accept-encoding': 'br,gzip,deflate', 'user-agent': self.config["user-agent"][1], 'cache-control': 'no-cache', 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'content-length': '60' } card_list = [] urllib3.disable_warnings() resp = requests.request("POST", url, headers=headers, data=payload, verify=False) if resp.content: ret = json.loads(resp.text) if ret["code"] == "0": if ret["message"] == "用户未登录": WARN( "config.yaml中的cookie值有误,请确保pt_key和pt_pin都存在,如都存在请检查cookie是否失效" ) sys.exit(1) if "cardList" not in ret["result"]: INFO("当前卡包中会员店铺为0个") sys.exit(0) card_list = (ret["result"]["cardList"]) else: ERROR(ret) return card_list else: ERROR("获取卡包列表接口返回None,请检查网络") def refresh_cache(self): """ 利用待领卡接口刷新卡包列表缓存 :return: """ url = "https://api.m.jd.com/client.action?functionId=getWalletUnreceivedCardList_New&clientVersion=10.0.2" \ "&build=88569&client=android&d_brand=Xiaomi&d_model=M2007J3SC&osVersion=11&screen=2266*1080&partner" \ "=xiaomi001&oaid=e02a70327f315862&openudid=3dab9a16bd95e38a&eid=eidA24e181233bsdmxzC3hIpQF2nJhWGGLb" \ "%2F1JscxFOzBjvkqrXbFQyAXZmstKs0K6bUwkQ0D3s1%2F7MzLZ7JDdhztfcdZur9xPTxU1ahqtHWYb54%2FyNK&sdkVersion=30" \ "&lang=zh_CN&uuid=3dab9a16bd95e38a&aid=3dab9a16bd95e38a&area=13_1000_40488_54442&networkType=wifi" \ "&wifiBssid=unknown&uts=0f31TVRjBSsa33%2BKCXYEGxOEcvF5WoCTLW6zy4ICUIZSJDN7StKCM709NzfQ4TH7UyK43CcV9m" \ "8NBxDef2fv9lr5dGonowgeJ4YODX5Jeb5TRw1PUE0YmmEdsQw4TlvNc5umf1j%2FKrR%2F3FAfMh%2Bs8nQ%2BG8trnDhaJW2kJKg" \ "Hzq7N3es4kOmO4MEmUYf2putd%2BK0ZMPqJ8MfHJCta74kmAA%3D%3D&uemps=0-0&st=1623387008796&sign=d8297b1521c" \ "0d56cdf290e2de658452e&sv=100" payload = "body=%7B%22pageNum%22%3A1%2C%22pageSize%22%3A10%2C%22v%22%3A%224.3%22%2C%22version%22%3A1580659200" \ "%7D&" headers = { 'Host': 'api.m.jd.com', 'cookie': self.config["cookie"], 'charset': 'UTF-8', 'accept-encoding': 'br,gzip,deflate', 'user-agent': self.config["user-agent"][1], 'cache-control': 'no-cache', 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'content-length': '102' } urllib3.disable_warnings() resp = requests.request("POST", url, headers=headers, data=payload, verify=False) ret = json.loads(resp.text) if ret["code"] == "0": return True else: ERROR(ret) return False def main(self): # 打开京东 self.browser.get("https://m.jd.com/") if self.config["cookie"] == "": WARN("请先在 config.yaml 里配置好cookie") sys.exit(1) # 写入 cookie self.browser.delete_all_cookies() for cookie in self.config['cookie'].split(";", 1): self.browser.add_cookie({ "name": cookie.split("=")[0].strip(" "), "value": cookie.split("=")[1].strip(";"), "domain": ".jd.com" }) self.browser.refresh() cache_card_list, retried = [], 0 cnt, member_close_max_number = 0, self.shop_cfg[ "member_close_max_number"] disgusting_shop, black_list = False, [] while True: # 获取店铺列表 card_list = self.get_shop_cards() if len(card_list) == 0: INFO("本次运行获取到的店铺数为0个,判断为没有需要注销的店铺,即将退出程序") sys.exit(0) # 记录一下所有请求数据,防止第一轮做完之后缓存没有刷新导致获取的链接请求失败 if len(cache_card_list) == 0: cache_card_list = [item['brandId'] for item in card_list] else: if retried >= 10: INFO("连续%d次获取到相同的店铺列表,判断为%d分钟左右的缓存仍未刷新,即将退出程序" % (retried, retried / 2)) sys.exit(0) if not disgusting_shop: # 每次比较新一轮的数量对比上一轮,即新的列表集合是否是旧的子集 new_card_list = [item['brandId'] for item in card_list] if set(new_card_list) <= set(cache_card_list) and len( new_card_list) == len(cache_card_list): INFO("当前接口获取到的店铺列表和上一轮一致,认为接口缓存还未刷新,即将尝试刷新缓存") if self.refresh_cache(): INFO("理论上缓存已经刷新成功,如页面未成功自动刷新请及时反馈") disgusting_shop = True continue else: INFO("当前接口获取到的店铺列表和上一轮一致,认为接口缓存还未刷新,30秒后会再次尝试") time.sleep(30) retried += 1 continue else: cache_card_list = new_card_list else: # 发现第二次缓存,多半是无法注销的店铺 try: INFO("糟糕,这家店铺可能无法注销,该店铺名字为 %s,请先手动跳过" % card_list[len(black_list)]["brandName"]) disgusting_shop = False if card_list[len(black_list)] in black_list: black_list.append(card_list[len(black_list) + 1]) else: black_list.append(card_list[len(black_list)]) except IndexError: INFO("好了🙆,剩下的店铺应该都是无法注销的,请手动打开手机查看对应店铺,程序即将退出") sys.exit(0) # 跳过无法注销的店铺 shops = [] for item in black_list: shops.append(item["brandName"]) # 加载配置文件中需要跳过的店铺 if self.shop_cfg['skip_shops'] != "": shops = self.shop_cfg['skip_shops'].split(",") INFO("本轮运行获取到", len(card_list), "家店铺会员信息") for card in card_list: # 判断本次运行数是否达到设置 if member_close_max_number != 0 and cnt >= member_close_max_number: INFO("已注销店铺数达到配置中允许注销的最大次数,程序退出") sys.exit(0) # 判断该店铺是否要跳过 if card["brandName"] in shops: INFO("发现需要跳过的店铺", card["brandName"]) continue try: # 打开注销页面 self.browser.get( "https://shopmember.m.jd.com/member/memberCloseAccount?venderId=" + card["brandId"]) INFO("开始注销店铺", card["brandName"]) # 检查当前店铺退会链接是否失效 # noinspection PyBroadException try: WebDriverWait(self.browser, 1).until( EC.presence_of_element_located( (By.XPATH, "//p[text()='网络请求失败']"))) INFO("当前店铺退会链接已失效,暂判定为缓存导致,正在尝试清除卡包列表缓存...") if self.refresh_cache(): INFO("理论上缓存已经刷新成功,如项目未继续执行请及时反馈") break else: INFO("卡包列表缓存清除失败,即将跳过该店铺,失效店铺链接为:") INFO( "https://shopmember.m.jd.com/member/memberCloseAccount?venderId=" + card["brandId"]) continue except Exception as _: pass # 检查手机尾号是否正确 phone = self.wait.until( EC.presence_of_element_located( (By.XPATH, "//div[@class='cm-ec']"))).text if self.shop_cfg['phone_tail_number'] != "": if phone[-4:] != self.shop_cfg['phone_tail_number']: INFO("当前店铺绑定手机号为%s,尾号≠配置中设置的尾号,跳过店铺" % phone) continue if "*" not in phone[:4]: INFO("当前店铺绑定手机号为%s,明显为无效号码,跳过店铺" % phone) continue # 发送短信验证码 self.wait.until( EC.presence_of_element_located( (By.XPATH, "//button[text()='发送验证码']")), "发送短信验证码超时 " + card["brandName"]).click() # 判断是否发送成功,发送失败为黑店,直接跳过 self.wait_check.until( EC.presence_of_element_located( (By.XPATH, "//div[text()='发送成功']")), f'发送失败,黑店【{card["brandName"]}】跳过') # 要连接的websocket地址 sms_code = "" ws_conn_url, ws_timeout = self.sms_captcha_cfg[ "ws_conn_url"], self.sms_captcha_cfg["ws_timeout"] # ocr识别投屏验证码 if self.sms_captcha_cfg["is_ocr"]: if len(self.ocr_cfg["ocr_range"]) != 4: WARN("请在config.yaml中配置 ocr_range") sys.exit(1) else: _range = (self.ocr_cfg["ocr_range"]) ocr_delay_time = self.ocr_cfg["ocr_delay_time"] INFO("刚发短信,%d秒后识别验证码" % ocr_delay_time) time.sleep(ocr_delay_time) if self.ocr_cfg["type"] == "baidu": INFO("开始调用百度OCR识别") sms_code = self.baidu_ocr.baidu_ocr( _range, ocr_delay_time) elif self.ocr_cfg["type"] == "aliyun": INFO("开始调用阿里云OCR识别") sms_code = self.aliyun_ocr.aliyun_ocr( _range, ocr_delay_time) elif self.ocr_cfg["type"] == "easyocr": INFO("开始调用EasyOCR识别") sms_code = self.easy_ocr.easy_ocr( _range, ocr_delay_time) else: try: if self.sms_captcha_cfg["jd_wstool"]: recv = asyncio.get_event_loop( ).run_until_complete( ws_conn(ws_conn_url, ws_timeout)) else: recv = self.sms.get_code() if recv == "": cache_card_list = [] INFO("等待websocket推送短信验证码超时,即将跳过", card["brandName"]) continue else: sms_code = json.loads(recv)["sms_code"] except Exception as e: WARN("WebSocket监听时发生了问题", e.args) sys.exit(1) # 输入短信验证码 self.wait.until( EC.presence_of_element_located( (By.XPATH, "//input[@type='number']")), "输入短信验证码超时 " + card["brandName"]).send_keys(sms_code) time.sleep(1) # 点击注销按钮 self.wait.until( EC.presence_of_element_located( (By.XPATH, "//div[text()='注销会员']")), "点击注销按钮超时 " + card["brandName"]).click() # 利用打码平台识别图形验证码并模拟点击 def auto_identify_captcha_click(): # 分割图形验证码 code_img = self.get_code_pic() img = open('code_pic.png', 'rb').read() pic_str, pic_id = "", "" if self.image_captcha_cfg["type"] == "cjy": # 调用超级鹰API接口识别点触验证码 INFO("开始调用超级鹰识别验证码") resp = self.cjy.post_pic( img, self.image_captcha_cfg["cjy_kind"]) if "pic_str" in resp and resp["pic_str"] == "": INFO("超级鹰验证失败,原因为:", resp["err_str"]) else: pic_str = resp["pic_str"] pic_id = resp["pic_id"] elif self.image_captcha_cfg["type"] == "tj": # 调用图鉴API接口识别点触验证码 INFO("开始调用图鉴识别验证码") resp = self.tj.post_pic( img, self.image_captcha_cfg["tj_type_id"]) pic_str = resp["result"] pic_id = resp["id"] # 处理要点击的坐标 all_list = [] xy_list = [] x = int(pic_str.split(',')[0]) xy_list.append(x) y = int(pic_str.split(',')[1]) xy_list.append(y) all_list.append(xy_list) # 循环遍历点击图片 for i in all_list: x = i[0] y = i[1] ActionChains( self.browser).move_to_element_with_offset( code_img, x, y).click().perform() time.sleep(1) # 图形验证码坐标点击错误尝试重试 # noinspection PyBroadException try: WebDriverWait(self.browser, 3).until( EC.presence_of_element_located( (By.XPATH, "//p[text()='验证失败,请重新验证']"))) INFO("验证码坐标识别出错,将上报平台处理") # 上报错误的图片到平台 if self.image_captcha_cfg["type"] == "cjy": self.cjy.report_error(pic_id) elif self.image_captcha_cfg["type"] == "tj": self.tj.report_error(pic_id) return False except Exception as _: return True # 本地识别图形验证码并模拟点击 def local_auto_identify_captcha_click(): for _ in range(4): cpc_img = self.wait.until( EC.presence_of_element_located( (By.XPATH, '//*[@id="cpc_img"]'))) zoom = cpc_img.size['height'] / 170 cpc_img_path_base64 = self.wait.until( EC.presence_of_element_located( (By.XPATH, '//*[@id="cpc_img"]' ))).get_attribute('src').replace( "data:image/jpeg;base64,", "") pcp_show_picture_path_base64 = self.wait.until( EC.presence_of_element_located( (By.XPATH, '//*[@class="pcp_showPicture"]' ))).get_attribute('src') # 正在识别验证码 if self.image_captcha_cfg["type"] == "local": INFO("正在通过本地引擎识别") res = JDcaptcha_base64( cpc_img_path_base64, pcp_show_picture_path_base64) else: INFO("正在通过深度学习引擎识别") res = self.JDyolo.JDyolo( cpc_img_path_base64, pcp_show_picture_path_base64) if res[0]: ActionChains( self.browser).move_to_element_with_offset( cpc_img, int(res[1][0] * zoom), int(res[1][1] * zoom)).click().perform() # 图形验证码坐标点击错误尝试重试 # noinspection PyBroadException try: WebDriverWait(self.browser, 3).until( EC.presence_of_element_located( (By.XPATH, "//p[text()='验证失败,请重新验证']"))) time.sleep(1) return False except Exception as _: return True else: INFO("识别未果") self.wait.until( EC.presence_of_element_located( (By.XPATH, '//*[@class="jcap_refresh"]' ))).click() time.sleep(1) return False # 识别点击,如果有一次失败将再次尝试一次,再失败就跳过 if self.image_captcha_cfg["type"] in ["local", "yolov4"]: if not local_auto_identify_captcha_click(): INFO("验证码位置点击错误,尝试再试一次") assert local_auto_identify_captcha_click() else: if not auto_identify_captcha_click(): INFO("验证码位置点击错误,尝试再试一次") assert auto_identify_captcha_click() # 解绑成功页面 self.wait_check.until( EC.presence_of_element_located( (By.XPATH, "//div[text()='解绑会员成功']")), f'解绑失败,黑店【{card["brandName"]}】跳过') time.sleep(1) cnt += 1 INFO("本次运行已成功注销店铺会员数量为:", cnt) except Exception as e: ERROR("发生了一点小问题:", e.args) if self.config["debug"]: import traceback traceback.print_exc() INFO("本轮店铺已执行完,即将开始获取下一轮店铺")