123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- 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()
|