xg_recommend.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. # -*- coding: utf-8 -*-
  2. # @Author: wangkun
  3. # @Time: 2023/7/6
  4. import os
  5. import random
  6. import sys
  7. import time
  8. import cv2
  9. import numpy as np
  10. from selenium.webdriver.common.keys import Keys
  11. import requests
  12. import urllib3
  13. from selenium.webdriver import DesiredCapabilities, ActionChains
  14. from selenium import webdriver
  15. from selenium.webdriver.chrome.service import Service
  16. from selenium.webdriver.common.by import By
  17. sys.path.append(os.getcwd())
  18. from common.common import Common
  19. class XGRecommend(object):
  20. def __init__(self, log_type, crawler, env):
  21. """
  22. 本地启动 Chrome,指定端口号:12306
  23. open -a "Google Chrome" --args --remote-debugging-port=12306
  24. """
  25. Common.logger(log_type, crawler).info("启动 Chrome 浏览器")
  26. cmd = 'open -a "Google Chrome" --args --remote-debugging-port=12306'
  27. os.system(cmd)
  28. if env == "dev":
  29. chromedriver = "/Users/wangkun/Downloads/chromedriver/chromedriver_v114/chromedriver"
  30. else:
  31. chromedriver = "/usr/bin/chromedriver"
  32. # 打印请求配置
  33. ca = DesiredCapabilities.CHROME
  34. ca["goog:loggingPrefs"] = {"performance": "ALL"}
  35. # 初始化浏览器
  36. self.browser = webdriver.ChromeOptions()
  37. self.browser.add_experimental_option("debuggerAddress", "127.0.0.1:12306")
  38. # # 设置user-agent
  39. # self.browser.add_argument(
  40. # f'user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36')
  41. # # 去掉提示:Chrome正收到自动测试软件的控制
  42. # self.browser.add_argument('--disable-infobars')
  43. # # 禁用GPU加速
  44. # self.browser.add_argument('--disable-gpu')
  45. # # 关闭开发者模式
  46. # self.browser.add_experimental_option("useAutomationExtension", False)
  47. # # 以键值对的形式加入参数
  48. # self.browser.add_experimental_option('excludeSwitches', ['enable-automation'])
  49. # # 禁用启用Blink运行时的功能
  50. # self.browser.add_argument('--disable-blink-features=AutomationControlled')
  51. # 不打开浏览器运行
  52. # self.browser.add_argument("--headless")
  53. # # linux 环境中,静默模式必须要加的参数
  54. # self.browser.add_argument("--no-sandbox")
  55. # # 设置浏览器size
  56. # self.browser.add_argument("--window-size=1920,1080")
  57. # driver初始化
  58. self.driver = webdriver.Chrome(desired_capabilities=ca, options=self.browser, service=Service(chromedriver))
  59. self.driver.implicitly_wait(10)
  60. Common.logger(log_type, crawler).info("打开西瓜推荐页")
  61. self.driver.get(f"https://www.ixigua.com/")
  62. # 在当前页面打开新的标签页
  63. self.driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.COMMAND + 't')
  64. # 切换到新打开的标签页
  65. self.driver.switch_to.window(self.driver.window_handles[-1])
  66. self.username = "19831265541"
  67. self.password = "Test111111"
  68. time.sleep(2)
  69. def quit(self, log_type, crawler):
  70. Common.logger(log_type, crawler).info("退出浏览器")
  71. self.driver.quit()
  72. # 传入滑块背景图片本地路径和滑块本地路径,返回滑块到缺口的距离
  73. @staticmethod
  74. def findPic(log_type, crawler, img_bg_path, img_slider_path):
  75. """
  76. 找出图像中最佳匹配位置
  77. :param log_type: log
  78. :param crawler: 爬虫
  79. :param img_bg_path: 滑块背景图本地路径
  80. :param img_slider_path: 滑块图片本地路径
  81. :return: 返回最差匹配、最佳匹配对应的x坐标
  82. """
  83. # 读取滑块背景图片,参数是图片路径,Opencv2默认使用BGR模式
  84. # cv2.imread()是 image read的简写
  85. # img_bg 是一个numpy库ndarray数组对象
  86. img_bg = cv2.imread(img_bg_path)
  87. # 对滑块背景图片进行处理,由BGR模式转为gray模式(即灰度模式,也就是黑白图片)
  88. # 为什么要处理? BGR模式(彩色图片)的数据比黑白图片的数据大,处理后可以加快算法的计算
  89. # BGR模式:常见的是RGB模式
  90. # R代表红,red; G代表绿,green; B代表蓝,blue。
  91. # RGB模式就是,色彩数据模式,R在高位,G在中间,B在低位。BGR正好相反。
  92. # 如红色:RGB模式是(255,0,0),BGR模式是(0,0,255)
  93. img_bg_gray = cv2.cvtColor(img_bg, cv2.COLOR_BGR2GRAY)
  94. # 读取滑块,参数1是图片路径,参数2是使用灰度模式
  95. img_slider_gray = cv2.imread(img_slider_path, 0)
  96. # 在滑块背景图中匹配滑块。参数cv2.TM_CCOEFF_NORMED是opencv2中的一种算法
  97. res = cv2.matchTemplate(img_bg_gray, img_slider_gray, cv2.TM_CCOEFF_NORMED)
  98. Common.logger(log_type, crawler).info(f"{'#' * 50}")
  99. Common.logger(log_type, crawler).info(type(res)) # 打印:<class 'numpy.ndarray'>
  100. Common.logger(log_type, crawler).info(res)
  101. # 打印:一个二维的ndarray数组
  102. # [[0.05604218 0.05557462 0.06844381... - 0.1784117 - 0.1811338 - 0.18415523]
  103. # [0.06151756 0.04408009 0.07010461... - 0.18493137 - 0.18440475 - 0.1843424]
  104. # [0.0643926 0.06221284 0.0719175... - 0.18742703 - 0.18535161 - 0.1823346]
  105. # ...
  106. # [-0.07755355 - 0.08177952 - 0.08642308... - 0.16476074 - 0.16210903 - 0.15467581]
  107. # [-0.06975575 - 0.07566144 - 0.07783117... - 0.1412715 - 0.15145643 - 0.14800543]
  108. # [-0.08476129 - 0.08415948 - 0.0949327... - 0.1371379 - 0.14271489 - 0.14166716]]
  109. Common.logger(log_type, crawler).info(f"{'#' * 50}")
  110. # cv22.minMaxLoc() 从ndarray数组中找到最小值、最大值及他们的坐标
  111. value = cv2.minMaxLoc(res)
  112. # 得到的value,如:(-0.1653602570295334, 0.6102921366691589, (144, 1), (141, 56))
  113. Common.logger(log_type, crawler).info(f"{value, '#' * 30}")
  114. # 获取x坐标,如上面的144、141
  115. return value[2:][0][0], value[2:][1][0]
  116. # 返回两个数组:一个用于加速拖动滑块,一个用于减速拖动滑块
  117. @staticmethod
  118. def generate_tracks(distance):
  119. # 给距离加上20,这20像素用在滑块滑过缺口后,减速折返回到缺口
  120. distance += 20
  121. v = 0
  122. t = 0.2
  123. forward_tracks = []
  124. current = 0
  125. mid = distance * 3 / 5 # 减速阀值
  126. while current < distance:
  127. if current < mid:
  128. a = 2 # 加速度为+2
  129. else:
  130. a = -3 # 加速度-3
  131. s = v * t + 0.5 * a * (t ** 2)
  132. v = v + a * t
  133. current += s
  134. forward_tracks.append(round(s))
  135. back_tracks = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1, -1]
  136. return forward_tracks, back_tracks
  137. # 获取距离值
  138. @staticmethod
  139. def get_tracks(distance):
  140. """
  141. 模拟人的滑动行为,先匀加速后匀减速
  142. 匀变速基本公式
  143. v=v0+at
  144. s=vot+1/2at2
  145. """
  146. # 初速度
  147. v = 0
  148. # 设置时间
  149. t = 0.3
  150. # 存储每段距离值
  151. tracks = []
  152. # 当前距离
  153. current = 0
  154. # 中间位置为4/5距离处
  155. mid = distance * 4 / 5
  156. while current < distance:
  157. if current < mid:
  158. # 加速阶段
  159. a = 2
  160. else:
  161. # 减速阶段
  162. a = -3
  163. # 当前速度
  164. v0 = v
  165. # 当前位移
  166. s = v0 * t + 0.5 * a * t ** 2
  167. # 更新当前速度
  168. v = v0 + a * t
  169. # 更新当前位移
  170. current += s
  171. # 添加到轨迹列表
  172. tracks.append(round(s))
  173. return tracks
  174. # 使用图像处理库(例如 OpenCV)找到滑块在背景图片中的位置
  175. @staticmethod
  176. def get_slider_offset(log_type, crawler, image_bg):
  177. image = cv2.imread(image_bg)
  178. # 高斯滤波
  179. blurred = cv2.GaussianBlur(image, (5, 5), 0)
  180. # 边缘检测
  181. canny = cv2.Canny(blurred, 200, 400)
  182. # 轮廓检测
  183. contours, hierarchy = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  184. for i, contour in enumerate(contours):
  185. m = cv2.moments(contour)
  186. if m['m00'] == 0:
  187. cx = cy = 0
  188. else:
  189. cx, cy = m['m10'] / m['m00'], m['m01'] / m['m00']
  190. if 6000 < cv2.contourArea(contour) < 8000 and 370 < cv2.arcLength(contour, True) < 390:
  191. if cx < 400:
  192. continue
  193. x, y, w, h = cv2.boundingRect(contour) # 外接矩形
  194. cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
  195. # cv.imshow('image', image) # 显示识别结果
  196. Common.logger(log_type, crawler).info('【缺口识别】 {x}px'.format(x=x / 2))
  197. return x / 2
  198. return 0
  199. @staticmethod
  200. def FindPic(log_type, crawler, target, template):
  201. """
  202. 找出图像中最佳匹配位置
  203. :param log_type: 日志
  204. :param crawler: 爬虫
  205. :param target: 目标即背景图
  206. :param template: 模板即需要找到的图
  207. :return: 返回最佳匹配及其最差匹配和对应的坐标
  208. """
  209. target_rgb = cv2.imread(target)
  210. target_gray = cv2.cvtColor(target_rgb, cv2.COLOR_BGR2GRAY)
  211. template_rgb = cv2.imread(template, 0)
  212. res = cv2.matchTemplate(target_gray, template_rgb, cv2.TM_CCOEFF_NORMED)
  213. value = cv2.minMaxLoc(res)
  214. Common.logger(log_type, crawler).info(value)
  215. # 计算缺口的 X 轴距离
  216. x_val = int(value[3][0])
  217. Common.logger(log_type, crawler).info(f"缺口的 X 轴距离:{x_val}")
  218. # 获取模板图的宽高
  219. template_height, template_width, template_c = cv2.imread(template).shape
  220. Common.logger(log_type, crawler).info(f"模板高:{template_height}")
  221. Common.logger(log_type, crawler).info(f"模板宽:{template_width}")
  222. Common.logger(log_type, crawler).info(f"图片的通道数:{template_c}")
  223. # 计算需要滑动的距离
  224. move_val = x_val - template_width
  225. Common.logger(log_type, crawler).info(f"需要滑动的距离:{move_val}")
  226. return x_val
  227. def login(self, log_type, crawler, env):
  228. # Common.logger(log_type, crawler).info("点击登录")
  229. # self.driver.find_element(By.XPATH, '//*[@class="xg-button xg-button-primary xg-button-middle loginButton"]').click()
  230. # time.sleep(random.randint(1, 2))
  231. # Common.logger(log_type, crawler).info("点击密码登录")
  232. # self.driver.find_element(By.XPATH, '//*[@class="web-login-link-list__item__text"]').click()
  233. # time.sleep(random.randint(1, 2))
  234. # Common.logger(log_type, crawler).info("输入手机号")
  235. # self.driver.find_element(By.XPATH, '//*[@class="web-login-normal-input__input"]').send_keys(self.username)
  236. # time.sleep(random.randint(1, 2))
  237. # Common.logger(log_type, crawler).info("输入密码")
  238. # self.driver.find_element(By.XPATH, '//*[@class="web-login-button-input__input"]').send_keys(self.password)
  239. # time.sleep(random.randint(1, 2))
  240. # Common.logger(log_type, crawler).info("点击登录")
  241. # self.driver.find_element(By.XPATH, '//*[@class="web-login-account-password__button-wrapper"]/*[1]').click()
  242. # time.sleep(random.randint(1, 2))
  243. # # 获取滑块
  244. # Common.logger(log_type, crawler).info("获取滑块")
  245. # move_btns = self.driver.find_elements(By.XPATH, '//*[@class="sc-kkGfuU bujTgx"]')
  246. # if len(move_btns) == 0:
  247. # Common.logger(log_type, crawler).info("未发现滑块,3-5 秒后重试")
  248. # self.quit(log_type, crawler)
  249. # time.sleep(random.randint(3, 5))
  250. # self.__init__(log_type, crawler, env)
  251. # self.login(log_type, crawler, env)
  252. # move_btn = move_btns[0]
  253. #
  254. # while True:
  255. #
  256. # # 使用requests下载滑块
  257. # slide_url = self.driver.find_element(By.XPATH,
  258. # '//*[@class="captcha_verify_img_slide react-draggable sc-VigVT ggNWOG"]').get_attribute(
  259. # "src")
  260. # slide_dir = f"./{crawler}/photo/img_slide.png"
  261. # urllib3.disable_warnings()
  262. # slide_url_response = requests.get(slide_url, verify=False)
  263. # with open(slide_dir, "wb") as file:
  264. # file.write(slide_url_response.content)
  265. #
  266. # # 使用urllib下载背景图
  267. # bg_image_url = self.driver.find_element(By.XPATH, '//*[@id="captcha-verify-image"]').get_attribute("src")
  268. # bg_image_dir = f"./{crawler}/photo/img_bg.png"
  269. # urllib3.disable_warnings()
  270. # bg_image_url_response = requests.get(bg_image_url, verify=False)
  271. # with open(bg_image_dir, "wb") as file:
  272. # file.write(bg_image_url_response.content)
  273. #
  274. # offset = self.FindPic(log_type, crawler, bg_image_dir, slide_dir)
  275. # Common.logger(log_type, crawler).info(f"offset:{offset}")
  276. #
  277. # # 在滑块上暂停
  278. # Common.logger(log_type, crawler).info("在滑块上暂停")
  279. # ActionChains(self.driver).click_and_hold(on_element=move_btn).perform()
  280. # # 拖动滑块
  281. # Common.logger(log_type, crawler).info("拖动滑块0.7*距离")
  282. # ActionChains(self.driver).move_to_element_with_offset(to_element=move_btn, xoffset=int(0.5*offset), yoffset=0).perform()
  283. # # 拖动剩余像素
  284. # Common.logger(log_type, crawler).info("拖动剩余像素")
  285. # tracks = self.get_tracks(int(0.15*offset))
  286. # # 遍历梅一段距离
  287. # for track in tracks:
  288. # # 滑块移动响应距离
  289. # ActionChains(self.driver).move_by_offset(xoffset=track, yoffset=0).perform()
  290. # # 休息1s
  291. # Common.logger(log_type, crawler).info("休息1s")
  292. # time.sleep(1)
  293. # # 释放滑块
  294. # Common.logger(log_type, crawler).info("释放滑块")
  295. # ActionChains(self.driver).release().perform()
  296. #
  297. # if len(move_btns) != 0:
  298. # time.sleep(1)
  299. # continue
  300. # break
  301. Common.logger(log_type, crawler).info("刷新页面")
  302. self.driver.refresh()
  303. Common.logger(log_type, crawler).info("关闭当前标签页")
  304. time.sleep(5)
  305. # 关闭当前标签页
  306. self.driver.find_element(By.TAG_NAME, 'body').send_keys(Keys.COMMAND + 'w')
  307. Common.logger(log_type, crawler).info("已关闭")
  308. Common.logger(log_type, crawler).info("退出浏览器")
  309. self.quit(log_type, crawler)
  310. if __name__ == "__main__":
  311. Recommend = XGRecommend("search", "dev", "dev")
  312. Recommend.login("search", "dev", "dev")
  313. pass