pookz@stme commited on
Commit ·
bd47096
1
Parent(s): 34d738a
1. add cli support.
Browse files2. change LOCAL_CHROME_PATH to conf file
3. change douyin gen cookie url
- README.MD +52 -5
- cli_main.py +92 -0
- conf.py +2 -1
- douyin_uploader/main.py +6 -3
- tencent_uploader/main.py +2 -1
- utils/base_social_media.py +14 -0
README.MD
CHANGED
|
@@ -65,9 +65,56 @@ meta_file 内容(content):
|
|
| 65 |
这位勇敢的男子为了心爱之人每天坚守 🥺❤️🩹
|
| 66 |
#坚持不懈 #爱情执着 #奋斗使者 #短视频
|
| 67 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
---
|
| 69 |
|
| 70 |
-
##
|
|
|
|
|
|
|
| 71 |
<img src="media/show/pdf3.gif" alt="douyin show" width="500"/>
|
| 72 |
|
| 73 |
使用playwright模拟浏览器行为
|
|
@@ -104,7 +151,7 @@ generate_schedule_time_next_day 默认从第二天开始(此举为避免选择
|
|
| 104 |
|
| 105 |
---
|
| 106 |
|
| 107 |
-
###
|
| 108 |
使用playwright模拟浏览器行为
|
| 109 |
1. 准备视频目录结构
|
| 110 |
2. cookie获取:get_tencent_cookie.py 扫码登录
|
|
@@ -126,7 +173,7 @@ generate_schedule_time_next_day 默认从第二天开始(此举为避免选择
|
|
| 126 |
---
|
| 127 |
|
| 128 |
|
| 129 |
-
###
|
| 130 |
该实现,借助ReaJason的[xhs](https://github.com/ReaJason/xhs),再次感谢。
|
| 131 |
|
| 132 |
1. 目录结构同上
|
|
@@ -166,7 +213,7 @@ https://github.com/requireCool/stealth.min.js
|
|
| 166 |
|
| 167 |
---
|
| 168 |
|
| 169 |
-
###
|
| 170 |
该实现,借助biliup的[biliup-rs](https://github.com/biliup/biliup-rs),再次感谢。
|
| 171 |
1. 准备视频目录结构
|
| 172 |
2. cookie获取:`biliup.exe -u account.json login` 选项你喜欢的登录方式
|
|
@@ -184,7 +231,7 @@ bilibili cookie 长期有效(至少我运行2年以来是这样的)
|
|
| 184 |
|
| 185 |
---
|
| 186 |
|
| 187 |
-
###
|
| 188 |
使用playwright模拟浏览器行为(Simulating Browser Behavior with playwright)
|
| 189 |
1. 准备视频目录结构(Prepare the video directory structure)
|
| 190 |
2. cookie获取(generate your cookie):get_tk_cookie.py
|
|
|
|
| 65 |
这位勇敢的男子为了心爱之人每天坚守 🥺❤️🩹
|
| 66 |
#坚持不懈 #爱情执着 #奋斗使者 #短视频
|
| 67 |
```
|
| 68 |
+
|
| 69 |
+
### Usage
|
| 70 |
+
1. 设置conf 文件中的 `LOCAL_CHROME_PATH`(在douyin 或者视频号可能出现chromium 不兼容的各种问题,建议设置本地的chrome)
|
| 71 |
+
2. 这里分割出来3条路
|
| 72 |
+
- 可自行研究源码,免费、任意 穿插在自己的项目中
|
| 73 |
+
- 可参考下面的各个平台的使用指南,`examples`文件夹中有各种示例代码
|
| 74 |
+
- 使用cli 简易使用(支持tiktok douyin 视频号)
|
| 75 |
+
|
| 76 |
+
#### cli 用法
|
| 77 |
+
```python
|
| 78 |
+
python cli_main.py <platform> <account_name> <action: upload, login> [options]
|
| 79 |
+
```
|
| 80 |
+
查看详细的参数说明使用:
|
| 81 |
+
```python
|
| 82 |
+
python cli_main.py -h
|
| 83 |
+
```
|
| 84 |
+
```python
|
| 85 |
+
usage: cli_main.py [-h] platform account_name action ...
|
| 86 |
+
|
| 87 |
+
Upload video to multiple social-media.
|
| 88 |
+
|
| 89 |
+
positional arguments:
|
| 90 |
+
platform Choose social-media platform: douyin tencent tiktok
|
| 91 |
+
account_name Account name for the platform: xiaoA
|
| 92 |
+
action Choose action
|
| 93 |
+
upload upload operation
|
| 94 |
+
login login operation
|
| 95 |
+
watch watch operation
|
| 96 |
+
|
| 97 |
+
options:
|
| 98 |
+
-h, --help show this help message and exit
|
| 99 |
+
|
| 100 |
+
```
|
| 101 |
+
示例
|
| 102 |
+
```python
|
| 103 |
+
python cli_main.py douyin test login
|
| 104 |
+
douyin平台,账号名为test,动作为login
|
| 105 |
+
|
| 106 |
+
python douyin test upload "C:\Users\duperdog\Videos\2023-11-07_05-27-44 - 这位少女如梦中仙... .mp4" -pt 0
|
| 107 |
+
douyin平台, 账号名为test, 动作为upload, 视频文件(需对应的meta文件,详见上), 发布方式(pt):0 立即发布
|
| 108 |
+
|
| 109 |
+
python douyin test upload "C:\Users\superdog\Videos\2023-11-07_05-27-44 - 这位少女如梦中仙... .mp4" -pt 1 -t "2024-6-14 12:00"
|
| 110 |
+
douyin平台, 账号名为test, 动作为upload, 视频文件, 发布方式(pt):1 定时发布, 发布时间(t): 2024-6-14 12:00
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
---
|
| 114 |
|
| 115 |
+
## 各平台详细说明
|
| 116 |
+
|
| 117 |
+
### 1. 抖音
|
| 118 |
<img src="media/show/pdf3.gif" alt="douyin show" width="500"/>
|
| 119 |
|
| 120 |
使用playwright模拟浏览器行为
|
|
|
|
| 151 |
|
| 152 |
---
|
| 153 |
|
| 154 |
+
### 2. 视频号
|
| 155 |
使用playwright模拟浏览器行为
|
| 156 |
1. 准备视频目录结构
|
| 157 |
2. cookie获取:get_tencent_cookie.py 扫码登录
|
|
|
|
| 173 |
---
|
| 174 |
|
| 175 |
|
| 176 |
+
### 3. 小红书
|
| 177 |
该实现,借助ReaJason的[xhs](https://github.com/ReaJason/xhs),再次感谢。
|
| 178 |
|
| 179 |
1. 目录结构同上
|
|
|
|
| 213 |
|
| 214 |
---
|
| 215 |
|
| 216 |
+
### 4. bilibili
|
| 217 |
该实现,借助biliup的[biliup-rs](https://github.com/biliup/biliup-rs),再次感谢。
|
| 218 |
1. 准备视频目录结构
|
| 219 |
2. cookie获取:`biliup.exe -u account.json login` 选项你喜欢的登录方式
|
|
|
|
| 231 |
|
| 232 |
---
|
| 233 |
|
| 234 |
+
### 5. tiktok
|
| 235 |
使用playwright模拟浏览器行为(Simulating Browser Behavior with playwright)
|
| 236 |
1. 准备视频目录结构(Prepare the video directory structure)
|
| 237 |
2. cookie获取(generate your cookie):get_tk_cookie.py
|
cli_main.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import asyncio
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
from os.path import exists
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from conf import BASE_DIR
|
| 8 |
+
from douyin_uploader.main import douyin_setup, DouYinVideo
|
| 9 |
+
from tencent_uploader.main import weixin_setup, TencentVideo
|
| 10 |
+
from tk_uploader.main import tiktok_setup, TiktokVideo
|
| 11 |
+
from utils.base_social_media import get_supported_social_media, get_cli_action, SOCIAL_MEDIA_DOUYIN, \
|
| 12 |
+
SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK
|
| 13 |
+
from utils.constant import TencentZoneTypes
|
| 14 |
+
from utils.files_times import get_title_and_hashtags
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def parse_schedule(schedule_raw):
|
| 18 |
+
if schedule_raw:
|
| 19 |
+
schedule = datetime.strptime(schedule_raw, '%Y-%m-%d %H:%M')
|
| 20 |
+
else:
|
| 21 |
+
schedule = None
|
| 22 |
+
return schedule
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
async def main():
|
| 26 |
+
# 主解析器
|
| 27 |
+
parser = argparse.ArgumentParser(description="Upload video to multiple social-media.")
|
| 28 |
+
parser.add_argument("platform", metavar='platform', choices=get_supported_social_media(), help="Choose social-media platform: douyin tencent tiktok")
|
| 29 |
+
|
| 30 |
+
parser.add_argument("account_name", type=str, help="Account name for the platform: xiaoA")
|
| 31 |
+
subparsers = parser.add_subparsers(dest="action", metavar='action', help="Choose action", required=True)
|
| 32 |
+
|
| 33 |
+
actions = get_cli_action()
|
| 34 |
+
for action in actions:
|
| 35 |
+
action_parser = subparsers.add_parser(action, help=f'{action} operation')
|
| 36 |
+
if action == 'login':
|
| 37 |
+
# Login 不需要额外参数
|
| 38 |
+
continue
|
| 39 |
+
elif action == 'upload':
|
| 40 |
+
action_parser.add_argument("video_file", help="Path to the Video file")
|
| 41 |
+
action_parser.add_argument("-pt", "--publish_type", type=int, choices=[0, 1],
|
| 42 |
+
help="0 for immediate, 1 for scheduled", default=0)
|
| 43 |
+
action_parser.add_argument('-t', '--schedule', help='Schedule UTC time in %Y-%m-%d %H:%M format')
|
| 44 |
+
|
| 45 |
+
# 解析命令行参数
|
| 46 |
+
args = parser.parse_args()
|
| 47 |
+
# 参数校验
|
| 48 |
+
if args.action == 'upload':
|
| 49 |
+
if not exists(args.video_file):
|
| 50 |
+
raise FileNotFoundError(f'Could not find the video file at {args["video_file"]}')
|
| 51 |
+
if args.publish_type == 1 and not args.schedule:
|
| 52 |
+
parser.error("The schedule must must be specified for scheduled publishing.")
|
| 53 |
+
|
| 54 |
+
account_file = Path(BASE_DIR / "cookies" / f"{args.platform}_{args.account_name}.json")
|
| 55 |
+
account_file.parent.mkdir(exist_ok=True)
|
| 56 |
+
|
| 57 |
+
# 根据 action 处理不同的逻辑
|
| 58 |
+
if args.action == 'login':
|
| 59 |
+
print(f"Logging in with account {args.account_name} on platform {args.platform}")
|
| 60 |
+
if args.platform == SOCIAL_MEDIA_DOUYIN:
|
| 61 |
+
await douyin_setup(str(account_file), handle=True)
|
| 62 |
+
elif args.platform == SOCIAL_MEDIA_TIKTOK:
|
| 63 |
+
await tiktok_setup(str(account_file), handle=True)
|
| 64 |
+
elif args.platform == SOCIAL_MEDIA_TENCENT:
|
| 65 |
+
await weixin_setup(str(account_file), handle=True)
|
| 66 |
+
elif args.action == 'upload':
|
| 67 |
+
title, tags = get_title_and_hashtags(args.video_file)
|
| 68 |
+
video_file = args.video_file
|
| 69 |
+
|
| 70 |
+
if args.publish_type == 0:
|
| 71 |
+
print("Uploading immediately...")
|
| 72 |
+
publish_date = 0
|
| 73 |
+
else:
|
| 74 |
+
print("Scheduling videos...")
|
| 75 |
+
publish_date = parse_schedule(args.schedule)
|
| 76 |
+
|
| 77 |
+
if args.platform == SOCIAL_MEDIA_DOUYIN:
|
| 78 |
+
await douyin_setup(account_file, handle=False)
|
| 79 |
+
app = DouYinVideo(title, video_file, tags, publish_date, account_file)
|
| 80 |
+
elif args.platform == SOCIAL_MEDIA_TIKTOK:
|
| 81 |
+
await tiktok_setup(account_file, handle=True)
|
| 82 |
+
app = TiktokVideo(title, video_file, tags, publish_date, account_file)
|
| 83 |
+
elif args.platform == SOCIAL_MEDIA_TENCENT:
|
| 84 |
+
await weixin_setup(account_file, handle=True)
|
| 85 |
+
category = TencentZoneTypes.LIFESTYLE.value # 标记原创需要否则不需要传
|
| 86 |
+
app = TencentVideo(title, video_file, tags, publish_date, account_file, category)
|
| 87 |
+
|
| 88 |
+
await app.main()
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
if __name__ == "__main__":
|
| 92 |
+
asyncio.run(main())
|
conf.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
from pathlib import Path
|
| 2 |
|
| 3 |
BASE_DIR = Path(__file__).parent.resolve()
|
| 4 |
-
XHS_SERVER = "http://127.0.0.1:11901"
|
|
|
|
|
|
| 1 |
from pathlib import Path
|
| 2 |
|
| 3 |
BASE_DIR = Path(__file__).parent.resolve()
|
| 4 |
+
XHS_SERVER = "http://127.0.0.1:11901"
|
| 5 |
+
LOCAL_CHROME_PATH = "" # change me necessary! for example C:/Program Files/Google/Chrome/Application/chrome.exe
|
douyin_uploader/main.py
CHANGED
|
@@ -6,6 +6,8 @@ from playwright.async_api import Playwright, async_playwright
|
|
| 6 |
import os
|
| 7 |
import asyncio
|
| 8 |
|
|
|
|
|
|
|
| 9 |
|
| 10 |
async def cookie_auth(account_file):
|
| 11 |
async with async_playwright() as playwright:
|
|
@@ -45,7 +47,7 @@ async def douyin_cookie_gen(account_file):
|
|
| 45 |
context = await browser.new_context() # Pass any options
|
| 46 |
# Pause the page, and start recording manually.
|
| 47 |
page = await context.new_page()
|
| 48 |
-
await page.goto("https://
|
| 49 |
await page.pause()
|
| 50 |
# 点击调试器的继续,保存cookie
|
| 51 |
await context.storage_state(path=account_file)
|
|
@@ -59,7 +61,7 @@ class DouYinVideo(object):
|
|
| 59 |
self.publish_date = publish_date
|
| 60 |
self.account_file = account_file
|
| 61 |
self.date_format = '%Y年%m月%d日 %H:%M'
|
| 62 |
-
self.local_executable_path =
|
| 63 |
|
| 64 |
async def set_schedule_time_douyin(self, page, publish_date):
|
| 65 |
# 选择包含特定文本内容的 label 元素
|
|
@@ -163,7 +165,8 @@ class DouYinVideo(object):
|
|
| 163 |
await page.keyboard.press("Control+KeyA")
|
| 164 |
await page.keyboard.press("Delete")
|
| 165 |
await page.keyboard.type("杭州市")
|
| 166 |
-
await asyncio.sleep(1)
|
|
|
|
| 167 |
await page.locator('div[role="listbox"] [role="option"]').first.click()
|
| 168 |
|
| 169 |
# 頭條/西瓜
|
|
|
|
| 6 |
import os
|
| 7 |
import asyncio
|
| 8 |
|
| 9 |
+
from conf import LOCAL_CHROME_PATH
|
| 10 |
+
|
| 11 |
|
| 12 |
async def cookie_auth(account_file):
|
| 13 |
async with async_playwright() as playwright:
|
|
|
|
| 47 |
context = await browser.new_context() # Pass any options
|
| 48 |
# Pause the page, and start recording manually.
|
| 49 |
page = await context.new_page()
|
| 50 |
+
await page.goto("https://creator.douyin.com/")
|
| 51 |
await page.pause()
|
| 52 |
# 点击调试器的继续,保存cookie
|
| 53 |
await context.storage_state(path=account_file)
|
|
|
|
| 61 |
self.publish_date = publish_date
|
| 62 |
self.account_file = account_file
|
| 63 |
self.date_format = '%Y年%m月%d日 %H:%M'
|
| 64 |
+
self.local_executable_path = LOCAL_CHROME_PATH
|
| 65 |
|
| 66 |
async def set_schedule_time_douyin(self, page, publish_date):
|
| 67 |
# 选择包含特定文本内容的 label 元素
|
|
|
|
| 165 |
await page.keyboard.press("Control+KeyA")
|
| 166 |
await page.keyboard.press("Delete")
|
| 167 |
await page.keyboard.type("杭州市")
|
| 168 |
+
# await asyncio.sleep(1)
|
| 169 |
+
await page.wait_for_timeout(1000)
|
| 170 |
await page.locator('div[role="listbox"] [role="option"]').first.click()
|
| 171 |
|
| 172 |
# 頭條/西瓜
|
tencent_uploader/main.py
CHANGED
|
@@ -6,6 +6,7 @@ from playwright.async_api import Playwright, async_playwright
|
|
| 6 |
import os
|
| 7 |
import asyncio
|
| 8 |
|
|
|
|
| 9 |
from utils.files_times import get_absolute_path
|
| 10 |
|
| 11 |
|
|
@@ -83,7 +84,7 @@ class TencentVideo(object):
|
|
| 83 |
self.publish_date = publish_date
|
| 84 |
self.account_file = account_file
|
| 85 |
self.category = category
|
| 86 |
-
self.local_executable_path =
|
| 87 |
|
| 88 |
async def set_schedule_time_tencent(self, page, publish_date):
|
| 89 |
print("click schedule")
|
|
|
|
| 6 |
import os
|
| 7 |
import asyncio
|
| 8 |
|
| 9 |
+
from conf import LOCAL_CHROME_PATH
|
| 10 |
from utils.files_times import get_absolute_path
|
| 11 |
|
| 12 |
|
|
|
|
| 84 |
self.publish_date = publish_date
|
| 85 |
self.account_file = account_file
|
| 86 |
self.category = category
|
| 87 |
+
self.local_executable_path = LOCAL_CHROME_PATH
|
| 88 |
|
| 89 |
async def set_schedule_time_tencent(self, page, publish_date):
|
| 90 |
print("click schedule")
|
utils/base_social_media.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List
|
| 2 |
+
|
| 3 |
+
SOCIAL_MEDIA_DOUYIN = "douyin"
|
| 4 |
+
SOCIAL_MEDIA_TENCENT = "tencent"
|
| 5 |
+
SOCIAL_MEDIA_TIKTOK = "tiktok"
|
| 6 |
+
SOCIAL_MEDIA_BILIBILI = "bilibili"
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def get_supported_social_media() -> List[str]:
|
| 10 |
+
return [SOCIAL_MEDIA_DOUYIN, SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def get_cli_action() -> List[str]:
|
| 14 |
+
return ["upload", "login", "watch"]
|