zoujie

Merge branch 'master' of ssh://gitlab.rinal-li.cn:10022/liufuhua/xj-marketing

# Conflicts:
#	cli_main.py
#	utils/base_social_media.py
...@@ -12,7 +12,41 @@ xj-marketing This project aims to automate the posting of videos to various soci ...@@ -12,7 +12,41 @@ xj-marketing This project aims to automate the posting of videos to various soci
12 - [x] 小红书 12 - [x] 小红书
13 - [x] 快手 13 - [x] 快手
14 - [ ] 百家号 14 - [ ] 百家号
15 - - [ ] 网易号 15 + - [ ] 哔哩哔哩
16 + - [ ] 腾讯视频
17 + - [ ] 西瓜视频
18 + - [ ] 搜狐视频
19 + - [ ] 爱奇艺
20 + - [ ] 皮皮虾
21 + - [ ] 多多视频
22 + - [ ] 美拍
23 + - [ ] 腾讯微视
24 + - [ ] AcFun
25 + - [ ] 秒拍
26 + - [ ] 小红书商家
27 + - [x] 头条号
28 + - [ ] 百家号
29 + - [ ] 搜狐号
30 + - [x] 网易号
31 + - [ ] 企鹅号
32 + - [ ] 知乎
33 + - [ ] 一点号
34 + - [ ] 大鱼号
35 + - [ ] 微信公众号
36 + - [ ] 新浪微博
37 + - [ ] 快传号
38 + - [ ] 雪球号
39 + - [ ] 豆瓣
40 + - [ ] 简书号
41 + - [ ] 东方号
42 + - [ ] 大风号
43 + - [ ] 蜂网
44 + - [ ] 美柚
45 + - [ ] WIFI万能钥匙
46 + - [ ] 易车号
47 + - [ ] 车家号
48 + - [ ] CSDN
49 + - [ ] 微博头条
16 50
17 - 部分国外社交媒体: 51 - 部分国外社交媒体:
18 - [x] tiktok 52 - [x] tiktok
......

11.3 KB

