pookz@stme commited on
Commit
b843170
·
1 Parent(s): 5525396

1. add xhs

Browse files

2. change douyin third-pard(checkbox)
3. add douyin demo
4. add group-qr

README.MD CHANGED
@@ -1,6 +1,7 @@
1
  # social-auto-upload
2
  social-auto-upload 该项目旨在自动化发布视频到各个社交媒体平台
3
 
 
4
  ## 💡Feature
5
  - 中国主流社交媒体平台:
6
  - 抖音
@@ -25,9 +26,15 @@ pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
25
  playwright install chromium
26
  ```
27
 
 
 
 
 
 
 
28
  # 核心模块解释
29
 
30
- ## 视频文件准备
31
  filepath 本地视频目录,目录包含
32
  - 视频文件
33
  - 视频meta信息txt文件
@@ -43,7 +50,9 @@ meta_file 内容:
43
  #坚持不懈 #爱情执着 #奋斗使者 #短视频
44
  ```
45
 
46
- ### 抖音
 
 
47
  使用playwright模拟浏览器行为
48
  > 抖音前端实现,诸多css class id 均为随机数,故项目中locator多采用相对定位,而非固定定位
49
  1. 准备视频目录结构
@@ -71,9 +80,45 @@ generate_schedule_time_next_day 默认从第二天开始(此举为避免选择
71
  - https://github.com/lishang520/DouYin-Auto-Upload.git
72
 
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  ### 其余部分(todo)
75
  整理后上传
76
 
77
  # 联系我
78
  探讨自动化上传、自动制作视频
79
- - 微信:rstar019
 
 
1
  # social-auto-upload
2
  social-auto-upload 该项目旨在自动化发布视频到各个社交媒体平台
3
 
4
+
5
  ## 💡Feature
6
  - 中国主流社交媒体平台:
7
  - 抖音
 
26
  playwright install chromium
27
  ```
28
 
29
+ # 🐇 About
30
+ 该项目为我自用项目抽离出来,我的发布策略是定时发布(提前一天发布),故发布部分采用的事件均为第二天的时间
31
+
32
+ 如果你有需求立即发布,可自行研究源码或者向我提问
33
+
34
+
35
  # 核心模块解释
36
 
37
+ ## 1. 视频文件准备
38
  filepath 本地视频目录,目录包含
39
  - 视频文件
40
  - 视频meta信息txt文件
 
50
  #坚持不懈 #爱情执着 #奋斗使者 #短视频
51
  ```
52
 
53
+ ### 2. 抖音
54
+ <img src="media/show/pdf3.gif" alt="douyin show" width="500"/>
55
+
56
  使用playwright模拟浏览器行为
57
  > 抖音前端实现,诸多css class id 均为随机数,故项目中locator多采用相对定位,而非固定定位
58
  1. 准备视频目录结构
 
80
  - https://github.com/lishang520/DouYin-Auto-Upload.git
81
 
82
 
83
+
84
+ ### 3. 小红书
85
+ 该实现,借助ReaJason的[xhs](https://github.com/ReaJason/xhs),再次感谢。
86
+
87
+ 1. 目录结构同上
88
+ 2. cookie获取,可使用chrome插件:EditThisCookie
89
+ - 设置导出格式
90
+ ![Alt text](media/20231009111131.png)
91
+ - 导出
92
+ ![Alt text](media/20231009111214.png)
93
+ 3. 黏贴至 accounts.ini文件中
94
+
95
+
96
+ #### 解释与注意事项:
97
+
98
+ ##### 上传方式
99
+ - 本地签名
100
+ - 自建签名服务
101
+
102
+ 测试下来发现本地签名,在实际多账号情况下会存在问题
103
+ 故如果你有多账号分发,建议采用自建签名服务(todo 上传docker配置)
104
+
105
+ ##### 疑难杂症
106
+ 遇到签名问题,可尝试更新cdn.jsdelivr.net_gh_requireCool_stealth.min.js_stealth.min.js文件
107
+ https://github.com/requireCool/stealth.min.js
108
+
109
+ 参考: https://reajason.github.io/xhs/basic
110
+
111
+ ##### todo
112
+ - 扫码登录方式(实验下来发现与浏览器获取的存在区别,会有问题,未来再研究)
113
+
114
+
115
+ 参考项目:
116
+ - https://github.com/ReaJason/xhs
117
+
118
  ### 其余部分(todo)
119
  整理后上传
120
 
121
  # 联系我
122
  探讨自动化上传、自动制作视频
123
+
124
+ <img src="media/group-qr.png" alt="group-qr" width="300"/>
conf.py CHANGED
@@ -1,3 +1,4 @@
1
  from pathlib import Path
2
 
3
  BASE_DIR = Path(__file__).parent.resolve()
 
 
1
  from pathlib import Path
2
 
3
  BASE_DIR = Path(__file__).parent.resolve()
4
+ XHS_SERVER = "http://127.0.0.1:11901"
douyin_uploader/main.py CHANGED
@@ -162,10 +162,13 @@ class DouYinVideo(object):
162
  await asyncio.sleep(1)
163
  await page.locator('div[role="listbox"] [role="option"]').first.click()
164
 
165
- # 頭條
166
- if await page.locator('text="今日头条" >> xpath=../following-sibling::div/div[contains(@class, "semi-switch")]').count():
167
- if 'semi-switch-checked' not in await page.eval_on_selector('text="今日头条" >> xpath=../following-sibling::div/div[contains(@class, "semi-switch")]', 'div => div.className'):
168
- await page.locator('text="今日头条" >> xpath=../following-sibling::div//input[contains(@class, "semi-switch-native-control")]').click()
 
 
 
169
 
170
  if self.publish_date != 0:
171
  await self.set_schedule_time_douyin(page, self.publish_date)
 
162
  await asyncio.sleep(1)
163
  await page.locator('div[role="listbox"] [role="option"]').first.click()
164
 
165
+ # 頭條/西瓜
166
+ third_part_element = '[class^="info"] > [class^="first-part"] div div.semi-switch'
167
+ # 定位是否有第三方平台
168
+ if await page.locator(third_part_element).count():
169
+ # 检测是否是已选中状态
170
+ if 'semi-switch-checked' not in await page.eval_on_selector(third_part_element, 'div => div.className'):
171
+ await page.locator(third_part_element).locator('input.semi-switch-native-control').click()
172
 
173
  if self.publish_date != 0:
174
  await self.set_schedule_time_douyin(page, self.publish_date)
examples/upload_video_to_xhs.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import configparser
2
+ from pathlib import Path
3
+ from time import sleep
4
+
5
+ from xhs import XhsClient
6
+
7
+ from conf import BASE_DIR
8
+ from utils.files_times import generate_schedule_time_next_day, get_title_and_hashtags
9
+ from xhs_uploader.main import sign_local, beauty_print
10
+
11
+ config = configparser.RawConfigParser()
12
+ config.read(Path(BASE_DIR / "xhs_uploader" / "accounts.ini"))
13
+
14
+
15
+ if __name__ == '__main__':
16
+ filepath = Path(BASE_DIR) / "videos"
17
+ # 获取视频目录
18
+ folder_path = Path(filepath)
19
+ # 获取文件夹中的所有文件
20
+ files = list(folder_path.glob("*.mp4"))
21
+ file_num = len(files)
22
+
23
+ cookies = config['account1']['cookies']
24
+ xhs_client = XhsClient(cookies, sign=sign_local, timeout=60)
25
+ # auth cookie
26
+ # 注意:该校验cookie方式可能并没那么准确
27
+ try:
28
+ xhs_client.get_video_first_frame_image_id("3214")
29
+ except:
30
+ print("cookie 失效")
31
+ exit()
32
+
33
+ publish_datetimes = generate_schedule_time_next_day(file_num, 1, daily_times=[16])
34
+
35
+ for index, file in enumerate(files):
36
+ title, tags = get_title_and_hashtags(str(file))
37
+ tags_str = ' '.join(['#' + tag for tag in tags])
38
+ # 打印视频文件名、标题和 hashtag
39
+ print(f"视频文件名:{file}")
40
+ print(f"标题:{title}")
41
+ print(f"Hashtag:{tags}")
42
+
43
+ topics = []
44
+ # 获取hashtag
45
+ for i in tags[:3]:
46
+ topic_official = xhs_client.get_suggest_topic(i)
47
+ if topic_official:
48
+ topics.append(topic_official[0])
49
+
50
+ note = xhs_client.create_video_note(title=title[:20], video_path=str(file), desc=title + tags_str,
51
+ topics=topics,
52
+ is_private=False,
53
+ post_time=publish_datetimes[index].strftime("%Y-%m-%d %H:%M:%S"))
54
+
55
+ beauty_print(note)
56
+ # 强制休眠30s,避免风控(必要)
57
+ sleep(30)
xhs_uploader/__init__.py ADDED
File without changes
xhs_uploader/accounts.ini ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [account1]
2
+ cookies = changeme
xhs_uploader/cdn.jsdelivr.net_gh_requireCool_stealth.min.js_stealth.min.js ADDED
The diff for this file is too large to render. See raw diff
 
xhs_uploader/main.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import configparser
2
+ import json
3
+ import pathlib
4
+ from time import sleep
5
+
6
+ import requests
7
+ from playwright.sync_api import sync_playwright
8
+
9
+ from conf import BASE_DIR, XHS_SERVER
10
+
11
+ config = configparser.RawConfigParser()
12
+ config.read('accounts.ini')
13
+
14
+
15
+ def sign_local(uri, data=None, a1="", web_session=""):
16
+ for _ in range(10):
17
+ try:
18
+ with sync_playwright() as playwright:
19
+ stealth_js_path = pathlib.Path(
20
+ BASE_DIR) / "xhs_uploader" / "cdn.jsdelivr.net_gh_requireCool_stealth.min.js_stealth.min.js"
21
+ chromium = playwright.chromium
22
+
23
+ # 如果一直失败可尝试设置成 False 让其打开浏览器,适当添加 sleep 可查看浏览器状态
24
+ browser = chromium.launch(headless=True)
25
+
26
+ browser_context = browser.new_context()
27
+ browser_context.add_init_script(path=stealth_js_path)
28
+ context_page = browser_context.new_page()
29
+ context_page.goto("https://www.xiaohongshu.com")
30
+ browser_context.add_cookies([
31
+ {'name': 'a1', 'value': a1, 'domain': ".xiaohongshu.com", 'path': "/"}]
32
+ )
33
+ context_page.reload()
34
+ # 这个地方设置完浏览器 cookie 之后,如果这儿不 sleep 一下签名获取就失败了,如果经常失败请设置长一点试试
35
+ sleep(2)
36
+ encrypt_params = context_page.evaluate("([url, data]) => window._webmsxyw(url, data)", [uri, data])
37
+ return {
38
+ "x-s": encrypt_params["X-s"],
39
+ "x-t": str(encrypt_params["X-t"])
40
+ }
41
+ except Exception:
42
+ # 这儿有时会出现 window._webmsxyw is not a function 或未知跳转错误,因此加一个失败重试趴
43
+ pass
44
+ raise Exception("重试了这么多次还是无法签名成功,寄寄寄")
45
+
46
+
47
+ def sign(uri, data=None, a1="", web_session=""):
48
+ # 填写自己的 flask 签名服务端口地址
49
+ res = requests.post(f"{XHS_SERVER}/sign",
50
+ json={"uri": uri, "data": data, "a1": a1, "web_session": web_session})
51
+ signs = res.json()
52
+ return {
53
+ "x-s": signs["x-s"],
54
+ "x-t": signs["x-t"]
55
+ }
56
+
57
+
58
+ def beauty_print(data: dict):
59
+ print(json.dumps(data, ensure_ascii=False, indent=2))
xhs_uploader/xhs_login_qrcode.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ import json
3
+ import qrcode
4
+ from time import sleep
5
+
6
+ from xhs import XhsClient
7
+
8
+ from xhs_uploader.main import sign
9
+
10
+ # pip install qrcode
11
+ if __name__ == '__main__':
12
+ xhs_client = XhsClient(sign=sign)
13
+ print(datetime.datetime.now())
14
+ qr_res = xhs_client.get_qrcode()
15
+ qr_id = qr_res["qr_id"]
16
+ qr_code = qr_res["code"]
17
+
18
+ qr = qrcode.QRCode(version=1, error_correction=qrcode.ERROR_CORRECT_L,
19
+ box_size=50,
20
+ border=1)
21
+ qr.add_data(qr_res["url"])
22
+ qr.make()
23
+ img = qr.make_image(fill_color="black", back_color="white")
24
+ img.save('qrcode.png')
25
+
26
+ while True:
27
+ check_qrcode = xhs_client.check_qrcode(qr_id, qr_code)
28
+ print(check_qrcode)
29
+ sleep(1)
30
+ if check_qrcode["code_status"] == 2:
31
+ print(json.dumps(check_qrcode["login_info"], indent=4))
32
+ print("当前 cookie:" + xhs_client.cookie)
33
+ break
34
+
35
+ print(json.dumps(xhs_client.get_self_info(), indent=4))