user_sign_poc.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. import json
  2. import time
  3. import uiautomator2 as u2
  4. import httpx
  5. from datetime import datetime, timedelta
  6. import subprocess
  7. from tools import loggerKit
  8. from difflib import SequenceMatcher
  9. class shell_status_content:
  10. def __init__(self, execute_status, error_code, error_msg, result, content):
  11. """
  12. :rtype: object
  13. """
  14. # 0失败 1成功
  15. self.execute_status = execute_status
  16. self.error_code = error_code
  17. self.error_msg = error_msg
  18. self.result = result
  19. self.content = content
  20. def to_dict(self):
  21. return {
  22. 'execute_status': self.execute_status,
  23. 'error_code': self.error_code,
  24. 'error_msg': self.error_msg,
  25. 'result': self.result,
  26. 'content': self.content
  27. }
  28. # 回复详情
  29. class content_detail:
  30. def __init__(self, keyword, title, reply, account_name):
  31. """
  32. :rtype: object
  33. """
  34. # 搜索关键词
  35. self.keyword = keyword
  36. self.title = title
  37. self.reply = reply
  38. self.account_name = account_name
  39. def to_dict(self):
  40. return {
  41. 'keyword': self.keyword,
  42. 'title': self.title,
  43. 'reply': self.reply,
  44. 'accountName': self.account_name
  45. }
  46. # 懂车帝 v7.8.3
  47. # 签到操作
  48. def simulated_operation(device_serial, keyword, task_id, media_channel):
  49. cmd_conn = f"adb connect {device_serial}"
  50. conn_output = subprocess.check_output(cmd_conn, shell=True)
  51. print(f"{conn_output}")
  52. d = u2.connect(device_serial)
  53. d.healthcheck()
  54. d.screen_on()
  55. d.unlock()
  56. d.debug = False
  57. # 跳过开屏广告
  58. d.watcher.when("以后再说").click()
  59. # 跳过升级弹框
  60. d.watcher.when("以后再说").click()
  61. # 开始后台监控
  62. # 默认监控间隔2.0s
  63. d.watcher.start(1.0)
  64. d.app_stop("com.ss.android.auto")
  65. d.app_start('com.ss.android.auto', use_monkey=True)
  66. loggerKit.info("懂车帝养号" + d.device_info['serial'] + ", 开始操作:{0}, {1},{2}", device_serial,
  67. task_id, media_channel)
  68. time.sleep(5)
  69. # 进入我的获取账号信息
  70. d.xpath('//*[@resource-id="android:id/tabs"]/android.widget.RelativeLayout[5]').click()
  71. time.sleep(2)
  72. account_name = d.xpath('//*[@resource-id="com.ss.android.auto:id/goi"]').get_text()
  73. target_text = '签到'
  74. xpath = f'//*[contains(@text, "{target_text}")]'
  75. if d.xpath(xpath).exists:
  76. print("找到了")
  77. d.xpath(xpath).click()
  78. if d.xpath('//*[@text="签到"]').exists:
  79. d.xpath('//*[@text="签到"]').click()
  80. status = shell_status_content(1, 0, '', '', '')
  81. return status
  82. # 查询到用户主页寻找帖子
  83. def find_user(d, user_name, keyword, account_name, task_id):
  84. time.sleep(3)
  85. if not d.xpath('//*[@resource-id="com.ss.android.auto:id/h1k"]').exists:
  86. status = shell_status_content(0, 501, '该帖子类型不支持/搜索用户页面错误', '', '')
  87. d.app_stop("com.ss.android.auto")
  88. return status
  89. # 赋值搜索框 用户名称
  90. d.xpath('//*[@resource-id="com.ss.android.auto:id/h1k"]').set_text(user_name)
  91. # 点击搜索
  92. d.xpath('//*[@resource-id="com.ss.android.auto:id/g61"]').click()
  93. time.sleep(3)
  94. # time.sleep(2)
  95. # d.xpath('//*[@text="小视频"]').click_exists()
  96. if d.xpath('//*[@text="二手车"]').exists:
  97. d(text='二手车').drag_to(text='综合', timeout=3)
  98. else:
  99. d(text='图片').drag_to(text='综合', timeout=3)
  100. # time.sleep(1)
  101. # d.xpath('//*[@text="车友圈"]').click_exists()
  102. #
  103. # time.sleep(1)
  104. # d.xpath('//*[@text="问答"]').click_exists()
  105. time.sleep(1)
  106. d.xpath('//*[@text="用户"]').click()
  107. time.sleep(3)
  108. xpath = '//*[@text="{}"]'
  109. user_name_list = d.xpath(xpath.format(user_name)).all()
  110. # 根据用户名获取元素 搜索框一定有一个 所以<=1 则表示未搜索到用户
  111. if user_name_list.__len__() <= 1:
  112. # 当前用户没有获取到任何帖子
  113. status = shell_status_content(0, 501, '未搜索到当前用户', '', '')
  114. return status
  115. else:
  116. # 懂车帝可能根据搜索算法 推出 是否仍要搜索目标选项, 这种情况对下标为2的进行点击。 正常情况对下标为1的进行点击
  117. if not d.xpath('//*[@text="”的搜索结果。仍然搜索:"]').exists:
  118. user_name_list[1].click()
  119. elif user_name_list.__len__() >= 3:
  120. user_name_list[2].click()
  121. # if user_name_list.__len__() > 1:
  122. # user_name_list[user_name_list.__len__() - 1].click()
  123. # else:
  124. # d.xpath(xpath.format(user_name)).click()
  125. # 点击第一个用户进入首页
  126. # d.xpath('//*[@resource-id="root"]/android.view.View[2]/android.view.View[1]/android.view.View[1]').click_exists()
  127. time.sleep(2)
  128. # 如果存在关注用户标签 进行关闭
  129. check_follow_button(d)
  130. # 获取帖子发布时间
  131. if not d.xpath('//*[@resource-id="com.ss.android.auto:id/tv_time"]').exists:
  132. # 当前用户没有获取到任何帖子
  133. status = shell_status_content(0, 501, '当前用户没有获取到任何帖子', '', '')
  134. return status
  135. # 预期时间
  136. transfer_date = datetime.strptime('2023-10-01', '%Y-%m-%d').date()
  137. while 1:
  138. # 如果存在关注用户标签 进行关闭
  139. check_follow_button(d)
  140. publish_time = d.xpath('//*[@resource-id="com.ss.android.auto:id/tv_time"]').get_text()
  141. publish_time = __formate_time(publish_time)
  142. # 发布时间
  143. publish_date = datetime.strptime(publish_time, '%Y-%m-%d').date()
  144. # 获取到所有帖子标题进行模糊匹配
  145. text_list = d.xpath('//*[@resource-id="com.ss.android.auto:id/s"]').all()
  146. for text in text_list:
  147. matcher = SequenceMatcher(None, text.text, keyword)
  148. similarity_ratio = matcher.ratio()
  149. if similarity_ratio >= 0.8:
  150. text.click()
  151. loggerKit.info("任务{0},用户{1}下搜索到{2}对应帖子 进行点击并操作", task_id, account_name, keyword)
  152. return title_action(d, keyword, account_name, task_id)
  153. # 如果能够根据标题搜索到帖子
  154. # if d.xpath(xpath.format(keyword)).exists:
  155. # d.xpath(xpath.format(keyword)).click()
  156. # return title_action(d, keyword, account_name)
  157. # 先判断是否超过10月1号
  158. if transfer_date > publish_date:
  159. # 已经抓取到10月1号帖子 仍未获取到该帖子
  160. print('已经获取到10月1号帖子')
  161. status = shell_status_content(0, 501, '已经抓取到10月1号帖子 仍未获取到该帖子,不进行下一步获取', '', '')
  162. return status
  163. elif d.xpath('//*[@text="没有更多了"]').exists:
  164. # 没有更多帖子
  165. print('没有更多了展示')
  166. status = shell_status_content(0, 501, "用户拉取到'没有更多了'标识,仍未获取到帖子", '', '')
  167. return status
  168. else:
  169. d.swipe_ext("up", 1)
  170. def check_follow_button(d):
  171. # 如果存在关注用户标签 进行关闭
  172. if d.xpath('//*[@resource-id="com.ss.android.auto:id/byg"]').exists:
  173. d.xpath('//*[@resource-id="com.ss.android.auto:id/byg"]').click()
  174. def __formate_time(time_str):
  175. now = datetime.now()
  176. if '刚刚' in time_str:
  177. p_time = now
  178. elif '分钟' in time_str:
  179. minutes = int(time_str[:time_str.index('分')])
  180. p_time = now - timedelta(minutes=minutes)
  181. elif '小时' in time_str:
  182. hours = int(time_str[:time_str.index('小')])
  183. p_time = now - timedelta(hours=hours)
  184. elif '昨天' in time_str:
  185. p_time = now - timedelta(days=1)
  186. elif '前天' in time_str:
  187. p_time = now - timedelta(days=2)
  188. elif '天' in time_str:
  189. days = int(time_str[:time_str.index('天')])
  190. p_time = now - timedelta(days=days)
  191. elif '周' in time_str:
  192. weeks = int(time_str[:time_str.index('周') - 1])
  193. p_time = now - timedelta(weeks=weeks)
  194. elif '年' not in time_str and '月' in time_str and '日' in time_str:
  195. p_time = datetime.strptime(f'{now.year}年{time_str}', "%Y年%m月%d日").date()
  196. else:
  197. time_str = time_str.split()[0]
  198. items = time_str.split("-")
  199. if len(items) == 2:
  200. p_time = datetime.strptime(f'{now.year}年{items[0]}月{items[1]}日', "%Y年%m月%d日").date()
  201. elif len(items) == 3:
  202. p_time = datetime.strptime(f'{items[0]}年{items[1]}月{items[2]}日', "%Y年%m月%d日").date()
  203. else:
  204. return None
  205. #
  206. p_time = p_time.strftime('%Y-%m-%d')
  207. return p_time
  208. # 进入帖子进行操作
  209. def title_action(d, keyword, account_name, task_id):
  210. time.sleep(3)
  211. # 目前只支持对帖子进行操作
  212. if not d.xpath('//*[@resource-id="com.ss.android.auto:id/ff6"]').exists and not d.xpath(
  213. '//*[@resource-id="com.ss.android.auto:id/d6l"]').exists:
  214. # 可能受网络/帖子内容影响 元素未加载出来 等待三秒再重试加载
  215. time.sleep(3)
  216. if not retry(lambda: d.xpath('//*[@resource-id="com.tencent.wework:id/ff6"]').exists, task_id):
  217. # if not d.xpath('//*[@resource-id="com.ss.android.auto:id/ff6"]').exists:
  218. # 获取评论内容,首先获取原文内容如果没有获取到,则获取标题
  219. status = shell_status_content(0, 501, "该文章类型不支持操作", '', '')
  220. d.app_stop("com.ss.android.auto")
  221. return status
  222. if d.xpath('//*[@text="视频"]').exists:
  223. # 返回未找到帖子
  224. status = shell_status_content(0, 501, '该帖子为视频类型不支持评论', '', '')
  225. d.app_stop("com.ss.android.auto")
  226. return status
  227. comment_request_text = ''
  228. if d.xpath('//*[@resource-id="com.ss.android.auto:id/s"]').exists:
  229. comment_request_text = d.xpath('//*[@resource-id="com.ss.android.auto:id/s"]').get_text()
  230. elif d.xpath('//*[@resource-id="com.ss.android.auto:id/izg"]').exists:
  231. comment_request_text = d.xpath('//*[@resource-id="com.ss.android.auto:id/izg"]').get_text()
  232. elif d.xpath('//*[@resource-id="com.ss.android.auto:id/p"]').exists:
  233. comment_request_text = d.xpath('//*[@resource-id="com.ss.android.auto:id/p"]').get_text()
  234. else:
  235. d.swipe_ext("up", 0.3)
  236. text_view_text = ''
  237. view_view_text = ''
  238. # 获取文章
  239. article_list = d.xpath('//android.widget.TextView').all()
  240. if article_list is None or article_list.__len__() == 0:
  241. text_view_text = keyword
  242. else:
  243. # if article_list.__len__() > 3:
  244. # article_list = article_list[3:]
  245. for article in article_list:
  246. # if article.text.contains('说点什么'):
  247. # break
  248. text_view_text += article.text
  249. # 获取文章
  250. article_list = d.xpath('//android.view.View').all()
  251. if article_list is None or article_list.__len__() == 0:
  252. view_view_text = keyword
  253. else:
  254. # if article_list.__len__() > 3:
  255. # article_list = article_list[3:]
  256. for article in article_list:
  257. # if article.text.contains('说点什么'):
  258. # break
  259. view_view_text += article.text
  260. if len(view_view_text) > len(text_view_text):
  261. comment_request_text = view_view_text
  262. else:
  263. comment_request_text = text_view_text
  264. request_data = {
  265. "templateId": "dcd_comment",
  266. "content": comment_request_text
  267. }
  268. response = httpx.post('http://47.116.62.124:3000/comment', json=request_data, timeout=120)
  269. if not response.is_success:
  270. # 调用AIGC获取评论失败
  271. status = shell_status_content(0, 500, "调用AIGC获取评论失败", '', '')
  272. d.app_stop("com.ss.android.auto")
  273. return status
  274. data = json.loads(response.text)
  275. reply_text = data.get('text')
  276. reply_text_json = json.loads(reply_text)
  277. reply = reply_text_json.get('comment')
  278. if '内容太少' in reply or '对不起' in reply or reply is None or reply == '':
  279. # 返回未获取合适回复内容
  280. status = shell_status_content(0, 500, "无效评论,请求为:" + comment_request_text + "回复为:" + reply, '', '')
  281. d.app_stop("com.ss.android.auto")
  282. return status
  283. # 点赞
  284. if d.xpath('//*[@resource-id="com.ss.android.auto:id/d6l"]').exists:
  285. like_list = d.xpath('//*[@resource-id="com.ss.android.auto:id/d6l"]').all()
  286. if like_list.__len__() == 1:
  287. like_list[0].click()
  288. else:
  289. like_list[like_list.__len__() - 1].click()
  290. else:
  291. like_list = d.xpath('//*[@resource-id="com.ss.android.auto:id/ff6"]').all()
  292. if like_list.__len__() == 1:
  293. like_list[0].click()
  294. else:
  295. like_list[like_list.__len__() - 1].click()
  296. time.sleep(2)
  297. # 点击对话框
  298. d.xpath('//*[@resource-id="com.ss.android.auto:id/kl2"]').click()
  299. time.sleep(2)
  300. # 评论框赋值
  301. d.xpath('//*[@resource-id="com.ss.android.auto:id/daj"]').set_text(reply)
  302. time.sleep(2)
  303. # 发布
  304. d.xpath('//*[@resource-id="com.ss.android.auto:id/fx_"]').click()
  305. time.sleep(2)
  306. if d.xpath('//*[@text="发布评论"]').exists:
  307. d.xpath('//*[@text="发布评论"]').click()
  308. content_result = content_detail(keyword, comment_request_text, reply, account_name)
  309. status = shell_status_content(1, 0, "", '', json.dumps(content_result.to_dict(), ensure_ascii=False))
  310. return status
  311. # 机器人重复查找元素 针对网络不顺畅的情况 或手机性能问题
  312. def retry(operation, device_serial, retries=11, retry_interval=3):
  313. for i in range(retries):
  314. loggerKit.info("懂车帝互动获取多次获取元素" + device_serial + "开始操作")
  315. if operation():
  316. return True
  317. if i == retries - 1:
  318. return False
  319. time.sleep(retry_interval) # 等待2秒再重试
  320. return False
  321. if __name__ == "__main__":
  322. # search_test('QXNUT21905001550','新车 售18.99万元,定位纯电中大型轿车,飞凡F7都市版正式上市')
  323. # while 1:
  324. #
  325. json_str = '''{
  326. "data": [
  327. {
  328. "taskId": 1163119,
  329. "mediaChannel": "dongchedi",
  330. "taskType": "testPOC",
  331. "taskSubType": "dongchediAppointInteraction",
  332. "taskSequenceId": "",
  333. "taskDesc": "懂车帝指定账号互动",
  334. "actionType": "content",
  335. "resourceName": "为何飞凡广州车展带来的两道“开胃菜”,让人越吃越“舒适”?",
  336. "subResourceName": null,
  337. "executeRobotAccount": "testRobotAccount",
  338. "executeRobotName": "testRobotAccount",
  339. "deviceId": "1",
  340. "content": "",
  341. "answerType": null,
  342. "materialUri": null,
  343. "spiderType": null,
  344. "sequence": null,
  345. "demoTask": false
  346. },
  347. {
  348. "taskId": 1163119,
  349. "mediaChannel": "dongchedi",
  350. "taskType": "testPOC",
  351. "taskSubType": "dongchediAppointInteraction",
  352. "taskSequenceId": "",
  353. "taskDesc": "懂车帝指定账号互动",
  354. "actionType": "content",
  355. "resourceName": "为何飞凡广州车展带来的两道“开胃菜”,让人越吃越“舒适”?",
  356. "subResourceName": null,
  357. "executeRobotAccount": "testRobotAccount",
  358. "executeRobotName": "testRobotAccount",
  359. "deviceId": "1",
  360. "content": "",
  361. "answerType": null,
  362. "materialUri": null,
  363. "spiderType": null,
  364. "sequence": null,
  365. "demoTask": false
  366. }
  367. ],
  368. "code": "00000000",
  369. "message": "success"
  370. }'''
  371. data_list = json.loads(json_str).get('data')
  372. for data in data_list:
  373. print(data.get('taskId'))
  374. status = simulated_operation("7HX5T19911019170", "感受飞凡F7最快的一次充电", 199, 'dongchedi', 'Lightman317')
  375. loggerKit.info(json.dumps(status, default=lambda o: o.__dict__, indent=4, ensure_ascii=False))