...@@ -10,8 +10,9 @@ from uploader.ks_uploader.main import ks_setup, KSVideo ...@@ -10,8 +10,9 @@ from uploader.ks_uploader.main import ks_setup, KSVideo
10 from uploader.tencent_uploader.main import weixin_setup, TencentVideo 10 from uploader.tencent_uploader.main import weixin_setup, TencentVideo
11 from uploader.tk_uploader.main_chrome import tiktok_setup, TiktokVideo 11 from uploader.tk_uploader.main_chrome import tiktok_setup, TiktokVideo
12 from uploader.wyh_uploader.main import wyh_setup 12 from uploader.wyh_uploader.main import wyh_setup
13 +from uploader.toutiao_uploader.main import toutiao_setup
13 from utils.base_social_media import get_supported_social_media, get_cli_action, SOCIAL_MEDIA_DOUYIN, \ 14 from utils.base_social_media import get_supported_social_media, get_cli_action, SOCIAL_MEDIA_DOUYIN, \
14 - SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK, SOCIAL_MEDIA_KUAISHOU, SOCIAL_MEDIA_WANGYIHAO, SOCIAL_MEDIA_XHS 15 + SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK, SOCIAL_MEDIA_KUAISHOU, SOCIAL_MEDIA_WANGYIHAO, SOCIAL_MEDIA_XHS, SOCIAL_MEDIA_TOUTIAO
15 from utils.constant import TencentZoneTypes 16 from utils.constant import TencentZoneTypes
16 from utils.files_times import get_title_and_hashtags 17 from utils.files_times import get_title_and_hashtags
17 18
...@@ -73,6 +74,8 @@ async def main(): ...@@ -73,6 +74,8 @@ async def main():
73 await wyh_setup(str(account_file), handle=True) 74 await wyh_setup(str(account_file), handle=True)
74 # elif args.platform == SOCIAL_MEDIA_XHS: 75 # elif args.platform == SOCIAL_MEDIA_XHS:
75 # await xhs_setup(str(account_file), handle=True) 76 # await xhs_setup(str(account_file), handle=True)
77 + elif args.platform == SOCIAL_MEDIA_TOUTIAO:
78 + await toutiao_setup(str(account_file), handle=True)
76 79
77 elif args.action == 'upload': 80 elif args.action == 'upload':
78 title, tags = get_title_and_hashtags(args.video_file) 81 title, tags = get_title_and_hashtags(args.video_file)
......
...@@ -6,4 +6,7 @@ cf_clearance ...@@ -6,4 +6,7 @@ cf_clearance
6 biliup 6 biliup
7 xhs 7 xhs
8 qrcode 8 qrcode
9 -loguru
...\ No newline at end of file ...\ No newline at end of file
9 +loguru
10 +opencv-python
11 +numpy
12 +pandas
...\ No newline at end of file ...\ No newline at end of file
......
1 +import os
2 +
3 +from utils.base_social_media import set_init_script
4 +from utils.files_times import get_absolute_path
5 +from utils.log import kuaishou_logger
6 +from playwright.async_api import Playwright, async_playwright
7 +
8 +
9 +async def toutiao_setup(account_file, handle=False):
10 + print(account_file)
11 + account_file = get_absolute_path(account_file, "wyh_uploader")
12 + if not os.path.exists(account_file):
13 + if not handle:
14 + return False
15 + kuaishou_logger.info('[+] cookie文件不存在或已失效,即将自动打开浏览器,请扫码登录,登陆后会自动生成cookie文件')
16 + await get_wyh_cookie(account_file)
17 + else:
18 + await open_wyh_main_page(account_file)
19 + return True
20 +
21 +
22 +async def open_wyh_main_page(account_file):
23 + async with async_playwright() as playwright:
24 + options = {
25 + 'args': [
26 + '--lang en-GB'
27 + ],
28 + 'headless': False, # Set headless option here
29 + }
30 + # Make sure to run headed.
31 + browser = await playwright.chromium.launch(**options)
32 + # Setup context however you like.
33 + context = await browser.new_context(storage_state=account_file) # Pass any options
34 + context = await set_init_script(context)
35 + # Pause the page, and start recording manually.
36 + page = await context.new_page()
37 + await page.goto("https://mp.toutiao.com")
38 + await page.pause()
39 + browser.close()
40 +
41 +
42 +async def get_wyh_cookie(account_file):
43 + print("get_wyh_cookie")
44 + async with async_playwright() as playwright:
45 + options = {
46 + 'args': [
47 + '--lang en-GB'
48 + ],
49 + 'headless': False, # Set headless option here
50 + }
51 + # Make sure to run headed.
52 + browser = await playwright.chromium.launch(**options)
53 + # Setup context however you like.
54 + context = await browser.new_context() # Pass any options
55 + context = await set_init_script(context)
56 + # Pause the page, and start recording manually.
57 + page = await context.new_page()
58 + await page.goto("https://mp.toutiao.com")
59 +
60 + # 手动授权登录
61 + # await page.pause()
62 + # 点击调试器的继续,保存cookie
63 + # await context.storage_state(path=account_file)
64 +
65 + # 自动登陆
66 + await page.wait_for_timeout(5000)
67 + await page.get_by_text('密码登录').click()
68 + await page.wait_for_timeout(5000)
69 +
70 + await page.get_by_placeholder("手机号/邮箱").fill('18610534668')
71 + print("输入账号成功")
72 + await page.wait_for_timeout(1000)
73 + await page.get_by_placeholder('密码').fill("Liuyihong1023@")
74 + await page.wait_for_timeout(1000)
75 + await page.locator("//*[@class='web-login-confirm-info__checkbox']").click()
76 + await page.wait_for_timeout(1000)
77 + await page.locator("//*[@class='web-login-button']").click()
78 + await page.wait_for_timeout(10000)
79 + # 点击调试器的继续,保存cookie
80 + await context.storage_state(path=account_file)
1 +import random
2 +import cv2
3 +import numpy as np
4 +import pandas as pd
5 +import math
6 +import os
7 +
8 +
9 +# x方向一阶导中值
10 +def get_dx_median(dx, x, y, w, h):
11 + return np.median(dx[y:(y + h), x])
12 +
13 +
14 +# 预处理
15 +def pre_process(img_path):
16 + img = cv2.imread(img_path, 1)
17 + img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转成灰度图像
18 + _, binary = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY) # 将灰度图像转成二值图像
19 + contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) # 查找轮廓
20 +
21 + rect_area = []
22 + rect_arc_length = []
23 + cnt_infos = {}
24 +
25 + for i, cnt in enumerate(contours):
26 + if cv2.contourArea(cnt) < 5000 or cv2.contourArea(cnt) > 25000:
27 + continue
28 +
29 + x, y, w, h = cv2.boundingRect(cnt)
30 + cnt_infos[i] = {'rect_area': w * h, # 矩形面积
31 + 'rect_arclength': 2 * (w + h), # 矩形周长
32 + 'cnt_area': cv2.contourArea(cnt), # 轮廓面积
33 + 'cnt_arclength': cv2.arcLength(cnt, True), # 轮廓周长
34 + 'cnt': cnt, # 轮廓
35 + 'w': w,
36 + 'h': h,
37 + 'x': x,
38 + 'y': y,
39 + 'mean': np.mean(np.min(img[y:(y + h), x:(x + w)], axis=2)), # 矩形内像素平均
40 + }
41 + rect_area.append(w * h)
42 + rect_arc_length.append(2 * (w + h))
43 + dx = cv2.Sobel(img, -1, 1, 0, ksize=5)
44 +
45 + return img, dx, cnt_infos
46 +
47 +
48 +def qq_mark_pos(img_path):
49 + if not os.path.exists(img_path):
50 + print("文件不存在")
51 + return 0
52 + img, dx, cnt_infos = pre_process(img_path)
53 + h, w = img.shape[:2]
54 + df = pd.DataFrame(cnt_infos).T
55 + df.head()
56 + df['dx_mean'] = df.apply(lambda x: get_dx_median(dx, x['x'], x['y'], x['w'], x['h']), axis=1)
57 + df['rect_ratio'] = df.apply(lambda v: v['rect_arclength'] / 4 / math.sqrt(v['rect_area'] + 1), axis=1)
58 + df['area_ratio'] = df.apply(lambda v: v['rect_area'] / v['cnt_area'], axis=1)
59 + df['score'] = df.apply(lambda x: abs(x['rect_ratio'] - 1), axis=1)
60 +
61 + result = df.query('x>0').query('area_ratio<2').query('rect_area>5000').query('rect_area<20000').sort_values(
62 + ['mean', 'score', 'dx_mean']).head(2)
63 + if len(result):
64 + x_left = result.x.values[0]
65 + # cv2.line(img, (x_left, 0), (x_left, h), color=(255, 0, 255))
66 + # plt.imshow(img)
67 + # plt.show()
68 + return result
69 +
70 +
71 +def get_track_list(distance):
72 + """
73 + 模拟轨迹 假装是人在操作
74 + """
75 + # 初速度
76 + v = 0
77 + # 单位时间为0.2s来统计轨迹,轨迹即0.2内的位移
78 + t = 0.2
79 + # 位移/轨迹列表,列表内的一个元素代表0.2s的位移
80 + tracks = []
81 + # 当前的位移
82 + current = 0
83 + # 到达mid值开始减速
84 + mid = distance * 7 / 8
85 +
86 + distance += 10 # 先滑过一点,最后再反着滑动回来
87 + # a = random.randint(1,3)
88 + while current < distance:
89 + if current < mid:
90 + # 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细
91 + a = random.randint(2, 4) # 加速运动
92 + else:
93 + a = -random.randint(3, 5) # 减速运动
94 +
95 + # 初速度
96 + v0 = v
97 + # 0.2秒时间内的位移
98 + s = v0 * t + 0.5 * a * (t ** 2)
99 + # 当前的位置
100 + current += s
101 + # 添加到轨迹列表
102 + tracks.append(round(s))
103 +
104 + # 速度已经达到v,该速度作为下次的初速度
105 + v = v0 + a * t
106 +
107 + # 反着滑动到大概准确位置
108 + for i in range(4):
109 + tracks.append(-random.randint(2, 3))
110 + for i in range(4):
111 + tracks.append(-random.randint(1, 3))
112 + return tracks
113 +
114 +
115 +# if __name__ == "__main__":
116 +# img_path = "bg.png"
117 +# if not os.path.exists(img_path):
118 +# print("文件不存在")
119 +# res = qq_mark_pos(img_path)
120 +# x = res.x.values[0]
121 +# x_r = 344 * x / 699
122 +# track = get_track_list(res.x.values[0])
123 +# print(f'x:{x},xr:{x_r}, track:{track}')
1 +import io
2 +import time
3 +from playwright.sync_api import sync_playwright, Route
4 +from CaptchaCv2 import (get_track_list, qq_mark_pos)
5 +
6 +distance = 0
7 +is_reflashed_img = False
8 +img = "bg.png"
9 +retryTimes = 10
10 +
11 +
12 +def handle_captcha(route: Route) -> None:
13 + print("handle captcha begin")
14 + response = route.fetch()
15 + if response.status == 200:
16 + print("handle captcha response is 200")
17 + buffer = response.body()
18 + # 下载指定规则url的验证码图片
19 + if "index=1" in response.url:
20 + is_reflashed_img = True
21 + with open(img, "wb") as f:
22 + f.write(buffer)
23 + route.continue_()
24 +
25 +
26 +def dragbox_location():
27 + for i in range(5):
28 + dragbox_bounding = page.frame_locator("#tcaptcha_iframe").locator(
29 + "#tcaptcha_drag_thumb").bounding_box()
30 + if dragbox_bounding is not None and dragbox_bounding["x"] > 20:
31 + return dragbox_bounding
32 + return None
33 +
34 +
35 +def drag_to_breach(move_distance):
36 + print('开始拖动滑块..')
37 + drag_box = dragbox_location()
38 + if drag_box is None:
39 + print('未获取到滑块位置,识别失败')
40 + return False
41 + page.mouse.move(drag_box["x"] + drag_box["width"] / 2,
42 + drag_box["y"] + drag_box["height"] / 2)
43 + page.mouse.down()
44 + location_x = drag_box["x"]
45 + for i in move_distance:
46 + location_x += i
47 + page.mouse.move(location_x, drag_box["y"])
48 + page.mouse.up()
49 + if page.get_by_text("后重试") is not None or page.get_by_text("请控制拼图对齐缺口") is not None:
50 + print("识别成功")
51 + return True
52 + print('识别失败')
53 + return False
54 +
55 +
56 +def calc_distance():
57 + for i in range(retryTimes):
58 + print(f"识别验证码距离中,当前等待轮数{i + 1}/{retryTimes}")
59 + try:
60 + res = qq_mark_pos(img)
61 + distance = res.x.values[0]
62 + if distance > 0:
63 + print(f"获取到缺口距离:{distance}")
64 + return distance
65 + except Exception as e:
66 + print(f"识别错误, 异常:{e}")
67 +
68 +
69 +"""demo sample"""
70 +with sync_playwright() as p:
71 + # browser = p.chromium.launch(channel="msedge",proxy={"server": "http://{}".format(proxy)})
72 + browser = p.chromium.launch(headless=False)
73 + iphone_12 = p.devices["iPhone 12"]
74 + context = browser.new_context(
75 + record_video_dir="videos-temp/",
76 + **iphone_12,
77 + )
78 + page = context.new_page()
79 + # 下载指定规则的验证码图片
80 + page.route("**/turing.captcha.qcloud.com/hycdn**", handle_captcha)
81 + page.goto(
82 + "https://wap.showstart.com/pages/passport/login/login?redirect=%252Fpages%252FmyHome%252FmyHome")
83 +
84 + page.wait_for_timeout(5000)
85 +
86 + page.get_by_role("spinbutton").fill("14445104596")
87 + page.get_by_text("获取验证码").click()
88 +
89 + frame = page.wait_for_selector("#tcaptcha_iframe")
90 + print(frame.bounding_box())
91 +
92 + page.wait_for_timeout(5000)
93 +
94 + move_distance = None
95 + for i in range(retryTimes):
96 + print(f"滑块拖动逻辑开始,当前尝试轮数{i + 1}/{retryTimes}")
97 +
98 + # 验证码刷新 重新计算距离
99 + if is_reflashed_img or move_distance is None:
100 + distance = calc_distance()
101 + page.wait_for_timeout(200)
102 +
103 + true_distance = distance * 353 / 680
104 + move_distance = get_track_list(true_distance)
105 + print(f"获取到相对滑动距离{true_distance}, 模拟拖动列表{move_distance}")
106 + is_reflashed_img = False
107 +
108 + drag_result = drag_to_breach(move_distance)
109 + if drag_result:
110 + break
111 +
112 + page.wait_for_timeout(3000)
113 + print("识别结束,退出程序")
114 + # input("为方便调试,可启用此代码,避免浏览器关闭")
115 + browser.close()
...\ No newline at end of file ...\ No newline at end of file
...@@ -10,10 +10,11 @@ SOCIAL_MEDIA_BILIBILI = "bilibili" ...@@ -10,10 +10,11 @@ SOCIAL_MEDIA_BILIBILI = "bilibili"
10 SOCIAL_MEDIA_KUAISHOU = "kuaishou" 10 SOCIAL_MEDIA_KUAISHOU = "kuaishou"
11 SOCIAL_MEDIA_WANGYIHAO = 'wangyihao' 11 SOCIAL_MEDIA_WANGYIHAO = 'wangyihao'
12 SOCIAL_MEDIA_XHS = 'xiaohongshu' 12 SOCIAL_MEDIA_XHS = 'xiaohongshu'
13 +SOCIAL_MEDIA_TOUTIAO = 'toutiao'
13 14
14 15
15 def get_supported_social_media() -> List[str]: 16 def get_supported_social_media() -> List[str]:
16 - return [SOCIAL_MEDIA_DOUYIN, SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK, SOCIAL_MEDIA_KUAISHOU,SOCIAL_MEDIA_WANGYIHAO] 17 + return [SOCIAL_MEDIA_DOUYIN, SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK, SOCIAL_MEDIA_KUAISHOU, SOCIAL_MEDIA_WANGYIHAO, SOCIAL_MEDIA_TOUTIAO]
17 18
18 19
19 def get_cli_action() -> List[str]: 20 def get_cli_action() -> List[str]:
......
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type