zhufuquanzi_recommend_new.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. # -*- coding: utf-8 -*-
  2. # @Time: 2023/11/17
  3. import json
  4. import os
  5. import random
  6. import sys
  7. import time
  8. import uuid
  9. from hashlib import md5
  10. from appium import webdriver
  11. from appium.webdriver.extensions.android.nativekey import AndroidKey
  12. from appium.webdriver.webdriver import WebDriver
  13. from bs4 import BeautifulSoup
  14. from selenium.common import NoSuchElementException
  15. from selenium.webdriver.common.by import By
  16. sys.path.append(os.getcwd())
  17. from common import AliyunLogger, PiaoQuanPipeline, get_redirect_url
  18. from common.common import Common
  19. from common.mq import MQ
  20. from common.scheduling_db import MysqlHelper
  21. class ZFQZRecommend:
  22. platform = "祝福圈子"
  23. download_cnt = 0
  24. element_list = []
  25. i = 0
  26. @classmethod
  27. def start_wechat(cls, log_type, crawler, env, rule_dict, our_uid):
  28. if env == "dev":
  29. chromedriverExecutable = "/Users/piaoquan/Downloads/chromedriver"
  30. else:
  31. chromedriverExecutable = "/Users/piaoquan/Downloads/chromedriver"
  32. Common.logger(log_type, crawler).info("启动微信")
  33. # Common.logging(log_type, crawler, env, '启动微信')
  34. caps = {
  35. "platformName": "Android",
  36. "devicesName": "Android",
  37. # "platformVersion": "11",
  38. # "udid": "emulator-5554",
  39. "appPackage": "com.tencent.mm",
  40. "appActivity": ".ui.LauncherUI",
  41. "autoGrantPermissions": "true",
  42. "noReset": True,
  43. "resetkeyboard": True,
  44. "unicodekeyboard": True,
  45. "showChromedriverLog": True,
  46. "printPageSourceOnFailure": True,
  47. "recreateChromeDriverSessions": True,
  48. "enableWebviewDetailsCollection": True,
  49. "setWebContentsDebuggingEnabled": True,
  50. "newCommandTimeout": 6000,
  51. "automationName": "UiAutomator2",
  52. "chromedriverExecutable": chromedriverExecutable,
  53. "chromeOptions": {"androidProcess": "com.tencent.mm:appbrand0"},
  54. }
  55. try:
  56. driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
  57. except Exception as e:
  58. AliyunLogger.logging(
  59. code="3002",
  60. platform=ZFQZRecommend.platform,
  61. mode=log_type,
  62. env=env,
  63. message="appium 启动异常, 报错原因是{}".format(e)
  64. )
  65. return
  66. driver.implicitly_wait(30)
  67. for i in range(120):
  68. try:
  69. if driver.find_elements(By.ID, "com.tencent.mm:id/f2s"):
  70. Common.logger(log_type, crawler).info("微信启动成功")
  71. AliyunLogger.logging(
  72. code="1000",
  73. platform=ZFQZRecommend.platform,
  74. mode=log_type,
  75. env=env,
  76. message="启动微信成功"
  77. )
  78. break
  79. elif driver.find_element(By.ID, "com.android.systemui:id/dismiss_view"):
  80. Common.logger(log_type, crawler).info("发现并关闭系统下拉菜单")
  81. AliyunLogger.logging(
  82. code="1000",
  83. platform=ZFQZRecommend.platform,
  84. mode=log_type,
  85. env=env,
  86. message="发现并关闭系统下拉菜单"
  87. )
  88. size = driver.get_window_size()
  89. driver.swipe(int(size['width'] * 0.5), int(size['height'] * 0.8),
  90. int(size['width'] * 0.5), int(size['height'] * 0.2), 200)
  91. # driver.find_element(By.ID, "com.android.system:id/dismiss_view").click()
  92. else:
  93. pass
  94. except NoSuchElementException:
  95. AliyunLogger.logging(
  96. code="3001",
  97. platform=ZFQZRecommend.platform,
  98. mode=log_type,
  99. env=env,
  100. message="打开微信异常"
  101. )
  102. time.sleep(1)
  103. Common.logger(log_type, crawler).info("下滑,展示小程序选择面板")
  104. AliyunLogger.logging(
  105. code="1000",
  106. platform=ZFQZRecommend.platform,
  107. mode=log_type,
  108. env=env,
  109. message="下滑,展示小程序选择面板"
  110. )
  111. size = driver.get_window_size()
  112. driver.swipe(int(size['width'] * 0.5), int(size['height'] * 0.2),
  113. int(size['width'] * 0.5), int(size['height'] * 0.8), 200)
  114. time.sleep(1)
  115. Common.logger(log_type, crawler).info('打开小程序"祝福圈子"')
  116. driver.find_elements(By.XPATH, '//*[@text="祝福圈子"]')[-1].click()
  117. AliyunLogger.logging(
  118. code="1000",
  119. platform=ZFQZRecommend.platform,
  120. mode=log_type,
  121. env=env,
  122. message='打开小程序"祝福圈子"成功'
  123. )
  124. time.sleep(5)
  125. cls.get_videoList(log_type, crawler, driver, env, rule_dict, our_uid)
  126. time.sleep(1)
  127. driver.quit()
  128. @classmethod
  129. def search_elements(cls, driver: WebDriver, xpath):
  130. time.sleep(1)
  131. windowHandles = driver.window_handles
  132. for handle in windowHandles:
  133. driver.switch_to.window(handle)
  134. time.sleep(1)
  135. try:
  136. elements = driver.find_elements(By.XPATH, xpath)
  137. if elements:
  138. return elements
  139. except NoSuchElementException:
  140. pass
  141. @classmethod
  142. def check_to_applet(cls, log_type, crawler, env, driver: WebDriver, xpath):
  143. time.sleep(1)
  144. webViews = driver.contexts
  145. driver.switch_to.context(webViews[-1])
  146. windowHandles = driver.window_handles
  147. for handle in windowHandles:
  148. driver.switch_to.window(handle)
  149. time.sleep(1)
  150. try:
  151. driver.find_element(By.XPATH, xpath)
  152. Common.logger(log_type, crawler).info("切换到小程序成功\n")
  153. return
  154. except NoSuchElementException:
  155. time.sleep(1)
  156. @classmethod
  157. def repeat_video(cls, log_type, crawler, video_id, env):
  158. sql = f""" select * from crawler_video where platform in ("众妙音信", "刚刚都传", "吉祥幸福", "知青天天看", "zhufuquanzi", "祝福圈子", "haitunzhufu", "海豚祝福") and out_video_id="{video_id}"; """
  159. repeat_video = MysqlHelper.get_values(log_type, crawler, sql, env)
  160. return len(repeat_video)
  161. @classmethod
  162. def swipe_up(cls, driver: WebDriver):
  163. cls.search_elements(driver, '//*[@class="bless--list"]')
  164. size = driver.get_window_size()
  165. driver.swipe(int(size["width"] * 0.5), int(size["height"] * 0.8),
  166. int(size["width"] * 0.5), int(size["height"] * 0.4), 200)
  167. @classmethod
  168. def get_video_url(cls, log_type, crawler, driver: WebDriver, video_title_element):
  169. for i in range(3):
  170. cls.search_elements(driver, '//*[@class="bless--list"]')
  171. Common.logger(log_type, crawler).info(f"video_title_element:{video_title_element[0]}")
  172. time.sleep(1)
  173. Common.logger(log_type, crawler).info("滑动标题至可见状态")
  174. driver.execute_script("arguments[0].scrollIntoView({block:'center',inline:'center'});", video_title_element[0])
  175. time.sleep(3)
  176. Common.logger(log_type, crawler).info("点击标题")
  177. video_title_element[0].click()
  178. # driver.execute_script("arguments[0].click();", video_title_element[0])
  179. Common.logger(log_type, crawler).info("点击标题完成")
  180. time.sleep(1)
  181. video_url_elements = cls.search_elements(driver, '//*[@class="index--video-item index--video"]')
  182. if video_url_elements:
  183. return video_url_elements[0].get_attribute("src")
  184. @classmethod
  185. def get_videoList(cls, log_type, crawler, driver: WebDriver, env, rule_dict, our_uid):
  186. mq = MQ(topic_name="topic_crawler_etl_" + env)
  187. driver.implicitly_wait(20)
  188. cls.check_to_applet(log_type=log_type, crawler=crawler, env=env, driver=driver,
  189. xpath='//*[@class="tags--tag tags--tag-0 tags--checked"]')
  190. time.sleep(1)
  191. name = ["推荐", "搞笑", "大雪", "亲子"]
  192. selected_text = random.choice(name)
  193. try:
  194. driver.find_element(By.XPATH, f"//wx-button[contains(., '{selected_text}')]").click()
  195. time.sleep(2)
  196. except NoSuchElementException:
  197. Common.logger(log_type, crawler).info(f"没有该tab:{selected_text}\n")
  198. pass
  199. page = 0
  200. while True:
  201. if cls.search_elements(driver, '//*[@class="bless--list"]') is None:
  202. Common.logger(log_type, crawler).info("窗口已销毁\n")
  203. AliyunLogger.logging(
  204. code="3000",
  205. platform=ZFQZRecommend.platform,
  206. mode=log_type,
  207. env=env,
  208. message='窗口已销毁'
  209. )
  210. cls.i = 0
  211. cls.download_cnt = 0
  212. cls.element_list = []
  213. return
  214. cls.swipe_up(driver)
  215. page_source = driver.page_source
  216. soup = BeautifulSoup(page_source, 'html.parser')
  217. soup.prettify()
  218. video_list_elements = soup.findAll("wx-view", class_="expose--adapt-parent")
  219. # video_list_elements 有,cls.element_list 中没有的元素
  220. video_list_elements = list(set(video_list_elements).difference(set(cls.element_list)))
  221. # video_list_elements 与 cls.element_list 的并集
  222. cls.element_list = list(set(video_list_elements) | set(cls.element_list))
  223. Common.logger(log_type, crawler).info(f"正在抓取第{page + 1}页,共:{len(video_list_elements)}条视频")
  224. AliyunLogger.logging(
  225. code="1000",
  226. platform=ZFQZRecommend.platform,
  227. mode=log_type,
  228. env=env,
  229. message=f"正在抓取第{page + 1}页,共:{len(video_list_elements)}条视频"
  230. )
  231. if len(video_list_elements) == 0:
  232. for i in range(10):
  233. Common.logger(log_type, crawler).info(f"向上滑动第{i+1}次")
  234. AliyunLogger.logging(
  235. code="1000",
  236. platform=ZFQZRecommend.platform,
  237. mode=log_type,
  238. env=env,
  239. message=f"向上滑动第{i+1}次"
  240. )
  241. cls.swipe_up(driver)
  242. time.sleep(0.5)
  243. continue
  244. for i, video_element in enumerate(video_list_elements):
  245. try:
  246. Common.logger(log_type, crawler).info(f"本轮已抓取{cls.download_cnt}条视频\n")
  247. AliyunLogger.logging(
  248. code="1000",
  249. platform=ZFQZRecommend.platform,
  250. mode=log_type,
  251. env=env,
  252. message=f"本轮已抓取{cls.download_cnt}条视频\n"
  253. )
  254. if cls.download_cnt >= int(rule_dict.get("videos_cnt", {}).get("min", 10)):
  255. cls.i = 0
  256. cls.download_cnt = 0
  257. cls.element_list = []
  258. return
  259. trace_id = crawler + str(uuid.uuid1())
  260. AliyunLogger.logging(
  261. code="1001",
  262. platform=ZFQZRecommend.platform,
  263. mode=log_type,
  264. env=env,
  265. trace_id=trace_id,
  266. message="扫描到一条视频",
  267. )
  268. cls.i += 1
  269. video_title = video_element.find("wx-view", class_="dynamic--title").text
  270. play_str = video_element.find("wx-view", class_="dynamic--views").text
  271. like_str = video_element.findAll("wx-view", class_="dynamic--commerce-btn-text")[0].text
  272. comment_str = video_element.findAll("wx-view", class_="dynamic--commerce-btn-text")[1].text
  273. duration_str = video_element.find("wx-view", class_="dynamic--duration").text
  274. user_name = video_element.find("wx-view", class_="dynamic--nick-top").text
  275. avatar_url = video_element.find("wx-image", class_="avatar--avatar")["src"]
  276. cover_url = video_element.find("wx-image", class_="dynamic--bg-image")["src"]
  277. play_cnt = int(play_str.replace("+", "").replace("次播放", ""))
  278. duration = int(duration_str.split(":")[0].strip()) * 60 + int(duration_str.split(":")[-1].strip())
  279. if "点赞" in like_str:
  280. like_cnt = 0
  281. elif "万" in like_str:
  282. like_cnt = int(like_str.split("万")[0]) * 10000
  283. else:
  284. like_cnt = int(like_str)
  285. if "评论" in comment_str:
  286. comment_cnt = 0
  287. elif "万" in comment_str:
  288. comment_cnt = int(comment_str.split("万")[0]) * 10000
  289. else:
  290. comment_cnt = int(comment_str)
  291. out_video_id = md5(video_title.encode('utf8')).hexdigest()+selected_text
  292. out_user_id = md5(user_name.encode('utf8')).hexdigest()
  293. Common.logger(log_type, crawler).warning(f"视频标题:{video_title},点赞:{like_str},播放:{play_cnt},用户名称:{user_name},")
  294. video_dict = {
  295. "video_title": video_title,
  296. "video_id": out_video_id,
  297. 'out_video_id': out_video_id,
  298. "duration_str": duration_str,
  299. "duration": duration,
  300. "play_str": play_str,
  301. "play_cnt": play_cnt,
  302. "like_str": like_str,
  303. "like_cnt": like_cnt,
  304. "comment_cnt": comment_cnt,
  305. "share_cnt": 0,
  306. "user_name": user_name,
  307. "user_id": out_user_id,
  308. 'publish_time_stamp': int(time.time()),
  309. 'publish_time_str': time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()))),
  310. 'update_time_stamp': int(time.time()),
  311. "avatar_url": avatar_url,
  312. "cover_url": cover_url,
  313. "session": f"zhufuquanzi-{int(time.time())}"
  314. }
  315. pipeline = PiaoQuanPipeline(
  316. platform=ZFQZRecommend.platform,
  317. mode=log_type,
  318. item=video_dict,
  319. rule_dict=rule_dict,
  320. env=env,
  321. trace_id=trace_id
  322. )
  323. flag = pipeline.process_item()
  324. if flag:
  325. video_title_element = cls.search_elements(driver, f'//*[contains(text(), "{video_title}")]')
  326. if video_title_element is None:
  327. Common.logger(log_type, crawler).warning(f"未找到该视频标题的element:{video_title_element}")
  328. continue
  329. Common.logger(log_type, crawler).info("点击标题,进入视频详情页")
  330. AliyunLogger.logging(
  331. code="1000",
  332. platform=ZFQZRecommend.platform,
  333. mode=log_type,
  334. env=env,
  335. message=f"点击标题,进入视频详情页\n"
  336. )
  337. video_url = cls.get_video_url(log_type, crawler, driver, video_title_element)
  338. video_url = get_redirect_url(video_url)
  339. if video_url is None:
  340. driver.press_keycode(AndroidKey.BACK)
  341. time.sleep(5)
  342. continue
  343. video_dict['video_url'] = video_url
  344. Common.logger(log_type, crawler).info(f"video_url:{video_url}")
  345. video_dict["platform"] = crawler
  346. video_dict["strategy"] = log_type
  347. video_dict["out_video_id"] = video_dict["video_id"]
  348. video_dict["crawler_rule"] = json.dumps(rule_dict)
  349. video_dict["user_id"] = our_uid
  350. video_dict["publish_time"] = video_dict["publish_time_str"]
  351. mq.send_msg(video_dict)
  352. cls.download_cnt += 1
  353. driver.press_keycode(AndroidKey.BACK)
  354. time.sleep(5)
  355. cls.swipe_up(driver)
  356. except Exception as e:
  357. Common.logger(log_type, crawler).error(f"抓取单条视频异常:{e}\n")
  358. driver.press_keycode(AndroidKey.BACK)
  359. AliyunLogger.logging(
  360. code="3001",
  361. platform=ZFQZRecommend.platform,
  362. mode=log_type,
  363. env=env,
  364. message=f"抓取单条视频异常:{e}\n"
  365. )
  366. Common.logger(log_type, crawler).info("已抓取完一组,休眠 5 秒\n")
  367. AliyunLogger.logging(
  368. code="1000",
  369. platform=ZFQZRecommend.platform,
  370. mode=log_type,
  371. env=env,
  372. message="已抓取完一组,休眠 5 秒\n"
  373. )
  374. time.sleep(5)
  375. page += 1
  376. if __name__ == "__main__":
  377. rule_dict1 = {"period": {"min": 365, "max": 365},
  378. "duration": {"min": 30, "max": 1800},
  379. "favorite_cnt": {"min": 5000, "max": 0},
  380. "videos_cnt": {"min": 10, "max": 20},
  381. "share_cnt": {"min": 1000, "max": 0}}
  382. ZFQZRecommend.start_wechat("recommend", "zhufuquanzi", "dev", rule_dict1, 6267141)