From e088d76516863b56ab5053264b56c356079e5bf2 Mon Sep 17 00:00:00 2001 From: "pookz@stme" Date: Thu, 13 Jun 2024 18:13:21 +0800 Subject: [PATCH] 1. add cli support. 2. change LOCAL_CHROME_PATH to conf file 3. change douyin gen cookie url --- README.MD | 57 ++++++++++++++++++++--- cli_main.py | 92 ++++++++++++++++++++++++++++++++++++++ conf.py | 3 +- douyin_uploader/main.py | 9 ++-- tencent_uploader/main.py | 3 +- utils/base_social_media.py | 14 ++++++ 6 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 cli_main.py create mode 100644 utils/base_social_media.py diff --git a/README.MD b/README.MD index ddd281b..391b6fe 100644 --- a/README.MD +++ b/README.MD @@ -65,9 +65,56 @@ meta_file 内容(content): 这位勇敢的男子为了心爱之人每天坚守 🥺❤️‍🩹 #坚持不懈 #爱情执着 #奋斗使者 #短视频 ``` + +### Usage +1. 设置conf 文件中的 `LOCAL_CHROME_PATH`(在douyin 或者视频号可能出现chromium 不兼容的各种问题,建议设置本地的chrome) +2. 这里分割出来3条路 + - 可自行研究源码,免费、任意 穿插在自己的项目中 + - 可参考下面的各个平台的使用指南,`examples`文件夹中有各种示例代码 + - 使用cli 简易使用(支持tiktok douyin 视频号) + +#### cli 用法 +```python +python cli_main.py [options] +``` +查看详细的参数说明使用: +```python +python cli_main.py -h +``` +```python +usage: cli_main.py [-h] platform account_name action ... + +Upload video to multiple social-media. + +positional arguments: + platform Choose social-media platform: douyin tencent tiktok + account_name Account name for the platform: xiaoA + action Choose action + upload upload operation + login login operation + watch watch operation + +options: + -h, --help show this help message and exit + +``` +示例 +```python +python cli_main.py douyin test login +douyin平台,账号名为test,动作为login + +python douyin test upload "C:\Users\duperdog\Videos\2023-11-07_05-27-44 - 这位少女如梦中仙... .mp4" -pt 0 +douyin平台, 账号名为test, 动作为upload, 视频文件(需对应的meta文件,详见上), 发布方式(pt):0 立即发布 + +python douyin test upload "C:\Users\superdog\Videos\2023-11-07_05-27-44 - 这位少女如梦中仙... .mp4" -pt 1 -t "2024-6-14 12:00" +douyin平台, 账号名为test, 动作为upload, 视频文件, 发布方式(pt):1 定时发布, 发布时间(t): 2024-6-14 12:00 +``` + --- -### 2. 抖音 +## 各平台详细说明 + +### 1. 抖音 douyin show 使用playwright模拟浏览器行为 @@ -104,7 +151,7 @@ generate_schedule_time_next_day 默认从第二天开始(此举为避免选择 --- -### 3. 视频号 +### 2. 视频号 使用playwright模拟浏览器行为 1. 准备视频目录结构 2. cookie获取:get_tencent_cookie.py 扫码登录 @@ -126,7 +173,7 @@ generate_schedule_time_next_day 默认从第二天开始(此举为避免选择 --- -### 4. 小红书 +### 3. 小红书 该实现,借助ReaJason的[xhs](https://github.com/ReaJason/xhs),再次感谢。 1. 目录结构同上 @@ -166,7 +213,7 @@ https://github.com/requireCool/stealth.min.js --- -### 5. bilibili +### 4. bilibili 该实现,借助biliup的[biliup-rs](https://github.com/biliup/biliup-rs),再次感谢。 1. 准备视频目录结构 2. cookie获取:`biliup.exe -u account.json login` 选项你喜欢的登录方式 @@ -184,7 +231,7 @@ bilibili cookie 长期有效(至少我运行2年以来是这样的) --- -### 6. tiktok +### 5. tiktok 使用playwright模拟浏览器行为(Simulating Browser Behavior with playwright) 1. 准备视频目录结构(Prepare the video directory structure) 2. cookie获取(generate your cookie):get_tk_cookie.py diff --git a/cli_main.py b/cli_main.py new file mode 100644 index 0000000..c2f61cb --- /dev/null +++ b/cli_main.py @@ -0,0 +1,92 @@ +import argparse +import asyncio +from datetime import datetime +from os.path import exists +from pathlib import Path + +from conf import BASE_DIR +from douyin_uploader.main import douyin_setup, DouYinVideo +from tencent_uploader.main import weixin_setup, TencentVideo +from tk_uploader.main import tiktok_setup, TiktokVideo +from utils.base_social_media import get_supported_social_media, get_cli_action, SOCIAL_MEDIA_DOUYIN, \ + SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK +from utils.constant import TencentZoneTypes +from utils.files_times import get_title_and_hashtags + + +def parse_schedule(schedule_raw): + if schedule_raw: + schedule = datetime.strptime(schedule_raw, '%Y-%m-%d %H:%M') + else: + schedule = None + return schedule + + +async def main(): + # 主解析器 + parser = argparse.ArgumentParser(description="Upload video to multiple social-media.") + parser.add_argument("platform", metavar='platform', choices=get_supported_social_media(), help="Choose social-media platform: douyin tencent tiktok") + + parser.add_argument("account_name", type=str, help="Account name for the platform: xiaoA") + subparsers = parser.add_subparsers(dest="action", metavar='action', help="Choose action", required=True) + + actions = get_cli_action() + for action in actions: + action_parser = subparsers.add_parser(action, help=f'{action} operation') + if action == 'login': + # Login 不需要额外参数 + continue + elif action == 'upload': + action_parser.add_argument("video_file", help="Path to the Video file") + action_parser.add_argument("-pt", "--publish_type", type=int, choices=[0, 1], + help="0 for immediate, 1 for scheduled", default=0) + action_parser.add_argument('-t', '--schedule', help='Schedule UTC time in %Y-%m-%d %H:%M format') + + # 解析命令行参数 + args = parser.parse_args() + # 参数校验 + if args.action == 'upload': + if not exists(args.video_file): + raise FileNotFoundError(f'Could not find the video file at {args["video_file"]}') + if args.publish_type == 1 and not args.schedule: + parser.error("The schedule must must be specified for scheduled publishing.") + + account_file = Path(BASE_DIR / "cookies" / f"{args.platform}_{args.account_name}.json") + account_file.parent.mkdir(exist_ok=True) + + # 根据 action 处理不同的逻辑 + if args.action == 'login': + print(f"Logging in with account {args.account_name} on platform {args.platform}") + if args.platform == SOCIAL_MEDIA_DOUYIN: + await douyin_setup(str(account_file), handle=True) + elif args.platform == SOCIAL_MEDIA_TIKTOK: + await tiktok_setup(str(account_file), handle=True) + elif args.platform == SOCIAL_MEDIA_TENCENT: + await weixin_setup(str(account_file), handle=True) + elif args.action == 'upload': + title, tags = get_title_and_hashtags(args.video_file) + video_file = args.video_file + + if args.publish_type == 0: + print("Uploading immediately...") + publish_date = 0 + else: + print("Scheduling videos...") + publish_date = parse_schedule(args.schedule) + + if args.platform == SOCIAL_MEDIA_DOUYIN: + await douyin_setup(account_file, handle=False) + app = DouYinVideo(title, video_file, tags, publish_date, account_file) + elif args.platform == SOCIAL_MEDIA_TIKTOK: + await tiktok_setup(account_file, handle=True) + app = TiktokVideo(title, video_file, tags, publish_date, account_file) + elif args.platform == SOCIAL_MEDIA_TENCENT: + await weixin_setup(account_file, handle=True) + category = TencentZoneTypes.LIFESTYLE.value # 标记原创需要否则不需要传 + app = TencentVideo(title, video_file, tags, publish_date, account_file, category) + + await app.main() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/conf.py b/conf.py index badfd44..b653ae4 100644 --- a/conf.py +++ b/conf.py @@ -1,4 +1,5 @@ from pathlib import Path BASE_DIR = Path(__file__).parent.resolve() -XHS_SERVER = "http://127.0.0.1:11901" \ No newline at end of file +XHS_SERVER = "http://127.0.0.1:11901" +LOCAL_CHROME_PATH = "" # change me necessary! for example C:/Program Files/Google/Chrome/Application/chrome.exe diff --git a/douyin_uploader/main.py b/douyin_uploader/main.py index f907df2..f7143ae 100644 --- a/douyin_uploader/main.py +++ b/douyin_uploader/main.py @@ -6,6 +6,8 @@ from playwright.async_api import Playwright, async_playwright import os import asyncio +from conf import LOCAL_CHROME_PATH + async def cookie_auth(account_file): async with async_playwright() as playwright: @@ -45,7 +47,7 @@ async def douyin_cookie_gen(account_file): context = await browser.new_context() # Pass any options # Pause the page, and start recording manually. page = await context.new_page() - await page.goto("https://www.douyin.com/") + await page.goto("https://creator.douyin.com/") await page.pause() # 点击调试器的继续,保存cookie await context.storage_state(path=account_file) @@ -59,7 +61,7 @@ class DouYinVideo(object): self.publish_date = publish_date self.account_file = account_file self.date_format = '%Y年%m月%d日 %H:%M' - self.local_executable_path = "" # change me + self.local_executable_path = LOCAL_CHROME_PATH async def set_schedule_time_douyin(self, page, publish_date): # 选择包含特定文本内容的 label 元素 @@ -163,7 +165,8 @@ class DouYinVideo(object): await page.keyboard.press("Control+KeyA") await page.keyboard.press("Delete") await page.keyboard.type("杭州市") - await asyncio.sleep(1) + # await asyncio.sleep(1) + await page.wait_for_timeout(1000) await page.locator('div[role="listbox"] [role="option"]').first.click() # 頭條/西瓜 diff --git a/tencent_uploader/main.py b/tencent_uploader/main.py index 4329058..236bfc8 100644 --- a/tencent_uploader/main.py +++ b/tencent_uploader/main.py @@ -6,6 +6,7 @@ from playwright.async_api import Playwright, async_playwright import os import asyncio +from conf import LOCAL_CHROME_PATH from utils.files_times import get_absolute_path @@ -83,7 +84,7 @@ class TencentVideo(object): self.publish_date = publish_date self.account_file = account_file self.category = category - self.local_executable_path = "" # change me necessary! + self.local_executable_path = LOCAL_CHROME_PATH async def set_schedule_time_tencent(self, page, publish_date): print("click schedule") diff --git a/utils/base_social_media.py b/utils/base_social_media.py new file mode 100644 index 0000000..5da8add --- /dev/null +++ b/utils/base_social_media.py @@ -0,0 +1,14 @@ +from typing import List + +SOCIAL_MEDIA_DOUYIN = "douyin" +SOCIAL_MEDIA_TENCENT = "tencent" +SOCIAL_MEDIA_TIKTOK = "tiktok" +SOCIAL_MEDIA_BILIBILI = "bilibili" + + +def get_supported_social_media() -> List[str]: + return [SOCIAL_MEDIA_DOUYIN, SOCIAL_MEDIA_TENCENT, SOCIAL_MEDIA_TIKTOK] + + +def get_cli_action() -> List[str]: + return ["upload", "login", "watch"]