From de2389c9b1bdac62dfb20cb198e4e5b974c3db2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=AC=E9=A3=8E?= Date: Thu, 6 Nov 2025 14:50:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0Docker=E5=AE=B9?= =?UTF-8?q?=E5=99=A8=E7=8E=AF=E5=A2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 8 ++++ Dockerfile | 56 +++++++++++++++++++++++++++ README.md | 23 ++++++++++- conf.example.py | 1 + myUtils/login.py | 10 ++--- sau_backend.py | 4 +- uploader/baijiahao_uploader/main.py | 11 +++--- uploader/douyin_uploader/main.py | 11 +++--- uploader/ks_uploader/main.py | 11 +++--- uploader/tencent_uploader/main.py | 9 +++-- uploader/tk_uploader/main.py | 8 ++-- uploader/tk_uploader/main_chrome.py | 9 +++-- uploader/xhs_uploader/main.py | 4 +- uploader/xiaohongshu_uploader/main.py | 11 +++--- 14 files changed, 135 insertions(+), 41 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..33e35a5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.venv +.git +.idea +cookies +conf.py +sau_frontend/dist +sau_frontend/node_modules +sau_frontend/package-lock.json \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fbaef1e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,56 @@ +FROM node:22.21.1 as builder + +WORKDIR /app + +RUN npm config set registry https://registry.npmmirror.com + +COPY sau_frontend . + +RUN npm install + +ENV NODE_ENV=production +ENV PATH=/app/node_modules/.bin:$PATH + +RUN npm run build + + +FROM python:3.10.19 + +WORKDIR /app + +ENV PLAYWRIGHT_BROWSERS_PATH=/opt/playwright + +COPY --from=builder /app/dist/index.html /app +COPY --from=builder /app/dist/assets /app/assets +COPY --from=builder /app/dist/vite.svg /app + +RUN apt-get update && apt-get install -y --no-install-recommends libnss3 \ + libnspr4 \ + libdbus-1-3 \ + libatk1.0-0 \ + libatk-bridge2.0-0 \ + libatspi2.0-0 \ + libxcomposite1 \ + libxdamage1 \ + libxfixes3 \ + libxrandr2 \ + libgbm1 \ + libxkbcommon0 \ + libasound2 && rm -rf /var/lib/apt/lists/* + +RUN pip config set global.index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple + +COPY requirements.txt requirements.txt + +RUN pip install -r requirements.txt + +RUN playwright install chromium-headless-shell + +COPY . . + +RUN cp conf.example.py conf.py + +EXPOSE 80 +EXPOSE 5409 + +CMD ["python", "sau_backend.py"] diff --git a/README.md b/README.md index 29eb947..a2722b3 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ - **功能增强**: - [x] 更易用的版本 (GUI / CLI 交互优化) - [x] API 封装 - - [ ] Docker 部署 + - [x] Docker 部署 - [ ] 自动化上传 (更智能的调度策略) - [ ] 多线程/异步上传优化 - [ ] Slack/消息推送通知 @@ -164,6 +164,27 @@ python examples/upload_video_to_douyin.py ``` +## Docker 环境 +### 自己构建镜像 +1. **构建Docker镜像**: + ``` + docker build -t social-auto-upload:latest . + ``` +2. **运行Docker容器**: + ``` + docker run -d -it -p 5409:5409 social-auto-upload:latest + ``` +### 使用预构建镜像 +1. **拉取镜像**: + ``` + docker pull gzxy/social-auto-upload:latest + ``` +2. **运行Docker容器**: + ``` + docker run -d -it -p 5409:5409 gzxy/social-auto-upload:latest + ``` +启动容器后访问:[http://localhost:5409](http://localhost:5409) + ## 🐇项目背景 该项目最初是我个人用于自动化管理社交媒体视频发布的工具。我的主要发布策略是提前一天设置定时发布,因此项目中很多定时发布相关的逻辑是基于“第二天”的时间进行计算的。 diff --git a/conf.example.py b/conf.example.py index b653ae4..7f45d63 100644 --- a/conf.example.py +++ b/conf.example.py @@ -3,3 +3,4 @@ from pathlib import Path BASE_DIR = Path(__file__).parent.resolve() XHS_SERVER = "http://127.0.0.1:11901" LOCAL_CHROME_PATH = "" # change me necessary! for example C:/Program Files/Google/Chrome/Application/chrome.exe +LOCAL_CHROME_HEADLESS = True diff --git a/myUtils/login.py b/myUtils/login.py index 56f3980..4437f99 100644 --- a/myUtils/login.py +++ b/myUtils/login.py @@ -7,7 +7,7 @@ from myUtils.auth import check_cookie from utils.base_social_media import set_init_script import uuid from pathlib import Path -from conf import BASE_DIR +from conf import BASE_DIR, LOCAL_CHROME_HEADLESS # 抖音登录 async def douyin_cookie_gen(id,status_queue): @@ -18,7 +18,7 @@ async def douyin_cookie_gen(id,status_queue): url_changed_event.set() async with async_playwright() as playwright: options = { - 'headless': False + 'headless': LOCAL_CHROME_HEADLESS } # Make sure to run headed. browser = await playwright.chromium.launch(**options) @@ -88,7 +88,7 @@ async def get_tencent_cookie(id,status_queue): 'args': [ '--lang en-GB' ], - 'headless': False, # Set headless option here + 'headless': LOCAL_CHROME_HEADLESS, # Set headless option here } # Make sure to run headed. browser = await playwright.chromium.launch(**options) @@ -165,7 +165,7 @@ async def get_ks_cookie(id,status_queue): 'args': [ '--lang en-GB' ], - 'headless': False, # Set headless option here + 'headless': LOCAL_CHROME_HEADLESS, # Set headless option here } # Make sure to run headed. browser = await playwright.chromium.launch(**options) @@ -241,7 +241,7 @@ async def xiaohongshu_cookie_gen(id,status_queue): 'args': [ '--lang en-GB' ], - 'headless': False, # Set headless option here + 'headless': LOCAL_CHROME_HEADLESS, # Set headless option here } # Make sure to run headed. browser = await playwright.chromium.launch(**options) diff --git a/sau_backend.py b/sau_backend.py index e3b92ca..83631d6 100644 --- a/sau_backend.py +++ b/sau_backend.py @@ -37,8 +37,8 @@ def favicon(filename): # (未来打包用) @app.route('/') -def hello_world(): # put application's code here - return render_template('index.html') +def index(): # put application's code here + return send_from_directory(current_dir, 'index.html') @app.route('/upload', methods=['POST']) def upload_file(): diff --git a/uploader/baijiahao_uploader/main.py b/uploader/baijiahao_uploader/main.py index 30ded1a..2839afd 100644 --- a/uploader/baijiahao_uploader/main.py +++ b/uploader/baijiahao_uploader/main.py @@ -7,7 +7,7 @@ import os import time import asyncio -from conf import LOCAL_CHROME_PATH +from conf import LOCAL_CHROME_PATH, LOCAL_CHROME_HEADLESS from utils.base_social_media import set_init_script from utils.log import baijiahao_logger from utils.network import async_retry @@ -19,7 +19,7 @@ async def baijiahao_cookie_gen(account_file): 'args': [ '--lang en-GB' ], - 'headless': False, # Set headless option here + 'headless': LOCAL_CHROME_HEADLESS, # Set headless option here } # Make sure to run headed. browser = await playwright.chromium.launch(**options) @@ -37,7 +37,7 @@ async def baijiahao_cookie_gen(account_file): async def cookie_auth(account_file): async with async_playwright() as playwright: - browser = await playwright.chromium.launch(headless=True) + browser = await playwright.chromium.launch(headless=LOCAL_CHROME_HEADLESS) context = await browser.new_context(storage_state=account_file) context = await set_init_script(context) # 创建一个新的页面 @@ -71,6 +71,7 @@ class BaiJiaHaoVideo(object): self.account_file = account_file self.date_format = '%Y年%m月%d日 %H:%M' self.local_executable_path = LOCAL_CHROME_PATH + self.headless = LOCAL_CHROME_HEADLESS self.proxy_setting = proxy_setting async def set_schedule_time(self, page, publish_date): @@ -119,7 +120,7 @@ class BaiJiaHaoVideo(object): async def upload(self, playwright: Playwright) -> None: # 使用 Chromium 浏览器启动一个浏览器实例 - browser = await playwright.chromium.launch(headless=False, executable_path=self.local_executable_path, proxy=self.proxy_setting) + browser = await playwright.chromium.launch(headless=self.headless, executable_path=self.local_executable_path, proxy=self.proxy_setting) # 创建一个浏览器上下文,使用指定的 cookie 文件 context = await browser.new_context(storage_state=f"{self.account_file}", user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.4324.150 Safari/537.36') # context = await set_init_script(context) @@ -252,7 +253,7 @@ class BaiJiaHaoVideo(object): # 使用 AI成片 功能 async def ai2video(self, playwright: Playwright) -> None: # 使用 Chromium 浏览器启动一个浏览器实例 - browser = await playwright.chromium.launch(headless=False, executable_path=self.local_executable_path, proxy=self.proxy_setting) + browser = await playwright.chromium.launch(headless=self.headless, executable_path=self.local_executable_path, proxy=self.proxy_setting) # 创建一个浏览器上下文,使用指定的 cookie 文件 context = await browser.new_context( viewport={"width": 1600, "height": 900}, diff --git a/uploader/douyin_uploader/main.py b/uploader/douyin_uploader/main.py index e3789b6..c4095f0 100644 --- a/uploader/douyin_uploader/main.py +++ b/uploader/douyin_uploader/main.py @@ -5,14 +5,14 @@ from playwright.async_api import Playwright, async_playwright, Page import os import asyncio -from conf import LOCAL_CHROME_PATH +from conf import LOCAL_CHROME_PATH, LOCAL_CHROME_HEADLESS from utils.base_social_media import set_init_script from utils.log import douyin_logger async def cookie_auth(account_file): async with async_playwright() as playwright: - browser = await playwright.chromium.launch(headless=True) + browser = await playwright.chromium.launch(headless=LOCAL_CHROME_HEADLESS) context = await browser.new_context(storage_state=account_file) context = await set_init_script(context) # 创建一个新的页面 @@ -48,7 +48,7 @@ async def douyin_setup(account_file, handle=False): async def douyin_cookie_gen(account_file): async with async_playwright() as playwright: options = { - 'headless': False + 'headless': LOCAL_CHROME_HEADLESS } # Make sure to run headed. browser = await playwright.chromium.launch(**options) @@ -72,6 +72,7 @@ class DouYinVideo(object): self.account_file = account_file self.date_format = '%Y年%m月%d日 %H:%M' self.local_executable_path = LOCAL_CHROME_PATH + self.headless = LOCAL_CHROME_HEADLESS self.thumbnail_path = thumbnail_path self.productLink = productLink self.productTitle = productTitle @@ -99,9 +100,9 @@ class DouYinVideo(object): async def upload(self, playwright: Playwright) -> None: # 使用 Chromium 浏览器启动一个浏览器实例 if self.local_executable_path: - browser = await playwright.chromium.launch(headless=False, executable_path=self.local_executable_path) + browser = await playwright.chromium.launch(headless=self.headless, executable_path=self.local_executable_path) else: - browser = await playwright.chromium.launch(headless=False) + browser = await playwright.chromium.launch(headless=self.headless) # 创建一个浏览器上下文,使用指定的 cookie 文件 context = await browser.new_context(storage_state=f"{self.account_file}") context = await set_init_script(context) diff --git a/uploader/ks_uploader/main.py b/uploader/ks_uploader/main.py index 2ca2e6d..7bf6aed 100644 --- a/uploader/ks_uploader/main.py +++ b/uploader/ks_uploader/main.py @@ -5,7 +5,7 @@ from playwright.async_api import Playwright, async_playwright import os import asyncio -from conf import LOCAL_CHROME_PATH +from conf import LOCAL_CHROME_PATH, LOCAL_CHROME_HEADLESS from utils.base_social_media import set_init_script from utils.files_times import get_absolute_path from utils.log import kuaishou_logger @@ -13,7 +13,7 @@ from utils.log import kuaishou_logger async def cookie_auth(account_file): async with async_playwright() as playwright: - browser = await playwright.chromium.launch(headless=True) + browser = await playwright.chromium.launch(headless=LOCAL_CHROME_HEADLESS) context = await browser.new_context(storage_state=account_file) context = await set_init_script(context) # 创建一个新的页面 @@ -46,7 +46,7 @@ async def get_ks_cookie(account_file): 'args': [ '--lang en-GB' ], - 'headless': False, # Set headless option here + 'headless': LOCAL_CHROME_HEADLESS, # Set headless option here } # Make sure to run headed. browser = await playwright.chromium.launch(**options) @@ -70,6 +70,7 @@ class KSVideo(object): self.account_file = account_file self.date_format = '%Y-%m-%d %H:%M' self.local_executable_path = LOCAL_CHROME_PATH + self.headless = LOCAL_CHROME_HEADLESS async def handle_upload_error(self, page): kuaishou_logger.error("视频出错了,重新上传中") @@ -80,12 +81,12 @@ class KSVideo(object): print(self.local_executable_path) if self.local_executable_path: browser = await playwright.chromium.launch( - headless=False, + headless=self.headless, executable_path=self.local_executable_path, ) else: browser = await playwright.chromium.launch( - headless=False + headless=self.headless ) # 创建一个浏览器上下文,使用指定的 cookie 文件 context = await browser.new_context(storage_state=f"{self.account_file}") context = await set_init_script(context) diff --git a/uploader/tencent_uploader/main.py b/uploader/tencent_uploader/main.py index 0f9d76a..d693250 100644 --- a/uploader/tencent_uploader/main.py +++ b/uploader/tencent_uploader/main.py @@ -5,7 +5,7 @@ from playwright.async_api import Playwright, async_playwright import os import asyncio -from conf import LOCAL_CHROME_PATH +from conf import LOCAL_CHROME_PATH, LOCAL_CHROME_HEADLESS from utils.base_social_media import set_init_script from utils.files_times import get_absolute_path from utils.log import tencent_logger @@ -33,7 +33,7 @@ def format_str_for_short_title(origin_title: str) -> str: async def cookie_auth(account_file): async with async_playwright() as playwright: - browser = await playwright.chromium.launch(headless=True) + browser = await playwright.chromium.launch(headless=LOCAL_CHROME_HEADLESS) context = await browser.new_context(storage_state=account_file) context = await set_init_script(context) # 创建一个新的页面 @@ -55,7 +55,7 @@ async def get_tencent_cookie(account_file): 'args': [ '--lang en-GB' ], - 'headless': False, # Set headless option here + 'headless': LOCAL_CHROME_HEADLESS, # Set headless option here } # Make sure to run headed. browser = await playwright.chromium.launch(**options) @@ -90,6 +90,7 @@ class TencentVideo(object): self.account_file = account_file self.category = category self.local_executable_path = LOCAL_CHROME_PATH + self.headless = LOCAL_CHROME_HEADLESS async def set_schedule_time_tencent(self, page, publish_date): label_element = page.locator("label").filter(has_text="定时").nth(1) @@ -135,7 +136,7 @@ class TencentVideo(object): async def upload(self, playwright: Playwright) -> None: # 使用 Chromium (这里使用系统内浏览器,用chromium 会造成h264错误 - browser = await playwright.chromium.launch(headless=False, executable_path=self.local_executable_path) + browser = await playwright.chromium.launch(headless=self.headless, executable_path=self.local_executable_path) # 创建一个浏览器上下文,使用指定的 cookie 文件 context = await browser.new_context(storage_state=f"{self.account_file}") context = await set_init_script(context) diff --git a/uploader/tk_uploader/main.py b/uploader/tk_uploader/main.py index 6a9500d..861be23 100644 --- a/uploader/tk_uploader/main.py +++ b/uploader/tk_uploader/main.py @@ -9,11 +9,12 @@ from uploader.tk_uploader.tk_config import Tk_Locator from utils.base_social_media import set_init_script from utils.files_times import get_absolute_path from utils.log import tiktok_logger +from conf import LOCAL_CHROME_HEADLESS async def cookie_auth(account_file): async with async_playwright() as playwright: - browser = await playwright.firefox.launch(headless=True) + browser = await playwright.firefox.launch(headless=LOCAL_CHROME_HEADLESS) context = await browser.new_context(storage_state=account_file) context = await set_init_script(context) # 创建一个新的页面 @@ -53,7 +54,7 @@ async def get_tiktok_cookie(account_file): 'args': [ '--lang en-GB', ], - 'headless': False, # Set headless option here + 'headless': LOCAL_CHROME_HEADLESS, # Set headless option here } # Make sure to run headed. browser = await playwright.firefox.launch(**options) @@ -75,6 +76,7 @@ class TiktokVideo(object): self.tags = tags self.publish_date = publish_date self.account_file = account_file + self.headless = LOCAL_CHROME_HEADLESS self.locator_base = None @@ -140,7 +142,7 @@ class TiktokVideo(object): await file_chooser.set_files(self.file_path) async def upload(self, playwright: Playwright) -> None: - browser = await playwright.firefox.launch(headless=False) + browser = await playwright.firefox.launch(headless=self.headless) context = await browser.new_context(storage_state=f"{self.account_file}") context = await set_init_script(context) page = await context.new_page() diff --git a/uploader/tk_uploader/main_chrome.py b/uploader/tk_uploader/main_chrome.py index 3d81442..a03c198 100644 --- a/uploader/tk_uploader/main_chrome.py +++ b/uploader/tk_uploader/main_chrome.py @@ -6,7 +6,7 @@ from playwright.async_api import Playwright, async_playwright import os import asyncio -from conf import LOCAL_CHROME_PATH +from conf import LOCAL_CHROME_PATH, LOCAL_CHROME_HEADLESS from uploader.tk_uploader.tk_config import Tk_Locator from utils.base_social_media import set_init_script from utils.files_times import get_absolute_path @@ -15,7 +15,7 @@ from utils.log import tiktok_logger async def cookie_auth(account_file): async with async_playwright() as playwright: - browser = await playwright.chromium.launch(headless=True) + browser = await playwright.chromium.launch(headless=LOCAL_CHROME_HEADLESS) context = await browser.new_context(storage_state=account_file) context = await set_init_script(context) # 创建一个新的页面 @@ -55,7 +55,7 @@ async def get_tiktok_cookie(account_file): 'args': [ '--lang en-GB', ], - 'headless': False, # Set headless option here + 'headless': LOCAL_CHROME_HEADLESS, # Set headless option here } # Make sure to run headed. browser = await playwright.chromium.launch(**options) @@ -79,6 +79,7 @@ class TiktokVideo(object): self.thumbnail_path = thumbnail_path self.account_file = account_file self.local_executable_path = LOCAL_CHROME_PATH + self.headless = LOCAL_CHROME_HEADLESS self.locator_base = None async def set_schedule_time(self, page, publish_date): @@ -146,7 +147,7 @@ class TiktokVideo(object): await file_chooser.set_files(self.file_path) async def upload(self, playwright: Playwright) -> None: - browser = await playwright.chromium.launch(headless=False, executable_path=self.local_executable_path) + browser = await playwright.chromium.launch(headless=self.headless, executable_path=self.local_executable_path) context = await browser.new_context(storage_state=f"{self.account_file}") # context = await set_init_script(context) page = await context.new_page() diff --git a/uploader/xhs_uploader/main.py b/uploader/xhs_uploader/main.py index dea9b14..7030dc7 100644 --- a/uploader/xhs_uploader/main.py +++ b/uploader/xhs_uploader/main.py @@ -6,7 +6,7 @@ from time import sleep import requests from playwright.sync_api import sync_playwright -from conf import BASE_DIR, XHS_SERVER +from conf import BASE_DIR, XHS_SERVER, LOCAL_CHROME_HEADLESS config = configparser.RawConfigParser() config.read('accounts.ini') @@ -20,7 +20,7 @@ def sign_local(uri, data=None, a1="", web_session=""): chromium = playwright.chromium # 如果一直失败可尝试设置成 False 让其打开浏览器,适当添加 sleep 可查看浏览器状态 - browser = chromium.launch(headless=True) + browser = chromium.launch(headless=LOCAL_CHROME_HEADLESS) browser_context = browser.new_context() browser_context.add_init_script(path=stealth_js_path) diff --git a/uploader/xiaohongshu_uploader/main.py b/uploader/xiaohongshu_uploader/main.py index 704769d..e6f902b 100644 --- a/uploader/xiaohongshu_uploader/main.py +++ b/uploader/xiaohongshu_uploader/main.py @@ -5,14 +5,14 @@ from playwright.async_api import Playwright, async_playwright, Page import os import asyncio -from conf import LOCAL_CHROME_PATH +from conf import LOCAL_CHROME_PATH, LOCAL_CHROME_HEADLESS from utils.base_social_media import set_init_script from utils.log import xiaohongshu_logger async def cookie_auth(account_file): async with async_playwright() as playwright: - browser = await playwright.chromium.launch(headless=True) + browser = await playwright.chromium.launch(headless=LOCAL_CHROME_HEADLESS) context = await browser.new_context(storage_state=account_file) context = await set_init_script(context) # 创建一个新的页面 @@ -48,7 +48,7 @@ async def xiaohongshu_setup(account_file, handle=False): async def xiaohongshu_cookie_gen(account_file): async with async_playwright() as playwright: options = { - 'headless': False + 'headless': LOCAL_CHROME_HEADLESS } # Make sure to run headed. browser = await playwright.chromium.launch(**options) @@ -72,6 +72,7 @@ class XiaoHongShuVideo(object): self.account_file = account_file self.date_format = '%Y年%m月%d日 %H:%M' self.local_executable_path = LOCAL_CHROME_PATH + self.headless = LOCAL_CHROME_HEADLESS self.thumbnail_path = thumbnail_path async def set_schedule_time_xiaohongshu(self, page, publish_date): @@ -108,9 +109,9 @@ class XiaoHongShuVideo(object): async def upload(self, playwright: Playwright) -> None: # 使用 Chromium 浏览器启动一个浏览器实例 if self.local_executable_path: - browser = await playwright.chromium.launch(headless=False, executable_path=self.local_executable_path) + browser = await playwright.chromium.launch(headless=self.headless, executable_path=self.local_executable_path) else: - browser = await playwright.chromium.launch(headless=False) + browser = await playwright.chromium.launch(headless=self.headless) # 创建一个浏览器上下文,使用指定的 cookie 文件 context = await browser.new_context( viewport={"width": 1600, "height": 900},