简单封装了基本模块
This commit is contained in:
parent
21aec20273
commit
a03cbf3bac
229
README.md
Normal file
229
README.md
Normal file
@ -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来改进这个项目!
|
||||
|
||||
## 免责声明
|
||||
|
||||
本工具仅用于学习和个人使用,请遵守抖音平台的使用条款和相关法律法规。
|
533
douyin_scheduler.py
Normal file
533
douyin_scheduler.py
Normal file
@ -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()
|
264
example_usage.py
Normal file
264
example_usage.py
Normal file
@ -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()
|
39
get_cookies.py
Normal file
39
get_cookies.py
Normal file
@ -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}")
|
Loading…
x
Reference in New Issue
Block a user