From a03cbf3bac6b922acd581dcd859a15ba80bef0c1 Mon Sep 17 00:00:00 2001 From: jinye_huang Date: Tue, 10 Jun 2025 09:59:25 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8D=95=E5=B0=81=E8=A3=85=E4=BA=86?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 229 +++++++++++++++++++ douyin_scheduler.py | 533 ++++++++++++++++++++++++++++++++++++++++++++ example_usage.py | 264 ++++++++++++++++++++++ get_cookies.py | 39 ++++ 4 files changed, 1065 insertions(+) create mode 100644 README.md create mode 100644 douyin_scheduler.py create mode 100644 example_usage.py create mode 100644 get_cookies.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..28093ff --- /dev/null +++ b/README.md @@ -0,0 +1,229 @@ +# 抖音发布调度器 (DouyinScheduler) + +一个用于自动化抖音视频发布的Python库,支持视频上传、内容编辑、商品挂载、定时发布等功能。 + +## 功能特性 + +- 🎥 **视频上传**: 自动上传视频文件 +- ✏️ **内容编辑**: 设置标题、描述等内容信息 +- 🛍️ **商品挂载**: 支持团购商品挂载 +- ⏰ **定时发布**: 设置定时发布时间 +- 🔄 **批量操作**: 支持批量发布多个视频 +- ⚙️ **配置管理**: 灵活的配置文件支持 +- 🛡️ **智能等待**: 优化的等待机制,减少不必要的时间浪费 +- 🌐 **多浏览器**: 支持Safari、Chrome、Firefox + +## 项目结构 + +``` +. +├── douyin_scheduler.py # 主调度器库 +├── douyin_publisher.py # 原始脚本(参考用) +├── example_usage.py # 使用示例 +├── config_example.json # 配置文件示例 +├── cookies.json # 登录cookies(需要自己获取) +└── README.md # 说明文档 +``` + +## 安装依赖 + +```bash +pip install selenium +``` + +## 快速开始 + +### 1. 准备cookies文件 + +首先需要获取登录状态的cookies: + +1. 手动登录抖音创作者中心 +2. 使用浏览器开发者工具导出cookies +3. 保存为 `cookies.json` 文件 + +### 2. 基本使用 + +```python +from douyin_scheduler import DouyinScheduler + +# 创建调度器实例 +scheduler = DouyinScheduler("cookies.json", "safari") + +try: + # 发布视频 + success = scheduler.publish_video_with_product( + video_path="/path/to/your/video.mp4", + title="你的视频标题", + description="你的视频描述 #标签1 #标签2" + ) + + if success: + print("发布成功!") + else: + print("发布失败!") + +finally: + scheduler.close() +``` + +### 3. 高级功能 + +#### 带商品发布 + +```python +success = scheduler.publish_video_with_product( + video_path="/path/to/your/video.mp4", + title="商品推广视频", + description="精彩内容描述 #商品推广", + product_name="商品名称", + product_info="商品详细信息" +) +``` + +#### 定时发布 + +```python +success = scheduler.publish_video_with_product( + video_path="/path/to/your/video.mp4", + title="定时发布视频", + description="定时发布内容", + schedule_time="2025-06-05 12:30" # 格式:YYYY-MM-DD HH:MM +) +``` + +#### 完整功能组合 + +```python +success = scheduler.publish_video_with_product( + video_path="/path/to/your/video.mp4", + title="完整功能演示", + description="包含所有功能的完整示例 #演示 #测试", + product_name="测试商品", + product_info="测试商品信息", + schedule_time="2025-06-05 15:00" +) +``` + +## 模块说明 + +### DouyinScheduler (主调度器) +- 统一的入口点,协调各个子模块 +- 管理浏览器驱动和cookies +- 提供完整的发布流程 + +### VideoUploader (视频上传模块) +- 处理视频文件上传 +- 智能等待上传完成 +- 支持上传状态检测 + +### ContentEditor (内容编辑模块) +- 设置视频标题 +- 设置视频描述 +- 支持内容验证 + +### ProductLinker (商品挂载模块) +- 选择团购类型 +- 搜索和选择商品 +- 设置商品信息 + +### ScheduleManager (定时管理模块) +- 选择定时发布选项 +- 设置发布时间 +- 支持时间格式验证 + +### DouyinUtils (工具类) +- 智能等待机制 +- 安全的元素操作 +- 通用的辅助方法 + +## 配置文件 + +使用 `config_example.json` 作为模板创建自己的配置文件: + +```json +{ + "browser": { + "type": "safari", + "timeout": 30 + }, + "cookies": { + "path": "cookies.json" + }, + "content": { + "default_hashtags": ["#抖音", "#分享生活"], + "title_max_length": 55 + } +} +``` + +## 使用示例 + +运行示例脚本查看各种使用场景: + +```bash +python example_usage.py +``` + +示例包括: +1. 基本视频发布 +2. 带商品视频发布 +3. 定时发布 +4. 批量发布 +5. 使用配置文件 +6. 交互式发布 + +## 优化特性 + +### 智能等待机制 +- 使用 `WebDriverWait` 替代固定的 `time.sleep` +- 动态检测页面元素状态 +- 减少不必要的等待时间 + +### 错误处理 +- 完善的异常捕获和处理 +- 详细的错误日志输出 +- 失败重试机制 + +### 模块化设计 +- 功能模块独立,便于维护 +- 支持扩展新功能 +- 代码复用性高 + +## 注意事项 + +1. **Cookies有效性**: 请确保cookies文件是有效的登录状态 +2. **元素定位**: 网页结构可能变化,需要及时更新XPath选择器 +3. **上传限制**: 注意视频文件大小和格式限制 +4. **频率控制**: 避免频繁操作,可能触发平台限制 +5. **浏览器兼容**: 确保安装了对应的浏览器驱动 + +## 常见问题 + +### Q: 如何获取cookies文件? +A: 登录抖音创作者中心后,使用浏览器开发者工具导出cookies,保存为JSON格式。 + +### Q: 视频上传失败怎么办? +A: 检查视频文件路径、格式和大小,确保符合平台要求。 + +### Q: 元素定位失败怎么办? +A: 网页结构可能已更新,需要重新获取元素的XPath选择器。 + +### Q: 支持哪些浏览器? +A: 目前支持Safari、Chrome、Firefox,推荐使用Safari。 + +## 开发计划 + +- [ ] 支持图文发布 +- [ ] 添加视频编辑功能 +- [ ] 支持更多商品类型 +- [ ] 添加发布数据统计 +- [ ] 优化错误处理机制 +- [ ] 支持多账号管理 + +## 贡献 + +欢迎提交Issue和Pull Request来改进这个项目! + +## 免责声明 + +本工具仅用于学习和个人使用,请遵守抖音平台的使用条款和相关法律法规。 diff --git a/douyin_scheduler.py b/douyin_scheduler.py new file mode 100644 index 0000000..ac37e8e --- /dev/null +++ b/douyin_scheduler.py @@ -0,0 +1,533 @@ +""" +抖音发布调度器库 +支持视频上传、内容编辑、商品挂载、定时发布等功能 +""" + +import json +import time +from datetime import datetime, timedelta +from typing import Dict, List, Optional, Any +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.action_chains import ActionChains +from selenium.common.exceptions import TimeoutException, NoSuchElementException + + +class DouyinUtils: + """工具类,提供通用的等待和操作方法""" + + @staticmethod + def smart_wait(driver, timeout=10): + """智能等待,根据页面加载状态动态调整等待时间""" + return WebDriverWait(driver, timeout) + + @staticmethod + def safe_click(driver, element_locator, timeout=10, use_js=False): + """安全点击元素,支持JavaScript点击""" + try: + wait = WebDriverWait(driver, timeout) + element = wait.until(EC.element_to_be_clickable(element_locator)) + + if use_js: + driver.execute_script("arguments[0].click();", element) + else: + element.click() + return True + except TimeoutException: + print(f"元素点击超时: {element_locator}") + return False + + @staticmethod + def safe_send_keys(driver, element_locator, text, timeout=10, clear_first=True): + """安全输入文本""" + try: + wait = WebDriverWait(driver, timeout) + element = wait.until(EC.presence_of_element_located(element_locator)) + + if clear_first: + element.clear() + element.send_keys(text) + return True + except TimeoutException: + print(f"文本输入超时: {element_locator}") + return False + + @staticmethod + def wait_for_element(driver, element_locator, timeout=10): + """等待元素出现""" + try: + wait = WebDriverWait(driver, timeout) + return wait.until(EC.presence_of_element_located(element_locator)) + except TimeoutException: + print(f"等待元素超时: {element_locator}") + return None + + +class VideoUploader: + """视频上传模块""" + + def __init__(self, driver): + self.driver = driver + self.utils = DouyinUtils() + + def upload_video(self, video_path: str) -> bool: + """上传视频文件""" + try: + print(f"开始上传视频: {video_path}") + + # 查找隐藏的文件输入框 + file_input = self.utils.wait_for_element( + self.driver, + (By.XPATH, "//input[@style='display: none;' or @type='file']"), + timeout=15 + ) + + if file_input: + file_input.send_keys(video_path) + print("视频文件已发送到输入框") + + # 等待上传完成的标志 + upload_success = self._wait_for_upload_complete() + if upload_success: + print("视频上传完成") + return True + else: + print("视频上传可能未完成") + return False + else: + print("未找到文件输入框") + return False + + except Exception as e: + print(f"视频上传失败: {e}") + return False + + def _wait_for_upload_complete(self, timeout=60) -> bool: + """等待视频上传完成""" + try: + # 可以通过检查进度条、上传状态等来判断是否完成 + # 这里使用简单的时间等待,实际可以更智能 + time.sleep(10) # 基础等待时间 + + # 尝试检查是否有上传进度指示器 + for i in range(timeout // 5): + try: + # 检查是否有上传完成的标志 + progress_element = self.driver.find_element(By.XPATH, "//div[contains(@class, 'progress') or contains(@class, 'upload')]") + if "100%" in progress_element.text or "完成" in progress_element.text: + return True + except NoSuchElementException: + pass + + time.sleep(5) + + return True # 如果没有找到进度指示器,假设上传完成 + + except Exception as e: + print(f"等待上传完成时出错: {e}") + return True + + +class ContentEditor: + """内容编辑模块""" + + def __init__(self, driver): + self.driver = driver + self.utils = DouyinUtils() + + def set_title(self, title: str) -> bool: + """设置视频标题""" + title_locator = (By.XPATH, "//input[@placeholder='填写作品标题,为作品获得更多流量']") + success = self.utils.safe_send_keys(self.driver, title_locator, title) + if success: + print(f"标题设置成功: {title}") + return success + + def set_description(self, description: str) -> bool: + """设置视频描述""" + desc_locator = (By.XPATH, "//div[@data-placeholder='添加作品简介']") + success = self.utils.safe_send_keys(self.driver, desc_locator, description) + if success: + print(f"描述设置成功: {description}") + return success + + def set_content(self, title: str, description: str = "") -> bool: + """设置标题和描述""" + title_success = self.set_title(title) + desc_success = self.set_description(description) if description else True + return title_success and desc_success + + +class ProductLinker: + """商品挂载模块""" + + def __init__(self, driver): + self.driver = driver + self.utils = DouyinUtils() + + def select_group_buy(self, product_name: str, product_info: str) -> bool: + """选择团购商品""" + try: + print(f"开始选择团购商品: {product_name}") + + # 1. 点击下拉框选择团购类型 + if not self._select_content_type(): + return False + + # 2. 选择具体商品 + if not self._select_product(product_name): + return False + + # 3. 输入商品信息 + if not self._input_product_info(product_info): + return False + + # 4. 确认选择 + if not self._confirm_selection(): + return False + + print("团购商品选择完成") + return True + + except Exception as e: + print(f"选择团购商品失败: {e}") + return False + + def _select_content_type(self) -> bool: + """选择内容类型为团购""" + # 点击下拉框 + tab_selector_locator = (By.XPATH, "//div[@class='semi-select select-lJTtRL semi-select-single']") + if not self.utils.safe_click(self.driver, tab_selector_locator, use_js=True): + return False + + time.sleep(1) # 等待下拉框展开 + + # 选择团购选项 + group_buy_locator = (By.XPATH, "//div[@data-code='13010' and @class='select-dropdown-option-video']") + return self.utils.safe_click(self.driver, group_buy_locator, use_js=True) + + def _select_product(self, product_name: str) -> bool: + """选择具体商品""" + # 点击商品选择下拉框 + product_selector_locator = (By.XPATH, "//div[@class='semi-select select-Qm4u8S semi-select-single semi-select-filterable']") + if not self.utils.safe_click(self.driver, product_selector_locator, use_js=True): + return False + + time.sleep(1) + + # 输入商品名称进行搜索 + product_input_locator = (By.XPATH, "//input[@class='semi-input semi-input-default' and @placeholder='']") + if not self.utils.safe_send_keys(self.driver, product_input_locator, product_name, clear_first=False): + return False + + time.sleep(2) # 等待搜索结果 + + # 选择匹配的商品 + return self._click_matching_product_option(product_name) + + def _click_matching_product_option(self, product_name: str, max_retries: int = 10) -> bool: + """点击匹配的商品选项""" + for attempt in range(max_retries): + clicked = self.driver.execute_script(""" + let elements = document.querySelectorAll('.semi-select-option'); + for (let el of elements) { + if (el.textContent.includes(arguments[0])) { + el.click(); + return true; + } + } + return false; + """, product_name) + + if clicked: + print(f"成功选择商品,尝试次数: {attempt + 1}") + return True + else: + print(f"等待商品选项,尝试次数: {attempt + 1}/{max_retries}") + time.sleep(1) + + print("未能找到匹配的商品选项") + return False + + def _input_product_info(self, product_info: str) -> bool: + """输入商品信息""" + product_info_locator = (By.XPATH, "//input[@class='semi-input semi-input-default' and contains(@placeholder, '如:')]") + return self.utils.safe_send_keys(self.driver, product_info_locator, product_info) + + def _confirm_selection(self) -> bool: + """确认商品选择""" + confirm_locator = (By.XPATH, "//div[@class='footer-button-DL8zDh']") + return self.utils.safe_click(self.driver, confirm_locator, use_js=True) + + +class ScheduleManager: + """定时发布管理模块""" + + def __init__(self, driver): + self.driver = driver + self.utils = DouyinUtils() + + def set_schedule_time(self, schedule_time: str) -> bool: + """设置定时发布时间 + + Args: + schedule_time: 时间字符串,格式如 '2025-06-05 12:30' + """ + try: + print(f"设置定时发布: {schedule_time}") + + # 1. 选择定时发布选项 + if not self._select_schedule_option(): + return False + + # 2. 输入时间 + if not self._input_schedule_time(schedule_time): + return False + + print("定时发布设置完成") + return True + + except Exception as e: + print(f"设置定时发布失败: {e}") + return False + + def _select_schedule_option(self) -> bool: + """选择定时发布选项""" + schedule_locator = (By.XPATH, "//label[contains(@class, 'radio-d4zkru')]//span[contains(text(), '定时发布')]/ancestor::label") + return self.utils.safe_click(self.driver, schedule_locator, use_js=True) + + def _input_schedule_time(self, time_str: str) -> bool: + """输入定时发布时间""" + try: + time_input = self.utils.wait_for_element( + self.driver, + (By.XPATH, "//input[@class='semi-input semi-input-default' and @type='text' and @placeholder='日期和时间']") + ) + + if time_input: + # 使用JavaScript设置时间值,确保触发相关事件 + self.driver.execute_script(""" + const input = arguments[0]; + const dateValue = arguments[1]; + + input.value = dateValue; + + // 触发事件确保组件更新 + const inputEvent = new Event('input', { bubbles: true }); + const changeEvent = new Event('change', { bubbles: true }); + input.dispatchEvent(inputEvent); + input.dispatchEvent(changeEvent); + """, time_input, time_str) + + return True + return False + + except Exception as e: + print(f"输入定时时间失败: {e}") + return False + + +class DouyinScheduler: + """抖音发布调度器主类""" + + def __init__(self, cookies_path: str, browser_type: str = "safari"): + self.driver = self._init_driver(browser_type) + self.cookies_path = cookies_path + + # 初始化各个模块 + self.video_uploader = VideoUploader(self.driver) + self.content_editor = ContentEditor(self.driver) + self.product_linker = ProductLinker(self.driver) + self.schedule_manager = ScheduleManager(self.driver) + self.utils = DouyinUtils() + + # 初始化浏览器和登录 + self._setup_browser() + + def _init_driver(self, browser_type: str): + """初始化浏览器驱动""" + if browser_type.lower() == "safari": + driver = webdriver.Safari() + elif browser_type.lower() == "chrome": + driver = webdriver.Chrome() + elif browser_type.lower() == "firefox": + driver = webdriver.Firefox() + else: + raise ValueError(f"不支持的浏览器类型: {browser_type}") + + driver.maximize_window() + driver.set_page_load_timeout(30) + return driver + + def _setup_browser(self): + """设置浏览器并加载cookies""" + # 访问抖音创作页面 + self.driver.get("https://creator.douyin.com/creator-micro/content/upload?enter_from=dou_web") + self.utils.smart_wait(self.driver, 5) + + # 加载cookies + self._load_cookies() + + # 刷新页面 + self.driver.refresh() + self.utils.smart_wait(self.driver, 5) + + def _load_cookies(self): + """加载cookies""" + try: + with open(self.cookies_path, 'r') as f: + cookies = json.load(f) + + for cookie in cookies: + # 移除可能导致问题的属性 + for attr in ['sameSite', 'expiry']: + cookie.pop(attr, None) + + try: + self.driver.add_cookie(cookie) + except Exception as e: + print(f"添加cookie失败: {e}") + + print("Cookies加载完成") + + except Exception as e: + print(f"加载cookies失败: {e}") + + def publish_video_with_product(self, + video_path: str, + title: str, + description: str = "", + product_name: str = None, + product_info: str = None, + schedule_time: str = None) -> bool: + """发布带商品的视频 + + Args: + video_path: 视频文件路径 + title: 视频标题 + description: 视频描述 + product_name: 商品名称 + product_info: 商品信息 + schedule_time: 定时发布时间,格式如 '2025-06-05 12:30' + """ + try: + print("开始发布流程...") + + # 1. 上传视频 + if not self.video_uploader.upload_video(video_path): + print("视频上传失败") + return False + + # 2. 设置内容 + if not self.content_editor.set_content(title, description): + print("内容设置失败") + return False + + # 3. 挂载商品(如果提供) + if product_name and product_info: + if not self.product_linker.select_group_buy(product_name, product_info): + print("商品挂载失败") + return False + + # 4. 设置定时发布(如果提供) + if schedule_time: + if not self.schedule_manager.set_schedule_time(schedule_time): + print("定时设置失败") + return False + + # 5. 发布 + return self._publish() + + except Exception as e: + print(f"发布流程失败: {e}") + return False + + def _publish(self) -> bool: + """执行发布操作""" + try: + publish_locator = (By.XPATH, "//button[@class='button-dhlUZE primary-cECiOJ fixed-J9O8Yw']") + success = self.utils.safe_click(self.driver, publish_locator, use_js=True) + + if success: + print("发布成功!") + return True + else: + print("点击发布按钮失败") + return False + + except Exception as e: + print(f"发布失败: {e}") + return False + + def wait_for_user_confirmation(self, message: str = "按回车键继续..."): + """等待用户确认""" + input(message) + + def close(self): + """关闭浏览器""" + if self.driver: + self.driver.quit() + print("浏览器已关闭") + + +# 配置类 +class DouyinConfig: + """配置管理类""" + + def __init__(self, config_file: str = None): + self.config = {} + if config_file: + self.load_config(config_file) + + def load_config(self, config_file: str): + """从文件加载配置""" + try: + with open(config_file, 'r', encoding='utf-8') as f: + self.config = json.load(f) + except Exception as e: + print(f"加载配置文件失败: {e}") + + def get(self, key: str, default=None): + """获取配置值""" + return self.config.get(key, default) + + def set(self, key: str, value): + """设置配置值""" + self.config[key] = value + + def save_config(self, config_file: str): + """保存配置到文件""" + try: + with open(config_file, 'w', encoding='utf-8') as f: + json.dump(self.config, f, ensure_ascii=False, indent=2) + except Exception as e: + print(f"保存配置文件失败: {e}") + + +if __name__ == "__main__": + # 使用示例 + scheduler = DouyinScheduler("cookies.json", "safari") + + try: + # 发布视频示例 + success = scheduler.publish_video_with_product( + video_path="/Users/yarrow/autoPublisher/video/gdsc/2011_1749089195_raw.mp4", + title="广州周末亲子游", + description="带孩子探索科学的奥秘 #广州亲子游 #科学中心", + product_name="广东科学中心门票--亲子1大1小票", + product_info="广东科学中心门票", + schedule_time="2025-06-05 12:30" + ) + + if success: + print("发布流程完成") + else: + print("发布流程失败") + + # 等待用户确认 + scheduler.wait_for_user_confirmation("按回车键退出...") + + finally: + scheduler.close() \ No newline at end of file diff --git a/example_usage.py b/example_usage.py new file mode 100644 index 0000000..a0450f7 --- /dev/null +++ b/example_usage.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +""" +抖音发布调度器使用示例 +展示如何使用 DouyinScheduler 进行各种发布操作 +""" + +from douyin_scheduler import DouyinScheduler, DouyinConfig +from datetime import datetime, timedelta +import os + + +def example_basic_video_publish(): + """示例:基本视频发布""" + print("=== 基本视频发布示例 ===") + + # 创建调度器实例 + scheduler = DouyinScheduler("cookies.json", "safari") + + try: + # 发布视频 + success = scheduler.publish_video_with_product( + video_path="/Users/yarrow/autoPublisher/video/gdsc/2011_1749089195_raw.mp4", + title="广州周末亲子游", + description="带孩子探索科学的奥秘 #广州亲子游 #科学中心" + ) + + if success: + print("✅ 视频发布成功") + else: + print("❌ 视频发布失败") + + finally: + scheduler.close() + + +def example_video_with_product(): + """示例:带商品的视频发布""" + print("=== 带商品视频发布示例 ===") + + scheduler = DouyinScheduler("cookies.json", "safari") + + try: + success = scheduler.publish_video_with_product( + video_path="/Users/yarrow/autoPublisher/video/gdsc/2011_1749089195_raw.mp4", + title="广州周末亲子游 - 科学中心探索之旅", + description="周末带娃好去处!广东科学中心等你来探索 #广州亲子游 #科学中心 #周末遛娃", + product_name="广东科学中心门票--亲子1大1小票", + product_info="广东科学中心门票" + ) + + if success: + print("✅ 带商品视频发布成功") + else: + print("❌ 带商品视频发布失败") + + finally: + scheduler.close() + + +def example_scheduled_publish(): + """示例:定时发布""" + print("=== 定时发布示例 ===") + + scheduler = DouyinScheduler("cookies.json", "safari") + + try: + # 计算明天中午12点的时间 + tomorrow_noon = datetime.now() + timedelta(days=1) + tomorrow_noon = tomorrow_noon.replace(hour=12, minute=0, second=0, microsecond=0) + schedule_time = tomorrow_noon.strftime("%Y-%m-%d %H:%M") + + success = scheduler.publish_video_with_product( + video_path="/Users/yarrow/autoPublisher/video/gdsc/2011_1749089195_raw.mp4", + title="定时发布测试 - 广州科学中心", + description="定时发布功能测试 #定时发布 #广州科学中心", + schedule_time=schedule_time + ) + + if success: + print(f"✅ 定时发布设置成功,发布时间:{schedule_time}") + else: + print("❌ 定时发布设置失败") + + finally: + scheduler.close() + + +def example_batch_publish(): + """示例:批量发布""" + print("=== 批量发布示例 ===") + + # 定义要发布的视频列表 + videos_to_publish = [ + { + "video_path": "/Users/yarrow/autoPublisher/video/gdsc/2011_1749089195_raw.mp4", + "title": "广州科学中心 - 亲子游第一站", + "description": "科学启蒙从这里开始 #广州亲子游 #科学中心", + "product_name": "广东科学中心门票--亲子1大1小票", + "product_info": "广东科学中心门票", + "schedule_time": "2025-06-05 12:30" + }, + { + "video_path": "/Users/yarrow/autoPublisher/video/gdsc/2011_1749089195_raw.mp4", + "title": "广州科学中心 - 亲子游第二站", + "description": "更多精彩等你发现 #广州亲子游 #科学中心", + "product_name": "广东科学中心门票--亲子1大1小票", + "product_info": "广东科学中心门票", + "schedule_time": "2025-06-05 14:30" + } + ] + + # 创建调度器实例 + scheduler = DouyinScheduler("cookies.json", "safari") + + try: + success_count = 0 + total_count = len(videos_to_publish) + + for i, video_info in enumerate(videos_to_publish, 1): + print(f"\n--- 发布第 {i}/{total_count} 个视频 ---") + + success = scheduler.publish_video_with_product(**video_info) + + if success: + success_count += 1 + print(f"✅ 第 {i} 个视频发布成功") + else: + print(f"❌ 第 {i} 个视频发布失败") + + # 如果不是最后一个视频,需要重新打开发布页面 + if i < total_count: + scheduler.driver.get("https://creator.douyin.com/creator-micro/content/upload?enter_from=dou_web") + scheduler.utils.smart_wait(scheduler.driver, 3) + + print(f"\n=== 批量发布完成 ===") + print(f"成功发布: {success_count}/{total_count} 个视频") + + finally: + scheduler.close() + + +def example_with_config(): + """示例:使用配置文件""" + print("=== 使用配置文件示例 ===") + + # 加载配置 + config = DouyinConfig("config_example.json") + + # 获取配置参数 + browser_type = config.get("browser", {}).get("type", "safari") + cookies_path = config.get("cookies", {}).get("path", "cookies.json") + + scheduler = DouyinScheduler(cookies_path, browser_type) + + try: + # 使用配置中的默认标签 + default_hashtags = config.get("content", {}).get("default_hashtags", []) + hashtag_str = " ".join(default_hashtags) + + success = scheduler.publish_video_with_product( + video_path="/Users/yarrow/autoPublisher/video/gdsc/2011_1749089195_raw.mp4", + title="配置文件测试发布", + description=f"使用配置文件进行发布测试 {hashtag_str}" + ) + + if success: + print("✅ 使用配置文件发布成功") + else: + print("❌ 使用配置文件发布失败") + + finally: + scheduler.close() + + +def interactive_publish(): + """交互式发布""" + print("=== 交互式发布 ===") + + # 获取用户输入 + video_path = input("请输入视频文件路径: ").strip() + if not video_path or not os.path.exists(video_path): + print("❌ 视频文件不存在") + return + + title = input("请输入视频标题: ").strip() + if not title: + print("❌ 标题不能为空") + return + + description = input("请输入视频描述(可选): ").strip() + + # 询问是否挂载商品 + add_product = input("是否挂载商品?(y/n): ").strip().lower() + product_name = None + product_info = None + + if add_product == 'y': + product_name = input("请输入商品名称: ").strip() + product_info = input("请输入商品信息: ").strip() + + # 询问是否定时发布 + schedule_publish = input("是否定时发布?(y/n): ").strip().lower() + schedule_time = None + + if schedule_publish == 'y': + schedule_time = input("请输入发布时间(格式:2025-06-05 12:30): ").strip() + + # 执行发布 + scheduler = DouyinScheduler("cookies.json", "safari") + + try: + success = scheduler.publish_video_with_product( + video_path=video_path, + title=title, + description=description, + product_name=product_name, + product_info=product_info, + schedule_time=schedule_time + ) + + if success: + print("✅ 发布成功") + else: + print("❌ 发布失败") + + finally: + scheduler.close() + + +def main(): + """主函数""" + print("抖音发布调度器使用示例") + print("=" * 40) + + examples = { + "1": ("基本视频发布", example_basic_video_publish), + "2": ("带商品视频发布", example_video_with_product), + "3": ("定时发布", example_scheduled_publish), + "4": ("批量发布", example_batch_publish), + "5": ("使用配置文件", example_with_config), + "6": ("交互式发布", interactive_publish), + } + + print("请选择要运行的示例:") + for key, (desc, _) in examples.items(): + print(f"{key}. {desc}") + + choice = input("\n请输入选择 (1-6): ").strip() + + if choice in examples: + desc, func = examples[choice] + print(f"\n运行示例: {desc}") + try: + func() + except KeyboardInterrupt: + print("\n用户中断操作") + except Exception as e: + print(f"运行示例时出错: {e}") + else: + print("无效选择") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/get_cookies.py b/get_cookies.py new file mode 100644 index 0000000..6136c0c --- /dev/null +++ b/get_cookies.py @@ -0,0 +1,39 @@ +import json +import time +from selenium import webdriver +from selenium.webdriver.common.by import By + +def get_douyin_cookies(): + # 初始化浏览器 + print("正在启动浏览器...") + driver = webdriver.Safari() # 也可以使用Chrome或Firefox + + # 打开抖音登录页面 + driver.get("https://www.douyin.com/") + driver.maximize_window() + + print("请在浏览器中手动登录抖音...") + print("登录成功后,请按Enter键继续...") + input() + + # 等待登录完成,可以手动检查是否登录成功 + time.sleep(2) + + # 获取cookies + cookies = driver.get_cookies() + + # 保存cookies + cookies_file = "douyin_cookies.json" + with open(cookies_file, "w") as f: + json.dump(cookies, f) + + print(f"Cookies已保存到 {cookies_file}") + + # 退出浏览器 + driver.quit() + + return cookies_file + +if __name__ == "__main__": + cookies_file = get_douyin_cookies() + print(f"您可以在douyin_publisher.py中使用此cookies文件: {cookies_file}") \ No newline at end of file