test.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. from selenium import webdriver
  2. from selenium.webdriver.common.by import By
  3. from selenium.webdriver.support.wait import WebDriverWait
  4. from selenium.webdriver.support import expected_conditions as EC
  5. import time
  6. from PIL import Image
  7. import cv2
  8. from selenium.webdriver import ActionChains
  9. import requests
  10. from io import BytesIO
  11. class MaoYanCode(object):
  12. # 初始化
  13. def __init__(self):
  14. self.url = 'https://open.yuewen.com/'
  15. self.browser = webdriver.Chrome()
  16. self.wait = WebDriverWait(self.browser, 10)
  17. def open(self):
  18. # 打开网页
  19. self.browser.get(self.url)
  20. # 定位背景图
  21. def bg_img_src(self):
  22. try:
  23. # 定位背景图片元素
  24. bg_element = self.wait.until(
  25. EC.presence_of_element_located((
  26. By.CSS_SELECTOR,
  27. 'div.tc-bg-img#slideBg'
  28. ))
  29. )
  30. # 获取style属性
  31. style = bg_element.get_attribute('style')
  32. # 提取background-image URL
  33. import re
  34. url_match = re.search(r'background-image: url\("([^"]+)"\)', style)
  35. if url_match:
  36. return url_match.group(1)
  37. return None
  38. except Exception as e:
  39. print(f"获取背景图片URL失败: {str(e)}")
  40. return None
  41. # 定位缺块
  42. def jpp_img_src(self):
  43. try:
  44. # 等待元素加载
  45. target_element = self.wait.until(
  46. EC.presence_of_element_located((
  47. By.CSS_SELECTOR,
  48. 'div.tc-fg-item[aria-label="拖动下方滑块完成拼图"]'
  49. ))
  50. )
  51. # 获取背景图片URL
  52. style = target_element.get_attribute('style')
  53. # 从style中提取background-image的url
  54. import re
  55. url_match = re.search(r'background-image: url\("([^"]+)"\)', style)
  56. if url_match:
  57. return url_match.group(1)
  58. return None
  59. except Exception as e:
  60. print(f"获取图片URL失败: {str(e)}")
  61. return None
  62. # 获取背景和缺块图片
  63. def get_img(self):
  64. bg_src = self.bg_img_src()
  65. jpp_src = self.jpp_img_src()
  66. response1 = requests.get(bg_src)
  67. image1 = Image.open(BytesIO(response1.content))
  68. image1.save('bg_img.png')
  69. response2 = requests.get(jpp_src)
  70. image2 = Image.open(BytesIO(response2.content))
  71. image2.save('jpp_img.png')
  72. return image1, image2
  73. # 定位滑块
  74. def slider_element(self):
  75. time.sleep(2)
  76. slider = self.wait.until(EC.presence_of_element_located(
  77. (By.XPATH,
  78. '//*[@class="tc-drag-thumb"]')))
  79. return slider
  80. # 登录按钮
  81. def login_btn_element(self):
  82. time.sleep(2)
  83. btn = self.wait.until(EC.presence_of_element_located(
  84. (By.XPATH,
  85. '//*[@class="op-header-info"]')))
  86. return btn
  87. def input_username(self, username="zywz2024013@163.com", retries=3):
  88. time.sleep(2)
  89. """
  90. 输入用户名
  91. Args:
  92. username: 要输入的用户名
  93. retries: 重试次数
  94. """
  95. for attempt in range(retries):
  96. try:
  97. # 多种定位方式
  98. selectors = [
  99. (By.CSS_SELECTOR, 'input[name="username"]'),
  100. (By.CSS_SELECTOR, 'input[placeholder="用户名"]'),
  101. (By.CSS_SELECTOR, '.el-input__inner[name="username"]'),
  102. (By.XPATH, '//input[@name="username"]'),
  103. ]
  104. # 尝试每种定位方式
  105. for selector in selectors:
  106. try:
  107. # 等待元素可见和可交互
  108. input_element = self.wait.until(
  109. EC.element_to_be_clickable(selector)
  110. )
  111. # 清除现有内容
  112. input_element.clear()
  113. # 输入用户名
  114. input_element.send_keys(username)
  115. # 验证输入是否成功
  116. if self.verify_input(input_element, username):
  117. self.logger.info("用户名输入成功")
  118. return True
  119. except Exception as e:
  120. self.logger.warning(f"使用选择器 {selector} 失败: {str(e)}")
  121. continue
  122. except Exception as e:
  123. self.logger.error(f"输入尝试 {attempt + 1}/{retries} 失败: {str(e)}")
  124. if attempt < retries - 1:
  125. time.sleep(1)
  126. continue
  127. self.logger.error("所有输入尝试均失败")
  128. return False
  129. def input_password(self, password="Huang888888", retries=3):
  130. time.sleep(2)
  131. """
  132. 输入密码
  133. Args:
  134. password: 要输入的密码
  135. retries: 重试次数
  136. """
  137. for attempt in range(retries):
  138. try:
  139. # 多种定位方式
  140. selectors = [
  141. (By.CSS_SELECTOR, 'input[name="password"]'),
  142. (By.CSS_SELECTOR, 'input[type="password"]'),
  143. (By.CSS_SELECTOR, '.el-input__inner[name="password"]'),
  144. (By.XPATH, '//input[@name="password"]'),
  145. ]
  146. for selector in selectors:
  147. try:
  148. # 等待密码输入框可交互
  149. password_input = self.wait.until(
  150. EC.element_to_be_clickable(selector)
  151. )
  152. # 清除现有内容
  153. password_input.clear()
  154. # 安全输入密码
  155. self.safe_input_password(password_input, password)
  156. # 验证输入
  157. if self.verify_password_input(password_input, password):
  158. self.logger.info("密码输入成功")
  159. return True
  160. except Exception as e:
  161. self.logger.warning(f"使用选择器 {selector} 失败: {str(e)}")
  162. continue
  163. except Exception as e:
  164. self.logger.error(f"密码输入尝试 {attempt + 1}/{retries} 失败: {str(e)}")
  165. if attempt < retries - 1:
  166. time.sleep(1)
  167. continue
  168. self.logger.error("所有密码输入尝试均失败")
  169. return False
  170. def click_login_button(self, retries=3):
  171. time.sleep(2)
  172. """
  173. 点击登录按钮
  174. Args:
  175. retries: 重试次数
  176. Returns:
  177. bool: 是否点击成功
  178. """
  179. for attempt in range(retries):
  180. try:
  181. # 多种定位方式
  182. selectors = [
  183. (By.CSS_SELECTOR, '.el-button.loginin'),
  184. (By.CSS_SELECTOR, '[data-v-0101b427].el-button'),
  185. (By.XPATH, "//button[contains(@class, 'loginin')]"),
  186. (By.XPATH, "//button[.//span[text()='登录']]"),
  187. ]
  188. for selector in selectors:
  189. try:
  190. # 等待按钮可点击
  191. button = self.wait.until(
  192. EC.element_to_be_clickable(selector)
  193. )
  194. # 确保按钮在视图中
  195. self.scroll_to_element(button)
  196. # 尝试点击
  197. if self.safe_click(button):
  198. self.logger.info("登录按钮点击成功")
  199. # 等待页面响应
  200. if self.wait_for_response():
  201. return True
  202. except ElementClickInterceptedException:
  203. # 如果按钮被遮挡,尝试JavaScript点击
  204. self.logger.warning("按钮被遮挡,尝试JavaScript点击")
  205. if self.js_click(button):
  206. return True
  207. except Exception as e:
  208. self.logger.warning(f"使用选择器 {selector} 失败: {str(e)}")
  209. continue
  210. except Exception as e:
  211. self.logger.error(f"点击尝试 {attempt + 1}/{retries} 失败: {str(e)}")
  212. if attempt < retries - 1:
  213. time.sleep(1)
  214. continue
  215. self.logger.error("所有点击尝试均失败")
  216. return False
  217. def jump_login_view(self):
  218. time.sleep(2)
  219. login_btn = self.wait.until(
  220. EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-v-0101b427]'))
  221. )
  222. login_btn.click()
  223. # 识别缺口
  224. def get_gap(self, gap_img):
  225. bg_img = cv2.imread('bg_img.png')
  226. tp_img = cv2.imread('jpp_img.png')
  227. # 识别图片边缘
  228. bg_edge = cv2.Canny(bg_img, 100, 200)
  229. tp_edge = cv2.Canny(tp_img, 100, 200)
  230. # 转换图片格式
  231. # 灰度图片转为RGB彩色图片
  232. bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
  233. tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)
  234. # 缺口匹配
  235. res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
  236. min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 寻找最优匹配
  237. # 绘制方框
  238. # img.shape[:2] 获取图片的长、宽
  239. height, width = tp_pic.shape[:2]
  240. tl = max_loc # 左上角点的坐标
  241. # cv2.rectangle(img, (x1, y1), (x2, y2), RGB颜色值, 边框宽度--->若为负则填充整个矩形)
  242. cv2.rectangle(bg_img, tl, (tl[0] + width - 15, tl[1] + height - 15),
  243. (0, 0, 255), 2) # 绘制矩形
  244. cv2.imwrite(gap_img, bg_img) # 保存在本地
  245. # cv2.imshow('Show', bg_img)
  246. # cv2.waitKey(0)
  247. # cv2.destroyAllWindows()
  248. # 返回缺口的X坐标
  249. return tl[0]
  250. # 构造移动轨迹
  251. def get_track(self, distance):
  252. # 移动轨迹
  253. track = []
  254. # 当前位移
  255. current = 0
  256. # 减速阈值
  257. mid = distance * 4 / 5
  258. # 计算间隔
  259. t = 0.2
  260. # 初速度
  261. v = 0
  262. while current < distance:
  263. if current < mid:
  264. # 加速度为正5
  265. a = 5
  266. else:
  267. # 加速度为负3
  268. a = -3
  269. # 初速度v0
  270. v0 = v
  271. # 当前速度v = v0 + at
  272. v = v0 + a * t
  273. # 移动距离x = v0t + 1/2 * a * t^2
  274. move = v0 * t + 1 / 2 * a * t * t
  275. # 当前位移
  276. current += move
  277. # 加入轨迹
  278. track.append(round(move))
  279. return track
  280. # 移动滑块
  281. def move_to_gap(self, slider, track):
  282. # click_and_hold()按住底部滑块
  283. ActionChains(self.browser).click_and_hold(slider).perform()
  284. for x in track:
  285. ActionChains(self.browser).move_by_offset(xoffset=x,
  286. yoffset=0).perform()
  287. time.sleep(0.5)
  288. # release()松开鼠标
  289. ActionChains(self.browser).release().perform()
  290. def login(self):
  291. self.open()
  292. time.sleep(2)
  293. # 网速原因可能导致网页加载不完全,致使iframe报错
  294. iframe = self.wait.until(
  295. EC.presence_of_all_elements_located((By.TAG_NAME, 'iframe')))
  296. self.wait.until(
  297. EC.frame_to_be_available_and_switch_to_it(iframe[1]))
  298. login_btn = self.login_btn_element()
  299. login_btn.click()
  300. self.jump_login_view()
  301. self.input_username()
  302. self.input_password()
  303. self.click_login_button()
  304. self.get_img()
  305. slider = self.slider_element()
  306. slider.click()
  307. gap = self.get_gap('result.png')
  308. # 页面为360*360,图片为680*390,更改比例,减去初始位移
  309. gap_end = int((gap - 40) / 2)
  310. # 获取缺口
  311. print('缺口位置', gap_end)
  312. # 减去缺块白边
  313. gap_end -= 10
  314. # 获取移动轨迹
  315. track = self.get_track(gap_end)
  316. print('滑动轨迹', track)
  317. # 拖动滑块
  318. self.move_to_gap(slider, track)
  319. if __name__ == '__main__':
  320. crack = MaoYanCode()
  321. crack.login()