xgms_recommend.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. # -*- coding: utf-8 -*-
  2. # @Author: wangkun
  3. # @Time: 2023/7/10
  4. import base64
  5. import datetime
  6. import json
  7. import os
  8. import random
  9. import string
  10. import sys
  11. import time
  12. import requests
  13. import urllib3
  14. import re
  15. from requests.adapters import HTTPAdapter
  16. from selenium import webdriver
  17. from selenium.webdriver import DesiredCapabilities
  18. from selenium.webdriver.chrome.service import Service
  19. from selenium.webdriver.chrome.webdriver import WebDriver
  20. from selenium.webdriver.common.by import By
  21. sys.path.append(os.getcwd())
  22. from common.mq import MQ
  23. from common.feishu import Feishu
  24. from common.public import download_rule, get_config_from_mysql
  25. from common.common import Common
  26. from common.scheduling_db import MysqlHelper
  27. from common.userAgent import get_random_user_agent
  28. class XiguaRecommend:
  29. platform = "xigua"
  30. @classmethod
  31. def random_signature(cls):
  32. src_digits = string.digits # string_数字
  33. src_uppercase = string.ascii_uppercase # string_大写字母
  34. src_lowercase = string.ascii_lowercase # string_小写字母
  35. digits_num = random.randint(1, 6)
  36. uppercase_num = random.randint(1, 26 - digits_num - 1)
  37. lowercase_num = 26 - (digits_num + uppercase_num)
  38. password = random.sample(src_digits, digits_num) + random.sample(src_uppercase, uppercase_num) + random.sample(
  39. src_lowercase, lowercase_num)
  40. random.shuffle(password)
  41. new_password = 'AAAAAAAAAA' + ''.join(password)[10:-4] + 'AAAB'
  42. new_password_start = new_password[0:18]
  43. new_password_end = new_password[-7:]
  44. if new_password[18] == '8':
  45. new_password = new_password_start + 'w' + new_password_end
  46. elif new_password[18] == '9':
  47. new_password = new_password_start + 'x' + new_password_end
  48. elif new_password[18] == '-':
  49. new_password = new_password_start + 'y' + new_password_end
  50. elif new_password[18] == '.':
  51. new_password = new_password_start + 'z' + new_password_end
  52. else:
  53. new_password = new_password_start + 'y' + new_password_end
  54. return new_password
  55. @classmethod
  56. def get_video_url(cls, video_info):
  57. video_url_dict = {}
  58. video_resource = video_info.get('videoResource', {})
  59. dash_120fps = video_resource.get('dash_120fps', {})
  60. normal = video_resource.get('normal', {})
  61. # 从dash_120fps和normal字典中获取video_list字典
  62. video_list = dash_120fps.get('video_list', {}) or normal.get('video_list', {})
  63. # 获取video_list字典中的video_4、video_3、video_2或video_1的值。如果找到非空视频URL,则将其赋值给变量video_url。否则,将赋值为空字符串。
  64. video = video_list.get('video_4') or video_list.get('video_3') or video_list.get('video_2') or video_list.get('video_1')
  65. video_url = video.get('backup_url_1', '') if video else ''
  66. audio_url = video.get('backup_url_1', '') if video else ''
  67. video_width = video.get('vwidth', 0) if video else 0
  68. video_height = video.get('vheight', 0) if video else 0
  69. video_url = re.sub(r'[^a-zA-Z0-9+/=]', '', video_url) # 从视频URL中删除特殊字符
  70. audio_url = re.sub(r'[^a-zA-Z0-9+/=]', '', audio_url) # 从音频URL中删除特殊字符
  71. video_url = base64.b64decode(video_url).decode('utf8') # 解码视频URL
  72. audio_url = base64.b64decode(audio_url).decode('utf8') # 解码音频URL
  73. video_url_dict["video_url"] = video_url
  74. video_url_dict["audio_url"] = audio_url
  75. video_url_dict["video_width"] = video_width
  76. video_url_dict["video_height"] = video_height
  77. return video_url_dict
  78. @classmethod
  79. def get_comment_cnt(cls, item_id):
  80. url = "https://www.ixigua.com/tlb/comment/article/v5/tab_comments/?"
  81. params = {
  82. "tab_index": "0",
  83. "count": "10",
  84. "offset": "10",
  85. "group_id": str(item_id),
  86. "item_id": str(item_id),
  87. "aid": "1768",
  88. "msToken": "50-JJObWB07HfHs-BMJWT1eIDX3G-6lPSF_i-QwxBIXE9VVa-iN0jbEXR5pG2DKjXBmP299n6ZTuXzY-GAy968CCvouSAYIS4GzvGQT3pNlKNejr5G4-1g==",
  89. "X-Bogus": "DFSzswVOyGtANVeWtCLMqR/F6q9U",
  90. "_signature": cls.random_signature(),
  91. }
  92. headers = {
  93. 'authority': 'www.ixigua.com',
  94. 'accept': 'application/json, text/plain, */*',
  95. 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
  96. 'cache-control': 'no-cache',
  97. 'cookie': 'MONITOR_WEB_ID=67cb5099-a022-4ec3-bb8e-c4de6ba51dd0; passport_csrf_token=72b2574f3c99f8ba670e42df430218fd; passport_csrf_token_default=72b2574f3c99f8ba670e42df430218fd; sid_guard=c7472b508ea631823ba765a60cf8757f%7C1680867422%7C3024002%7CFri%2C+12-May-2023+11%3A37%3A04+GMT; uid_tt=c13f47d51767f616befe32fb3e9f485a; uid_tt_ss=c13f47d51767f616befe32fb3e9f485a; sid_tt=c7472b508ea631823ba765a60cf8757f; sessionid=c7472b508ea631823ba765a60cf8757f; sessionid_ss=c7472b508ea631823ba765a60cf8757f; sid_ucp_v1=1.0.0-KGUzNWYxNmRkZGJiZjgxY2MzZWNkMTEzMTkwYjY1Yjg5OTY5NzVlNmMKFQiu3d-eqQIQ3oDAoQYYGCAMOAhACxoCaGwiIGM3NDcyYjUwOGVhNjMxODIzYmE3NjVhNjBjZjg3NTdm; ssid_ucp_v1=1.0.0-KGUzNWYxNmRkZGJiZjgxY2MzZWNkMTEzMTkwYjY1Yjg5OTY5NzVlNmMKFQiu3d-eqQIQ3oDAoQYYGCAMOAhACxoCaGwiIGM3NDcyYjUwOGVhNjMxODIzYmE3NjVhNjBjZjg3NTdm; odin_tt=b893608d4dde2e1e8df8cd5d97a0e2fbeafc4ca762ac72ebef6e6c97e2ed19859bb01d46b4190ddd6dd17d7f9678e1de; SEARCH_CARD_MODE=7168304743566296612_0; support_webp=true; support_avif=false; csrf_session_id=a5355d954d3c63ed1ba35faada452b4d; tt_scid=7Pux7s634-z8DYvCM20y7KigwH5u7Rh6D9C-RROpnT.aGMEcz6Vsxp.oai47wJqa4f86; ttwid=1%7CHHtv2QqpSGuSu8r-zXF1QoWsvjmNi1SJrqOrZzg-UCY%7C1683858689%7Ca5223fe1500578e01e138a0d71d6444692018296c4c24f5885af174a65873c95; ixigua-a-s=3; msToken=50-JJObWB07HfHs-BMJWT1eIDX3G-6lPSF_i-QwxBIXE9VVa-iN0jbEXR5pG2DKjXBmP299n6ZTuXzY-GAy968CCvouSAYIS4GzvGQT3pNlKNejr5G4-1g==; __ac_nonce=0645dcbf0005064517440; __ac_signature=_02B4Z6wo00f01FEGmAwAAIDBKchzCGqn-MBRJpyAAHAjieFC5GEg6gGiwz.I4PRrJl7f0GcixFrExKmgt6QI1i1S-dQyofPEj2ugWTCnmKUdJQv-wYuDofeKNe8VtMtZq2aKewyUGeKU-5Ud21; ixigua-a-s=3',
  98. 'pragma': 'no-cache',
  99. 'referer': f'https://www.ixigua.com/{item_id}?logTag=3c5aa86a8600b9ab8540',
  100. 'sec-ch-ua': '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
  101. 'sec-ch-ua-mobile': '?0',
  102. 'sec-ch-ua-platform': '"macOS"',
  103. 'sec-fetch-dest': 'empty',
  104. 'sec-fetch-mode': 'cors',
  105. 'sec-fetch-site': 'same-origin',
  106. 'tt-anti-token': 'cBITBHvmYjEygzv-f9c78c1297722cf1f559c74b084e4525ce4900bdcf9e8588f20cc7c2e3234422',
  107. 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35',
  108. 'x-secsdk-csrf-token': '000100000001f8e733cf37f0cd255a51aea9a81ff7bc0c09490cfe41ad827c3c5c18ec809279175e4d9f5553d8a5'
  109. }
  110. urllib3.disable_warnings()
  111. s = requests.session()
  112. # max_retries=3 重试3次
  113. s.mount('http://', HTTPAdapter(max_retries=3))
  114. s.mount('https://', HTTPAdapter(max_retries=3))
  115. response = s.get(url=url, headers=headers, params=params, verify=False, proxies=Common.tunnel_proxies(),
  116. timeout=5)
  117. response.close()
  118. if response.status_code != 200 or 'total_number' not in response.json() or response.json() == {}:
  119. return 0
  120. return response.json().get("total_number", 0)
  121. # 获取视频详情
  122. @classmethod
  123. def get_video_info(cls, log_type, crawler, item_id):
  124. url = 'https://www.ixigua.com/api/mixVideo/information?'
  125. headers = {
  126. "accept-encoding": "gzip, deflate",
  127. "accept-language": "zh-CN,zh-Hans;q=0.9",
  128. "user-agent": get_random_user_agent('pc'),
  129. "referer": "https://www.ixigua.com/7102614741050196520?logTag=0531c88ac04f38ab2c62",
  130. }
  131. params = {
  132. 'mixId': str(item_id),
  133. 'msToken': 'IlG0wd0Pylyw9ghcYiB2YseUmTwrsrqqhXrbIcsSaTcLTJyVlbYJzk20zw3UO-CfrfC'
  134. 'NVVIOBNjIl7vfBoxnVUwO9ZyzAI3umSKsT5-pef_RRfQCJwmA',
  135. 'X-Bogus': 'DFSzswVupYTANCJOSBk0P53WxM-r',
  136. '_signature': '_02B4Z6wo0000119LvEwAAIDCuktNZ0y5wkdfS7jAALThuOR8D9yWNZ.EmWHKV0WSn6Px'
  137. 'fPsH9-BldyxVje0f49ryXgmn7Tzk-swEHNb15TiGqa6YF.cX0jW8Eds1TtJOIZyfc9s5emH7gdWN94',
  138. }
  139. cookies = {
  140. 'ixigua-a-s': '1',
  141. 'msToken': 'IlG0wd0Pylyw9ghcYiB2YseUmTwrsrqqhXrbIcsSaTcLTJyVlbYJzk20zw3UO-CfrfCNVVIOB'
  142. 'NjIl7vfBoxnVUwO9ZyzAI3umSKsT5-pef_RRfQCJwmA',
  143. 'ttwid': '1%7C_yXQeHWwLZgCsgHClOwTCdYSOt_MjdOkgnPIkpi-Sr8%7C1661241238%7Cf57d0c5ef3f1d7'
  144. '6e049fccdca1ac54887c34d1f8731c8e51a49780ff0ceab9f8',
  145. 'tt_scid': 'QZ4l8KXDG0YAEaMCSbADdcybdKbUfG4BC6S4OBv9lpRS5VyqYLX2bIR8CTeZeGHR9ee3',
  146. 'MONITOR_WEB_ID': '0a49204a-7af5-4e96-95f0-f4bafb7450ad',
  147. '__ac_nonce': '06304878000964fdad287',
  148. '__ac_signature': '_02B4Z6wo00f017Rcr3AAAIDCUVxeW1tOKEu0fKvAAI4cvoYzV-wBhq7B6D8k0no7lb'
  149. 'FlvYoinmtK6UXjRIYPXnahUlFTvmWVtb77jsMkKAXzAEsLE56m36RlvL7ky.M3Xn52r9t1IEb7IR3ke8',
  150. 'ttcid': 'e56fabf6e85d4adf9e4d91902496a0e882',
  151. '_tea_utm_cache_1300': 'undefined',
  152. 'support_avif': 'false',
  153. 'support_webp': 'false',
  154. 'xiguavideopcwebid': '7134967546256016900',
  155. 'xiguavideopcwebid.sig': 'xxRww5R1VEMJN_dQepHorEu_eAc',
  156. }
  157. urllib3.disable_warnings()
  158. s = requests.session()
  159. # max_retries=3 重试3次
  160. s.mount('http://', HTTPAdapter(max_retries=3))
  161. s.mount('https://', HTTPAdapter(max_retries=3))
  162. response = s.get(url=url, headers=headers, params=params, cookies=cookies, verify=False,
  163. proxies=Common.tunnel_proxies(), timeout=5)
  164. response.close()
  165. if response.status_code != 200 or 'data' not in response.json() or response.json()['data'] == {}:
  166. Common.logger(log_type, crawler).warning(f"get_video_info:{response.status_code}, {response.text}\n")
  167. return None
  168. else:
  169. video_info = response.json()['data'].get("gidInformation", {}).get("packerData", {}).get("video", {})
  170. if video_info == {}:
  171. return None
  172. video_dict = {
  173. "video_title": video_info.get("title", ""),
  174. "video_id": video_info.get("videoResource", {}).get("vid", ""),
  175. "gid": str(item_id),
  176. "play_cnt": int(video_info.get("video_watch_count", 0)),
  177. "like_cnt": int(video_info.get("video_like_count", 0)),
  178. "comment_cnt": int(cls.get_comment_cnt(item_id)),
  179. "share_cnt": 0,
  180. "favorite_cnt": 0,
  181. "duration": int(video_info.get("video_duration", 0)),
  182. "video_width": int(cls.get_video_url(video_info)["video_width"]),
  183. "video_height": int(cls.get_video_url(video_info)["video_height"]),
  184. "publish_time_stamp": int(video_info.get("video_publish_time", 0)),
  185. "publish_time_str": time.strftime("%Y-%m-%d %H:%M:%S",
  186. time.localtime(int(video_info.get("video_publish_time", 0)))),
  187. "user_name": video_info.get("user_info", {}).get("name", ""),
  188. "user_id": str(video_info.get("user_info", {}).get("user_id", "")),
  189. "avatar_url": str(video_info.get("user_info", {}).get("avatar_url", "")),
  190. "cover_url": video_info.get("poster_url", ""),
  191. "audio_url": cls.get_video_url(video_info)["audio_url"],
  192. "video_url": cls.get_video_url(video_info)["video_url"],
  193. "session": f"xigua-search-{int(time.time())}"
  194. }
  195. return video_dict
  196. @classmethod
  197. def repeat_video(cls, log_type, crawler, video_id, env):
  198. sql = f""" select * from crawler_video where platform in ("{crawler}","{cls.platform}") and out_video_id="{video_id}"; """
  199. repeat_video = MysqlHelper.get_values(log_type, crawler, sql, env)
  200. return len(repeat_video)
  201. @classmethod
  202. def quit(cls, log_type, crawler, env, driver: WebDriver):
  203. Common.logger(log_type, crawler).info("退出浏览器")
  204. Common.logging(log_type, crawler, env, "退出浏览器")
  205. driver.quit()
  206. quit_cmd = "ps aux | grep Chrome | grep -v grep | awk '{print $2}' | xargs kill -9"
  207. os.system(quit_cmd)
  208. @classmethod
  209. def get_videoList(cls, log_type, crawler, our_uid, rule_dict, env):
  210. mq = MQ(topic_name="topic_crawler_etl_" + env)
  211. Common.logger(log_type, crawler).info("启动 Chrome 浏览器")
  212. Common.logging(log_type, crawler, env, "启动 Chrome 浏览器")
  213. # kill 所有 Chrome 进程
  214. quit_cmd = "ps aux | grep Chrome | grep -v grep | awk '{print $2}' | xargs kill -9"
  215. os.system(quit_cmd)
  216. time.sleep(1)
  217. # 启动 Chrome,指定端口号:12306
  218. cmd = 'open -a "Google Chrome" --args --remote-debugging-port=12306'
  219. os.system(cmd)
  220. # 打印请求配置
  221. ca = DesiredCapabilities.CHROME
  222. ca["goog:loggingPrefs"] = {"performance": "ALL"}
  223. # 配置 chromedriver
  224. if env == "dev":
  225. chromedriver = "/Users/wangkun/Downloads/chromedriver/chromedriver_v114/chromedriver"
  226. else:
  227. # chromedriver = "/usr/bin/chromedriver"
  228. chromedriver = "/Users/piaoquan/Downloads/chromedriver/chromedriver_v114/chromedriver"
  229. # 初始化浏览器
  230. browser = webdriver.ChromeOptions()
  231. # browser.add_argument(f'--proxy-server={Common.tunnel_proxies()}') # 代理的IP地址和端口号
  232. browser.add_experimental_option("debuggerAddress", "127.0.0.1:12306")
  233. # driver初始化
  234. driver = webdriver.Chrome(desired_capabilities=ca, options=browser, service=Service(chromedriver))
  235. driver.implicitly_wait(10)
  236. Common.logger(log_type, crawler).info("打开西瓜推荐页")
  237. Common.logging(log_type, crawler, env, "打开西瓜推荐页")
  238. driver.get(f"https://www.ixigua.com/")
  239. time.sleep(2)
  240. # 检查登录状态
  241. if len(driver.find_elements(By.XPATH, '//*[@class="BU-Component-Header-Avatar__image"]')) == 0:
  242. Common.logger(log_type, crawler).info("登录失效")
  243. Common.logging(log_type, crawler, env, "登录失效")
  244. driver.get_screenshot_as_file(f"./{crawler}/photos/logon_err.png")
  245. # # 登录失效,报警
  246. # if 20 >= datetime.datetime.now().hour >= 10:
  247. # Feishu.bot(log_type, crawler, "西瓜推荐,登录失效")
  248. return
  249. videoList_elements = driver.find_elements(By.XPATH, '//*[@class="HorizontalFeedCard HorizontalChannelBlockList__item"]')
  250. if len(videoList_elements) == 0:
  251. Common.logger(log_type, crawler).info("到底啦~~~~~~~~~~\n")
  252. Common.logging(log_type, crawler, env, "到底啦~~~~~~~~~~\n")
  253. cls.quit(log_type, crawler, env, driver)
  254. return
  255. for i, video_element in enumerate(videoList_elements):
  256. Common.logger(log_type, crawler).info(f"正在抓取第{i+1}条视频")
  257. Common.logging(log_type, crawler, env, f"正在抓取第{i+1}条视频")
  258. item_id = video_element.find_elements(By.XPATH, '//*[@class="HorizontalFeedCard__coverWrapper disableZoomAnimation"]')[i].get_attribute("href")
  259. item_id = item_id.replace("https://www.ixigua.com/", "").replace("?&", "")
  260. Common.logger(log_type, crawler).info(f"item_id:{item_id}")
  261. video_dict = cls.get_video_info(log_type, crawler, item_id)
  262. if video_dict is None:
  263. Common.logger(log_type, crawler).info("无效视频\n")
  264. Common.logging(log_type, crawler, env, "无效视频\n")
  265. continue
  266. for k, v in video_dict.items():
  267. Common.logger(log_type, crawler).info(f"{k}:{v}")
  268. Common.logging(log_type, crawler, env, f"{video_dict}")
  269. if download_rule(log_type=log_type, crawler=crawler, video_dict=video_dict, rule_dict=rule_dict) is False:
  270. Common.logger(log_type, crawler).info("不满足抓取规则\n")
  271. Common.logging(log_type, crawler, env, "不满足抓取规则\n")
  272. elif any(str(word) if str(word) in video_dict["video_title"] else False
  273. for word in get_config_from_mysql(log_type=log_type,
  274. source=crawler,
  275. env=env,
  276. text="filter",
  277. action="")) is True:
  278. Common.logger(log_type, crawler).info('已中过滤词\n')
  279. Common.logging(log_type, crawler, env, '已中过滤词\n')
  280. elif cls.repeat_video(log_type, crawler, video_dict["video_id"], env) != 0:
  281. Common.logger(log_type, crawler).info('视频已下载\n')
  282. Common.logging(log_type, crawler, env, '视频已下载\n')
  283. else:
  284. video_dict["out_user_id"] = video_dict["user_id"]
  285. video_dict["platform"] = crawler
  286. video_dict["strategy"] = log_type
  287. video_dict["strategy_type"] = "ms"
  288. video_dict["out_video_id"] = video_dict["video_id"]
  289. video_dict["width"] = video_dict["video_width"]
  290. video_dict["height"] = video_dict["video_height"]
  291. video_dict["crawler_rule"] = json.dumps(rule_dict)
  292. video_dict["user_id"] = our_uid
  293. video_dict["publish_time"] = video_dict["publish_time_str"]
  294. mq.send_msg(video_dict)
  295. cls.quit(log_type, crawler, env, driver)
  296. if __name__ == "__main__":
  297. XiguaRecommend.get_videoList(log_type="recommend2",
  298. crawler="xigua",
  299. our_uid=6267140,
  300. rule_dict={},
  301. env="dev")
  302. pass