from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time from PIL import Image import cv2 from selenium.webdriver import ActionChains import requests from io import BytesIO class MaoYanCode(object): # 初始化 def __init__(self): self.url = 'https://open.yuewen.com/' self.browser = webdriver.Chrome() self.wait = WebDriverWait(self.browser, 10) def open(self): # 打开网页 self.browser.get(self.url) # 定位背景图 def bg_img_src(self): try: # 定位背景图片元素 bg_element = self.wait.until( EC.presence_of_element_located(( By.CSS_SELECTOR, 'div.tc-bg-img#slideBg' )) ) # 获取style属性 style = bg_element.get_attribute('style') # 提取background-image URL import re url_match = re.search(r'background-image: url\("([^"]+)"\)', style) if url_match: return url_match.group(1) return None except Exception as e: print(f"获取背景图片URL失败: {str(e)}") return None # 定位缺块 def jpp_img_src(self): try: # 等待元素加载 target_element = self.wait.until( EC.presence_of_element_located(( By.CSS_SELECTOR, 'div.tc-fg-item[aria-label="拖动下方滑块完成拼图"]' )) ) # 获取背景图片URL style = target_element.get_attribute('style') # 从style中提取background-image的url import re url_match = re.search(r'background-image: url\("([^"]+)"\)', style) if url_match: return url_match.group(1) return None except Exception as e: print(f"获取图片URL失败: {str(e)}") return None # 获取背景和缺块图片 def get_img(self): bg_src = self.bg_img_src() jpp_src = self.jpp_img_src() response1 = requests.get(bg_src) image1 = Image.open(BytesIO(response1.content)) image1.save('bg_img.png') response2 = requests.get(jpp_src) image2 = Image.open(BytesIO(response2.content)) image2.save('jpp_img.png') return image1, image2 # 定位滑块 def slider_element(self): time.sleep(2) slider = self.wait.until(EC.presence_of_element_located( (By.XPATH, '//*[@class="tc-drag-thumb"]'))) return slider # 登录按钮 def login_btn_element(self): time.sleep(2) btn = self.wait.until(EC.presence_of_element_located( (By.XPATH, '//*[@class="op-header-info"]'))) return btn def input_username(self, username="zywz2024013@163.com", retries=3): time.sleep(2) """ 输入用户名 Args: username: 要输入的用户名 retries: 重试次数 """ for attempt in range(retries): try: # 多种定位方式 selectors = [ (By.CSS_SELECTOR, 'input[name="username"]'), (By.CSS_SELECTOR, 'input[placeholder="用户名"]'), (By.CSS_SELECTOR, '.el-input__inner[name="username"]'), (By.XPATH, '//input[@name="username"]'), ] # 尝试每种定位方式 for selector in selectors: try: # 等待元素可见和可交互 input_element = self.wait.until( EC.element_to_be_clickable(selector) ) # 清除现有内容 input_element.clear() # 输入用户名 input_element.send_keys(username) # 验证输入是否成功 if self.verify_input(input_element, username): self.logger.info("用户名输入成功") return True except Exception as e: self.logger.warning(f"使用选择器 {selector} 失败: {str(e)}") continue except Exception as e: self.logger.error(f"输入尝试 {attempt + 1}/{retries} 失败: {str(e)}") if attempt < retries - 1: time.sleep(1) continue self.logger.error("所有输入尝试均失败") return False def input_password(self, password="Huang888888", retries=3): time.sleep(2) """ 输入密码 Args: password: 要输入的密码 retries: 重试次数 """ for attempt in range(retries): try: # 多种定位方式 selectors = [ (By.CSS_SELECTOR, 'input[name="password"]'), (By.CSS_SELECTOR, 'input[type="password"]'), (By.CSS_SELECTOR, '.el-input__inner[name="password"]'), (By.XPATH, '//input[@name="password"]'), ] for selector in selectors: try: # 等待密码输入框可交互 password_input = self.wait.until( EC.element_to_be_clickable(selector) ) # 清除现有内容 password_input.clear() # 安全输入密码 self.safe_input_password(password_input, password) # 验证输入 if self.verify_password_input(password_input, password): self.logger.info("密码输入成功") return True except Exception as e: self.logger.warning(f"使用选择器 {selector} 失败: {str(e)}") continue except Exception as e: self.logger.error(f"密码输入尝试 {attempt + 1}/{retries} 失败: {str(e)}") if attempt < retries - 1: time.sleep(1) continue self.logger.error("所有密码输入尝试均失败") return False def click_login_button(self, retries=3): time.sleep(2) """ 点击登录按钮 Args: retries: 重试次数 Returns: bool: 是否点击成功 """ for attempt in range(retries): try: # 多种定位方式 selectors = [ (By.CSS_SELECTOR, '.el-button.loginin'), (By.CSS_SELECTOR, '[data-v-0101b427].el-button'), (By.XPATH, "//button[contains(@class, 'loginin')]"), (By.XPATH, "//button[.//span[text()='登录']]"), ] for selector in selectors: try: # 等待按钮可点击 button = self.wait.until( EC.element_to_be_clickable(selector) ) # 确保按钮在视图中 self.scroll_to_element(button) # 尝试点击 if self.safe_click(button): self.logger.info("登录按钮点击成功") # 等待页面响应 if self.wait_for_response(): return True except ElementClickInterceptedException: # 如果按钮被遮挡,尝试JavaScript点击 self.logger.warning("按钮被遮挡,尝试JavaScript点击") if self.js_click(button): return True except Exception as e: self.logger.warning(f"使用选择器 {selector} 失败: {str(e)}") continue except Exception as e: self.logger.error(f"点击尝试 {attempt + 1}/{retries} 失败: {str(e)}") if attempt < retries - 1: time.sleep(1) continue self.logger.error("所有点击尝试均失败") return False def jump_login_view(self): time.sleep(2) login_btn = self.wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-v-0101b427]')) ) login_btn.click() # 识别缺口 def get_gap(self, gap_img): bg_img = cv2.imread('bg_img.png') tp_img = cv2.imread('jpp_img.png') # 识别图片边缘 bg_edge = cv2.Canny(bg_img, 100, 200) tp_edge = cv2.Canny(tp_img, 100, 200) # 转换图片格式 # 灰度图片转为RGB彩色图片 bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB) tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB) # 缺口匹配 res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 寻找最优匹配 # 绘制方框 # img.shape[:2] 获取图片的长、宽 height, width = tp_pic.shape[:2] tl = max_loc # 左上角点的坐标 # cv2.rectangle(img, (x1, y1), (x2, y2), RGB颜色值, 边框宽度--->若为负则填充整个矩形) cv2.rectangle(bg_img, tl, (tl[0] + width - 15, tl[1] + height - 15), (0, 0, 255), 2) # 绘制矩形 cv2.imwrite(gap_img, bg_img) # 保存在本地 # cv2.imshow('Show', bg_img) # cv2.waitKey(0) # cv2.destroyAllWindows() # 返回缺口的X坐标 return tl[0] # 构造移动轨迹 def get_track(self, distance): # 移动轨迹 track = [] # 当前位移 current = 0 # 减速阈值 mid = distance * 4 / 5 # 计算间隔 t = 0.2 # 初速度 v = 0 while current < distance: if current < mid: # 加速度为正5 a = 5 else: # 加速度为负3 a = -3 # 初速度v0 v0 = v # 当前速度v = v0 + at v = v0 + a * t # 移动距离x = v0t + 1/2 * a * t^2 move = v0 * t + 1 / 2 * a * t * t # 当前位移 current += move # 加入轨迹 track.append(round(move)) return track # 移动滑块 def move_to_gap(self, slider, track): # click_and_hold()按住底部滑块 ActionChains(self.browser).click_and_hold(slider).perform() for x in track: ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) # release()松开鼠标 ActionChains(self.browser).release().perform() def login(self): self.open() time.sleep(2) # 网速原因可能导致网页加载不完全,致使iframe报错 iframe = self.wait.until( EC.presence_of_all_elements_located((By.TAG_NAME, 'iframe'))) self.wait.until( EC.frame_to_be_available_and_switch_to_it(iframe[1])) login_btn = self.login_btn_element() login_btn.click() self.jump_login_view() self.input_username() self.input_password() self.click_login_button() self.get_img() slider = self.slider_element() slider.click() gap = self.get_gap('result.png') # 页面为360*360,图片为680*390,更改比例,减去初始位移 gap_end = int((gap - 40) / 2) # 获取缺口 print('缺口位置', gap_end) # 减去缺块白边 gap_end -= 10 # 获取移动轨迹 track = self.get_track(gap_end) print('滑动轨迹', track) # 拖动滑块 self.move_to_gap(slider, track) if __name__ == '__main__': crack = MaoYanCode() crack.login()