shipinshuashua.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. # -*- coding: utf-8 -*-
  2. # @Author: luojunhui
  3. # @Time: 2023/12/25
  4. import json
  5. import os
  6. import random
  7. import sys
  8. import time
  9. import uuid
  10. from hashlib import md5
  11. from appium import webdriver
  12. from appium.webdriver.extensions.android.nativekey import AndroidKey
  13. from bs4 import BeautifulSoup
  14. from selenium.common.exceptions import NoSuchElementException
  15. from selenium.webdriver.common.by import By
  16. sys.path.append(os.getcwd())
  17. from application.common.log import AliyunLogger, Local
  18. from application.common.messageQueue import MQ
  19. from application.functions import get_redirect_url
  20. from application.pipeline import PiaoQuanPipeline
  21. class SPSSRecommend:
  22. def __init__(self, log_type, crawler, env, rule_dict, our_uid):
  23. self.mq = MQ(topic_name="topic_crawler_etl_" + env)
  24. self.platform = "shipinshuashua"
  25. self.download_cnt = 0
  26. self.element_list = []
  27. self.count = 0
  28. self.swipe_count = 0
  29. self.log_type = log_type
  30. self.crawler = crawler
  31. self.env = env
  32. self.rule_dict = rule_dict
  33. self.our_uid_list = our_uid
  34. chromedriverExecutable = "/Users/luojunhui/chromedriver/chromedriver_v116/chromedriver"
  35. self.aliyun_log = AliyunLogger(platform=crawler, mode=log_type, env=env)
  36. Local.logger(self.log_type, self.crawler).info("启动微信")
  37. # 微信的配置文件
  38. caps = {
  39. "platformName": "Android",
  40. "devicesName": "Android",
  41. "appPackage": "com.tencent.mm",
  42. "appActivity": ".ui.LauncherUI",
  43. "autoGrantPermissions": True,
  44. "noReset": True,
  45. "resetkeyboard": True,
  46. "unicodekeyboard": True,
  47. "showChromedriverLog": True,
  48. "printPageSourceOnFailure": True,
  49. "recreateChromeDriverSessions": True,
  50. "enableWebviewDetailsCollection": True,
  51. "setWebContentsDebuggingEnabled": True,
  52. "newCommandTimeout": 6000,
  53. "automationName": "UiAutomator2",
  54. "chromedriverExecutable": chromedriverExecutable,
  55. "chromeOptions": {"androidProcess": "com.tencent.mm:appbrand0"},
  56. }
  57. try:
  58. self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
  59. except Exception as e:
  60. print(e)
  61. self.aliyun_log.logging(
  62. code="3002",
  63. message=f'appium 启动异常: {e}'
  64. )
  65. return
  66. self.driver.implicitly_wait(30)
  67. for i in range(120):
  68. try:
  69. if self.driver.find_elements(By.ID, "com.tencent.mm:id/f2s"):
  70. Local.logger(self.log_type, self.crawler).info("微信启动成功")
  71. # Common.logging(self.log_type, self.crawler, self.env, '微信启动成功')
  72. self.aliyun_log.logging(
  73. code="1000",
  74. message="启动微信成功"
  75. )
  76. break
  77. elif self.driver.find_element(By.ID, "com.android.systemui:id/dismiss_view"):
  78. Local.logger(self.log_type, self.crawler).info("发现并关闭系统下拉菜单")
  79. # Common.logging(self.log_type, self.crawler, self.env, '发现并关闭系统下拉菜单')
  80. self.aliyun_log.logging(
  81. code="1000",
  82. message="发现并关闭系统下拉菜单"
  83. )
  84. size = self.driver.get_window_size()
  85. self.driver.swipe(int(size['width'] * 0.5), int(size['height'] * 0.8),
  86. int(size['width'] * 0.5), int(size['height'] * 0.2), 200)
  87. else:
  88. pass
  89. except NoSuchElementException:
  90. self.aliyun_log.logging(
  91. code="3001",
  92. message="打开微信异常"
  93. )
  94. time.sleep(1)
  95. Local.logger(self.log_type, self.crawler).info("下滑,展示小程序选择面板")
  96. size = self.driver.get_window_size()
  97. self.driver.swipe(int(size['width'] * 0.5), int(size['height'] * 0.2),
  98. int(size['width'] * 0.5), int(size['height'] * 0.8), 200)
  99. time.sleep(1)
  100. Local.logger(self.log_type, self.crawler).info('打开小程序"视频刷刷"')
  101. self.driver.find_elements(By.XPATH, '//*[@text="视频刷刷"]')[-1].click()
  102. self.aliyun_log.logging(
  103. code="1000",
  104. message="打开小程序 视频刷刷 成功"
  105. )
  106. time.sleep(5)
  107. print("打开小程序")
  108. self.get_videoList()
  109. time.sleep(1)
  110. self.driver.quit()
  111. def search_elements(self, xpath):
  112. time.sleep(1)
  113. windowHandles = self.driver.window_handles
  114. for handle in windowHandles:
  115. self.driver.switch_to.window(handle)
  116. time.sleep(1)
  117. try:
  118. elements = self.driver.find_elements(By.XPATH, xpath)
  119. if elements:
  120. return elements
  121. except NoSuchElementException:
  122. pass
  123. def check_to_applet(self, xpath):
  124. time.sleep(1)
  125. webViews = self.driver.contexts
  126. self.driver.switch_to.context(webViews[-1])
  127. windowHandles = self.driver.window_handles
  128. for handle in windowHandles:
  129. self.driver.switch_to.window(handle)
  130. time.sleep(1)
  131. try:
  132. self.driver.find_element(By.XPATH, xpath)
  133. Local.logger(self.log_type, self.crawler).info("切换到WebView成功\n")
  134. # Common.logging(self.log_type, self.crawler, self.env, '切换到WebView成功\n')
  135. self.aliyun_log.logging(
  136. code="1000",
  137. message="成功切换到 webview"
  138. )
  139. return
  140. except NoSuchElementException:
  141. time.sleep(1)
  142. def swipe_up(self):
  143. self.search_elements('//*[@class="dynamic--title-container"]')
  144. size = self.driver.get_window_size()
  145. self.driver.swipe(int(size["width"] * 0.5), int(size["height"] * 0.8),
  146. int(size["width"] * 0.5), int(size["height"] * 0.442), 200)
  147. self.swipe_count += 1
  148. def get_video_url(self, video_title_element):
  149. for i in range(3):
  150. self.search_elements('//*[@class="dynamic--title-container"]')
  151. Local.logger(self.log_type, self.crawler).info(f"video_title_element:{video_title_element[0]}")
  152. time.sleep(1)
  153. Local.logger(self.log_type, self.crawler).info("滑动标题至可见状态")
  154. self.driver.execute_script("arguments[0].scrollIntoView({block:'center',inline:'center'});",
  155. video_title_element[0])
  156. time.sleep(3)
  157. Local.logger(self.log_type, self.crawler).info("点击标题")
  158. video_title_element[0].click()
  159. self.check_to_applet(xpath=r'//wx-video[@class="infos--title infos--ellipsis"]')
  160. Local.logger(self.log_type, self.crawler).info("点击标题完成")
  161. time.sleep(10)
  162. video_url_elements = self.search_elements(
  163. '//wx-video[@class="dynamic-index--video-item dynamic-index--video"]')
  164. if video_url_elements:
  165. return video_url_elements[0].get_attribute("src")
  166. def parse_detail(self, index):
  167. page_source = self.driver.page_source
  168. soup = BeautifulSoup(page_source, 'html.parser')
  169. soup.prettify()
  170. video_list = soup.findAll(name="wx-view", attrs={"class": "expose--adapt-parent"})
  171. element_list = [i for i in video_list][index:]
  172. return element_list[0]
  173. def get_video_info_2(self, video_element):
  174. Local.logger(self.log_type, self.crawler).info(f"本轮已抓取{self.download_cnt}条视频\n")
  175. # Common.logging(self.log_type, self.crawler, self.env, f"本轮已抓取{self.download_cnt}条视频\n")
  176. if self.download_cnt >= int(self.rule_dict.get("videos_cnt", {}).get("min", 10)):
  177. self.count = 0
  178. self.download_cnt = 0
  179. self.element_list = []
  180. return
  181. self.count += 1
  182. Local.logger(self.log_type, self.crawler).info(f"第{self.count}条视频")
  183. # 获取 trace_id, 并且把该 id 当做视频生命周期唯一索引
  184. trace_id = self.crawler + str(uuid.uuid1())
  185. self.aliyun_log.logging(
  186. code="1001",
  187. trace_id=trace_id,
  188. message="扫描到一条视频",
  189. )
  190. # 标题
  191. video_title = video_element.find("wx-view", class_="dynamic--title").text
  192. # 播放量字符串
  193. play_str = video_element.find("wx-view", class_="dynamic--views").text
  194. # 视频时长
  195. duration_str = video_element.find("wx-view", class_="dynamic--duration").text
  196. user_name = video_element.find("wx-view", class_="dynamic--nick-top").text
  197. # 头像 URL
  198. avatar_url = video_element.find("wx-image", class_="avatar--avatar")["src"]
  199. # 封面 URL
  200. cover_url = video_element.find("wx-image", class_="dynamic--bg-image")["src"]
  201. play_cnt = int(play_str.replace("+", "").replace("次播放", ""))
  202. duration = int(duration_str.split(":")[0].strip()) * 60 + int(duration_str.split(":")[-1].strip())
  203. out_video_id = md5(video_title.encode('utf8')).hexdigest()
  204. out_user_id = md5(user_name.encode('utf8')).hexdigest()
  205. video_dict = {
  206. "video_title": video_title,
  207. "video_id": out_video_id,
  208. 'out_video_id': out_video_id,
  209. "duration_str": duration_str,
  210. "duration": duration,
  211. "play_str": play_str,
  212. "play_cnt": play_cnt,
  213. "like_str": "",
  214. "like_cnt": 0,
  215. "comment_cnt": 0,
  216. "share_cnt": 0,
  217. "user_name": user_name,
  218. "user_id": out_user_id,
  219. 'publish_time_stamp': int(time.time()),
  220. 'publish_time_str': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()))),
  221. 'update_time_stamp': int(time.time()),
  222. "avatar_url": avatar_url,
  223. "cover_url": cover_url,
  224. "session": f"piaopiiaoquan-{int(time.time())}"
  225. }
  226. print(video_dict)
  227. pipeline = PiaoQuanPipeline(
  228. platform=self.crawler,
  229. mode=self.log_type,
  230. item=video_dict,
  231. rule_dict=self.rule_dict,
  232. env=self.env,
  233. trace_id=trace_id
  234. )
  235. flag = pipeline.process_item()
  236. if flag:
  237. video_title_element = self.search_elements(f'//*[contains(text(), "{video_title}")]')
  238. if video_title_element is None:
  239. return
  240. Local.logger(self.log_type, self.crawler).info("点击标题,进入视频详情页")
  241. self.aliyun_log.logging(
  242. code="1000",
  243. message="点击标题,进入视频详情页",
  244. )
  245. video_url = self.get_video_url(video_title_element)
  246. video_url = get_redirect_url(video_url)
  247. if video_url is None:
  248. self.driver.press_keycode(AndroidKey.BACK)
  249. time.sleep(5)
  250. return
  251. video_dict['video_url'] = video_url
  252. video_dict["platform"] = self.crawler
  253. video_dict["strategy"] = self.log_type
  254. video_dict["out_video_id"] = video_dict["video_id"]
  255. video_dict["crawler_rule"] = json.dumps(self.rule_dict)
  256. video_dict["user_id"] = random.choice(self.our_uid_list)
  257. video_dict["publish_time"] = video_dict["publish_time_str"]
  258. self.mq.send_msg(video_dict)
  259. self.aliyun_log.logging(
  260. code="1002",
  261. message="成功发送至ETL",
  262. data=video_dict
  263. )
  264. self.download_cnt += 1
  265. self.driver.press_keycode(AndroidKey.BACK)
  266. time.sleep(5)
  267. def get_video_info(self, video_element):
  268. try:
  269. self.get_video_info_2(video_element)
  270. except Exception as e:
  271. self.driver.press_keycode(AndroidKey.BACK)
  272. Local.logger(self.log_type, self.crawler).error(f"抓取单条视频异常:{e}\n")
  273. self.aliyun_log.logging(
  274. code="3001",
  275. message=f"抓取单条视频异常:{e}\n"
  276. )
  277. def get_videoList(self):
  278. self.driver.implicitly_wait(20)
  279. # 切换到 web_view
  280. self.check_to_applet(xpath='//*[@class="expose--adapt-parent"]')
  281. print("切换到 webview 成功")
  282. time.sleep(1)
  283. page = 0
  284. if self.search_elements('//*[@class="expose--adapt-parent"]') is None:
  285. Local.logger(self.log_type, self.crawler).info("窗口已销毁\n")
  286. # Common.logging(self.log_type, self.crawler, self.env, '窗口已销毁\n')
  287. self.aliyun_log.logging(
  288. code="3000",
  289. message="窗口已销毁"
  290. )
  291. self.count = 0
  292. self.download_cnt = 0
  293. self.element_list = []
  294. return
  295. print("开始获取视频信息")
  296. for i in range(50):
  297. print("下滑{}次".format(i))
  298. element = self.parse_detail(i)
  299. self.get_video_info(element)
  300. self.swipe_up()
  301. time.sleep(1)
  302. if self.swipe_count > 100:
  303. return
  304. print("下滑完成")
  305. # time.sleep(100)
  306. Local.logger(self.log_type, self.crawler).info("已抓取完一组,休眠 5 秒\n")
  307. # Common.logging(self.log_type, self.crawler, self.env, "已抓取完一组,休眠 5 秒\n")
  308. self.aliyun_log.logging(
  309. code="1000",
  310. message="已抓取完一组,休眠 5 秒\n",
  311. )
  312. time.sleep(5)