diff --git a/0.webp b/0.webp deleted file mode 100644 index 87ba06f..0000000 Binary files a/0.webp and /dev/null differ diff --git a/1.webp b/1.webp deleted file mode 100644 index 41d24b3..0000000 Binary files a/1.webp and /dev/null differ diff --git a/2.webp b/2.webp deleted file mode 100644 index 77fd9de..0000000 Binary files a/2.webp and /dev/null differ diff --git a/3.webp b/3.webp deleted file mode 100644 index 0538be6..0000000 Binary files a/3.webp and /dev/null differ diff --git a/4.webp b/4.webp deleted file mode 100644 index f23b280..0000000 Binary files a/4.webp and /dev/null differ diff --git a/5.webp b/5.webp deleted file mode 100644 index 1d96203..0000000 Binary files a/5.webp and /dev/null differ diff --git a/6.webp b/6.webp deleted file mode 100644 index fc90541..0000000 Binary files a/6.webp and /dev/null differ diff --git a/7.webp b/7.webp deleted file mode 100644 index 8288460..0000000 Binary files a/7.webp and /dev/null differ diff --git a/8.webp b/8.webp deleted file mode 100644 index d305f34..0000000 Binary files a/8.webp and /dev/null differ diff --git a/gui_config.py b/gui_config.py deleted file mode 100644 index 71247dd..0000000 --- a/gui_config.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -兼容入口:使用统一的 GUI 实现。 -""" - -from 自动化_gui import main - - -if __name__ == "__main__": - main() diff --git a/https_server_example.py b/https_server_example.py deleted file mode 100644 index 624091b..0000000 --- a/https_server_example.py +++ /dev/null @@ -1,202 +0,0 @@ -""" -微信小程序 HTTPS 后端服务器示例 -支持 Flask 和 FastAPI 两种框架 -""" - -# ==================== Flask 版本 ==================== -from flask import Flask, jsonify, request -from flask_cors import CORS -import ssl -import os - -app = Flask(__name__) -# 配置 CORS,允许微信小程序访问 -CORS(app, resources={ - r"/api/*": { - "origins": "*", # 生产环境建议指定具体域名 - "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"], - "allow_headers": ["Content-Type", "Authorization"] - } -}) - -@app.route('/api/test', methods=['GET']) -def test(): - """测试接口""" - return jsonify({ - 'code': 200, - 'message': 'HTTPS 接口测试成功', - 'data': { - 'timestamp': request.headers.get('X-Request-Time', ''), - 'user_agent': request.headers.get('User-Agent', '') - } - }) - -@app.route('/api/user/login', methods=['POST']) -def login(): - """登录接口示例""" - data = request.get_json() - username = data.get('username', '') - password = data.get('password', '') - - # 这里添加您的登录逻辑 - if username and password: - return jsonify({ - 'code': 200, - 'message': '登录成功', - 'data': { - 'token': 'example_token_12345', - 'user_id': 1 - } - }) - else: - return jsonify({ - 'code': 400, - 'message': '用户名或密码不能为空' - }), 400 - -@app.route('/api/health', methods=['GET']) -def health(): - """健康检查接口""" - return jsonify({ - 'status': 'healthy', - 'service': 'wechat-miniprogram-api' - }) - -def run_flask_server(): - """运行 Flask HTTPS 服务器""" - # SSL 证书路径(根据您的实际情况修改) - cert_file = os.getenv('SSL_CERT', '/etc/letsencrypt/live/yourdomain.com/fullchain.pem') - key_file = os.getenv('SSL_KEY', '/etc/letsencrypt/live/yourdomain.com/privkey.pem') - - # 检查证书文件是否存在 - if not os.path.exists(cert_file) or not os.path.exists(key_file): - print(f"警告:证书文件不存在!") - print(f"证书路径: {cert_file}") - print(f"私钥路径: {key_file}") - print("请先配置 SSL 证书,或使用 Nginx 反向代理") - # 开发环境可以运行 HTTP(仅用于测试) - app.run(host='0.0.0.0', port=8000, debug=True) - return - - # 配置 SSL - context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) - context.load_cert_chain(cert_file, key_file) - - print("启动 Flask HTTPS 服务器...") - print(f"访问地址: https://yourdomain.com/api/test") - - app.run( - host='0.0.0.0', - port=443, - ssl_context=context, - debug=False # 生产环境设为 False - ) - - -# ==================== FastAPI 版本 ==================== -try: - from fastapi import FastAPI, Request - from fastapi.middleware.cors import CORSMiddleware - from fastapi.responses import JSONResponse - import uvicorn - - fastapi_app = FastAPI(title="微信小程序 API", version="1.0.0") - - # 配置 CORS - fastapi_app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # 生产环境建议指定具体域名 - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) - - @fastapi_app.get("/api/test") - async def test_api(request: Request): - """测试接口""" - return { - 'code': 200, - 'message': 'HTTPS 接口测试成功', - 'data': { - 'timestamp': request.headers.get('x-request-time', ''), - 'user_agent': request.headers.get('user-agent', '') - } - } - - @fastapi_app.post("/api/user/login") - async def login_api(data: dict): - """登录接口示例""" - username = data.get('username', '') - password = data.get('password', '') - - if username and password: - return { - 'code': 200, - 'message': '登录成功', - 'data': { - 'token': 'example_token_12345', - 'user_id': 1 - } - } - else: - return JSONResponse( - status_code=400, - content={ - 'code': 400, - 'message': '用户名或密码不能为空' - } - ) - - @fastapi_app.get("/api/health") - async def health_check(): - """健康检查接口""" - return { - 'status': 'healthy', - 'service': 'wechat-miniprogram-api' - } - - def run_fastapi_server(): - """运行 FastAPI HTTPS 服务器""" - cert_file = os.getenv('SSL_CERT', '/etc/letsencrypt/live/yourdomain.com/fullchain.pem') - key_file = os.getenv('SSL_KEY', '/etc/letsencrypt/live/yourdomain.com/privkey.pem') - - if not os.path.exists(cert_file) or not os.path.exists(key_file): - print(f"警告:证书文件不存在!") - print(f"证书路径: {cert_file}") - print(f"私钥路径: {key_file}") - print("请先配置 SSL 证书,或使用 Nginx 反向代理") - # 开发环境可以运行 HTTP(仅用于测试) - uvicorn.run(fastapi_app, host="0.0.0.0", port=8000) - return - - print("启动 FastAPI HTTPS 服务器...") - print(f"访问地址: https://yourdomain.com/api/test") - - uvicorn.run( - fastapi_app, - host="0.0.0.0", - port=443, - ssl_keyfile=key_file, - ssl_certfile=cert_file - ) - -except ImportError: - print("FastAPI 未安装,跳过 FastAPI 示例") - print("安装命令: pip install fastapi uvicorn") - - -# ==================== 主程序 ==================== -if __name__ == '__main__': - import sys - - # 选择框架:'flask' 或 'fastapi' - framework = os.getenv('FRAMEWORK', 'flask').lower() - - if framework == 'fastapi': - try: - run_fastapi_server() - except NameError: - print("FastAPI 未安装,使用 Flask") - run_flask_server() - else: - run_flask_server() diff --git a/main.py b/main.py deleted file mode 100644 index e6f783f..0000000 --- a/main.py +++ /dev/null @@ -1,48 +0,0 @@ -import requests - - -cookies = { - 'abRequestId': '343dbd09-707e-59a3-9315-3b6fa1c3ff34', - 'webBuild': '5.7.0', - 'xsecappid': 'xhs-pc-web', - 'a1': '19bbae7730ahxzrs5lm6s46vde8fq350g5klg6uij50000427550', - 'webId': '17c9fe1d1bc556b2837a35fb01b770b5', - 'gid': 'yjDD0dWYyWk8yjDD0dWWq2K780xCuFM216KMT3V4KhfdY128iAq28k8884JW2288YJWqyYjD', - 'web_session': '0400698f1bdf69567ccb126e523b4ba45d4326', - 'id_token': 'VjEAANiXRALq8n+D/Uh5zBxUZgZQea2cBzD/+4ZtKQHrx2FPtJYJfV+n5N7LJDdNZmVmMugQNdlm0mg6Dy78u0wHOnF2RDB4ZB7i2whxyVT8v97Yrbwz4hbQM3EtVEyNMgzIvZnR', - 'websectiga': '29098a4cf41f76ee3f8db19051aaa60c0fc7c5e305572fec762da32d457d76ae', - 'sec_poison_id': '36102520-3a09-4447-a36f-a1c31b15b950', - 'acw_tc': '0a00d49317683703864606878eb20c852cedbeb990c4ffae205ebc083fcc93', - 'loadts': '1768370387610', -} - -headers = { - 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', - 'cache-control': 'no-cache', - 'dnt': '1', - 'pragma': 'no-cache', - 'priority': 'u=0, i', - 'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"', - 'sec-fetch-dest': 'document', - 'sec-fetch-mode': 'navigate', - 'sec-fetch-site': 'same-origin', - 'sec-fetch-user': '?1', - 'upgrade-insecure-requests': '1', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0', - # 'cookie': 'abRequestId=343dbd09-707e-59a3-9315-3b6fa1c3ff34; webBuild=5.7.0; xsecappid=xhs-pc-web; a1=19bbae7730ahxzrs5lm6s46vde8fq350g5klg6uij50000427550; webId=17c9fe1d1bc556b2837a35fb01b770b5; gid=yjDD0dWYyWk8yjDD0dWWq2K780xCuFM216KMT3V4KhfdY128iAq28k8884JW2288YJWqyYjD; web_session=0400698f1bdf69567ccb126e523b4ba45d4326; id_token=VjEAANiXRALq8n+D/Uh5zBxUZgZQea2cBzD/+4ZtKQHrx2FPtJYJfV+n5N7LJDdNZmVmMugQNdlm0mg6Dy78u0wHOnF2RDB4ZB7i2whxyVT8v97Yrbwz4hbQM3EtVEyNMgzIvZnR; websectiga=29098a4cf41f76ee3f8db19051aaa60c0fc7c5e305572fec762da32d457d76ae; sec_poison_id=36102520-3a09-4447-a36f-a1c31b15b950; acw_tc=0a00d49317683703864606878eb20c852cedbeb990c4ffae205ebc083fcc93; loadts=1768370387610', -} - -response = requests.get( - 'https://www.xiaohongshu.com/explore/68eab3870000000004004112?xsec_token=ABhDIDWci_QiY0PVUtlkViv01hH_8c3AiHfagOPVFNVms=&xsec_source=pc_search&source=unknown', - # cookies=cookies, - headers=headers, -) - -# 保存HTML到文件 -with open('1.html', 'w', encoding='utf-8') as f: - f.write(response.text) - -print("HTML已保存到 1.html") diff --git a/parse_html.py b/parse_html.py deleted file mode 100644 index 3538f78..0000000 --- a/parse_html.py +++ /dev/null @@ -1,281 +0,0 @@ -import re -import json -import sys -import io -from bs4 import BeautifulSoup - -# 设置标准输出编码为UTF-8,避免Windows控制台编码问题 -if sys.platform == 'win32': - sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') - - -def extract_video_from_meta(html_content): - """ - 从HTML的meta标签中提取视频信息 - - Args: - html_content: HTML内容字符串 - - Returns: - dict: 视频信息字典,如果没有找到则返回None - """ - try: - soup = BeautifulSoup(html_content, 'html.parser') - video_info = {} - - # 提取og:video标签 - og_video = soup.find('meta', {'name': 'og:video'}) - if og_video and og_video.get('content'): - video_info['url'] = og_video.get('content') - - # 提取视频时长 - og_videotime = soup.find('meta', {'name': 'og:videotime'}) - if og_videotime and og_videotime.get('content'): - video_info['time'] = og_videotime.get('content') - - # 提取视频质量 - og_videoquality = soup.find('meta', {'name': 'og:videoquality'}) - if og_videoquality and og_videoquality.get('content'): - video_info['quality'] = og_videoquality.get('content') - - # 如果找到了视频URL,返回视频信息 - if video_info.get('url'): - return video_info - - return None - except Exception as e: - print(f"从meta标签提取视频信息时出错:{e}") - return None - - -def parse_html_file(html_file='1.html'): - """ - 解析HTML文件,提取标题、描述、图片列表、视频列表和话题 - - Args: - html_file: HTML文件路径 - - Returns: - dict: 解析出的笔记数据 - """ - try: - # 读取HTML文件 - with open(html_file, 'r', encoding='utf-8') as f: - html_content = f.read() - - # 提取meta标签中的视频信息 - video_info = extract_video_from_meta(html_content) - - # 使用正则表达式提取window.__INITIAL_STATE__的内容 - pattern = r'' - match = re.search(pattern, html_content, re.DOTALL) - - if not match: - print("未找到 window.__INITIAL_STATE__ 数据") - # 如果只有视频信息,返回视频信息 - if video_info: - return {'videos': [video_info]} - return None - - # 提取JSON字符串 - json_str = match.group(1) - - # 处理JavaScript中的undefined值(Python JSON不支持undefined) - json_str = re.sub(r'\bundefined\b', 'null', json_str) - - # 解析JSON - initial_state = json.loads(json_str) - - # 提取笔记数据 - note_data = extract_note_data(initial_state) - - # 如果提取到视频信息,添加到笔记数据中 - if video_info and note_data: - if 'videos' not in note_data or not note_data['videos']: - note_data['videos'] = [] - note_data['videos'].append(video_info) - - return note_data - - except FileNotFoundError: - print(f"错误:找不到文件 {html_file}") - return None - except json.JSONDecodeError as e: - print(f"JSON解析错误:{e}") - return None - except Exception as e: - print(f"解析错误:{e}") - return None - - -def extract_note_data(initial_state): - """ - 从初始状态中提取笔记数据(只提取标题、描述、图片列表、视频列表和话题) - - Args: - initial_state: window.__INITIAL_STATE__ 解析后的字典 - - Returns: - dict: 提取的笔记数据 - """ - try: - # 获取笔记详情 - note_store = initial_state.get('note', {}) - note_detail_map = note_store.get('noteDetailMap', {}) - - # 获取第一个笔记ID - first_note_id = note_store.get('firstNoteId') - if not first_note_id: - # 如果没有firstNoteId,尝试获取noteDetailMap中的第一个key - if note_detail_map: - first_note_id = list(note_detail_map.keys())[0] - else: - print("未找到笔记ID") - return None - - # 获取笔记详情 - note_detail = note_detail_map.get(first_note_id, {}) - note_info = note_detail.get('note', {}) - - if not note_info: - print("未找到笔记信息") - return None - - # 只提取需要的字段 - extracted_data = { - 'title': note_info.get('title'), - 'desc': note_info.get('desc'), - 'images': [], - 'videos': [], - 'topics': [] - } - - # 提取图片信息 - image_list = note_info.get('imageList', []) - for img in image_list: - image_data = { - 'url': img.get('urlDefault') or img.get('url'), - 'urlPre': img.get('urlPre'), - 'width': img.get('width'), - 'height': img.get('height'), - } - extracted_data['images'].append(image_data) - - # 提取视频信息(如果存在) - video_info = note_info.get('video', {}) - if video_info: - video_data = {} - - # 尝试提取视频URL - media = video_info.get('media', {}) - if media: - stream = media.get('stream', {}) - if stream: - hls = stream.get('hls', {}) - if hls: - video_data['url'] = hls.get('masterUrl') or hls.get('url') - # 如果没有hls,尝试其他字段 - if not video_data.get('url'): - video_data['url'] = media.get('url') or media.get('videoUrl') - - # 提取视频封面 - if video_info.get('cover'): - video_data['cover'] = video_info.get('cover') - - # 提取视频时长 - if video_info.get('time'): - video_data['time'] = video_info.get('time') - - if video_data.get('url'): - extracted_data['videos'].append(video_data) - - # 提取话题信息 - # 话题可能在多个位置,尝试不同的字段名 - topic_list = note_info.get('topicList', []) or note_info.get('tagList', []) or note_info.get('hashtagList', []) - if topic_list: - for topic in topic_list: - topic_data = { - 'name': topic.get('name') or topic.get('title') or topic.get('tagName'), - 'id': topic.get('id') or topic.get('topicId') or topic.get('tagId'), - } - if topic_data.get('name'): - extracted_data['topics'].append(topic_data) - - # 如果描述中包含话题(#话题#格式),也提取出来 - desc = note_info.get('desc', '') - if desc: - # 使用正则表达式提取 #话题# 格式 - topic_pattern = r'#([^#]+)#' - matches = re.findall(topic_pattern, desc) - for match in matches: - # 避免重复添加 - if not any(t.get('name') == match for t in extracted_data['topics']): - extracted_data['topics'].append({'name': match}) - - return extracted_data - - except Exception as e: - print(f"提取笔记数据时出错:{e}") - import traceback - traceback.print_exc() - return None - - -def print_note_data(note_data): - """ - 格式化打印笔记数据 - - Args: - note_data: 笔记数据字典 - """ - if not note_data: - print("没有可显示的数据") - return - - print("=" * 60) - print("笔记信息") - print("=" * 60) - print(f"标题: {note_data.get('title')}") - print() - - print("描述:") - desc = note_data.get('desc', '') - if desc: - print(desc) - - print("图片列表:") - images = note_data.get('images', []) - print(f" 共 {len(images)} 张图片") - print(images) - - print("视频列表:") - videos = note_data.get('videos', []) - print(f" 共 {len(videos)} 个视频") - print(videos) - - print("话题列表:") - topics = note_data.get('topics', []) - print(f" 共 {len(topics)} 个话题") - print(topics) - - -if __name__ == '__main__': - # 解析HTML文件 - print("正在解析HTML文件...") - note_data = parse_html_file('1.html') - - if note_data: - # 保存为JSON文件 - with open('note_data.json', 'w', encoding='utf-8') as f: - json.dump(note_data, f, ensure_ascii=False, indent=2) - print("数据已保存到 note_data.json") - print() - - # 打印数据(尝试处理编码问题) - try: - print_note_data(note_data) - except Exception as e: - print(f"打印数据时出错(但数据已保存到JSON文件): {e}") - print("请查看 note_data.json 文件获取完整数据") - else: - print("解析失败,请检查HTML文件") diff --git a/pdd_gui.log b/pdd_gui.log deleted file mode 100644 index b258c07..0000000 --- a/pdd_gui.log +++ /dev/null @@ -1,24 +0,0 @@ -从Excel文件加载了 0 个任务 -已添加任务: C:/Users/27942/Desktop/image -配置已保存,开始运行任务... -开始任务:C:/Users/27942/Desktop/image -程序关闭,配置已保存 -任务已停止:C:/Users/27942/Desktop/image -任务队列已停止 -从Excel文件加载了 0 个任务 -已添加任务: C:/Users/27942/Desktop/image -配置已保存,开始运行任务... -开始任务:C:/Users/27942/Desktop/image -正在停止任务队列... -强制终止任务线程... -任务线程已强制终止 -任务队列已停止 -从Excel文件加载了 0 个任务 -已添加任务: C:/Users/27942/Desktop/image -配置已保存,开始运行任务... -开始任务:C:/Users/27942/Desktop/image -正在停止任务队列... -强制终止任务线程... -任务线程已强制终止 -任务队列已停止 -程序关闭,配置已保存 diff --git a/pdd_tasks.json b/pdd_tasks.json deleted file mode 100644 index b6ee1e5..0000000 --- a/pdd_tasks.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "user_id": "1050100241", - "file_path": "C:/Users/27942/Desktop/image", - "topics": "python-haha", - "interval": "5", - "creator_link": "https://www.xiaohongshu.com/explore/694d4fb6000000001e014195?xsec_token=ABdCdLrjMkmGbv623XZvEingO82fryJDFzzAXLgmYmark=&xsec_source=pc_user", - "count": "1", - "note": "", - "time_start": null, - "enabled": true, - "status": "运行中", - "last_run": "2026-01-16 11:18:35" - } -] \ No newline at end of file diff --git a/pdd_tasks.json.backup b/pdd_tasks.json.backup deleted file mode 100644 index b6ee1e5..0000000 --- a/pdd_tasks.json.backup +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "user_id": "1050100241", - "file_path": "C:/Users/27942/Desktop/image", - "topics": "python-haha", - "interval": "5", - "creator_link": "https://www.xiaohongshu.com/explore/694d4fb6000000001e014195?xsec_token=ABdCdLrjMkmGbv623XZvEingO82fryJDFzzAXLgmYmark=&xsec_source=pc_user", - "count": "1", - "note": "", - "time_start": null, - "enabled": true, - "status": "运行中", - "last_run": "2026-01-16 11:18:35" - } -] \ No newline at end of file diff --git a/test.py b/test.py deleted file mode 100644 index 2731aff..0000000 --- a/test.py +++ /dev/null @@ -1,218 +0,0 @@ -""" -Django settings for django_lanyu project. - -Generated by 'django-admin startproject' using Django 4.1. - -For more information on this file, see -https://docs.djangoproject.com/en/4.1/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/4.1/ref/settings/ -""" - -from pathlib import Path -import os - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure--+*v2+o0c*d7z3&+!z%&k^!b8w%1myflhkf4doh!12#$xo8)o=" - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = ['*'] - -# Application definition - -INSTALLED_APPS = [ - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", - "corsheaders", - "agent", # 经济人应用 - "user", - "operate", # 运营应用 - "risk", # 风控 - 'agent_manage' # 经纪人管理员 - -] - -MIDDLEWARE = [ - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - # "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", - "django_lanyu.middleware.JWTAuthenticationMiddleware", - 'corsheaders.middleware.CorsMiddleware', - 'django.middleware.common.CommonMiddleware', -] - -ROOT_URLCONF = "django_lanyu.urls" - -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - # "DIRS": [BASE_DIR / 'templates'] - # , - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - ], - }, - }, -] - -WSGI_APPLICATION = "django_lanyu.wsgi.application" - -REST_FRAMEWORK = { - 'DEFAULT_RENDERER_CLASSES': [ - 'rest_framework.renderers.JSONRenderer', - ], -} -# Database -# https://docs.djangoproject.com/en/4.1/ref/settings/#databases - -# DATABASES = { -# 'default': { -# 'ENGINE': 'django.db.backends.mysql', # 替换为你的数据库引擎,比如 'django.db.engine.mysql' -# 'NAME': 'lanyu', # 你的数据库名称 -# 'USER': 'root', # 数据库用户名 -# 'PASSWORD': '123456', # 数据库密码 -# 'HOST': '127.0.0.1', # 数据库主机地址 -# 'PORT': '3306', # 数据库端口 -# 'OPTIONS': { -# 'charset': 'utf8mb4', -# }, -# } -# } - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', # 替换为你的数据库引擎,比如 'django.db.engine.mysql' - 'NAME': 'lanyu', # 你的数据库名称 - 'USER': 'lanyu', # 数据库用户名 - 'PASSWORD': 'Ly123456.', # 数据库密码 - 'HOST': '127.0.0.1', # 数据库主机地址 - 'PORT': '3306', # 数据库端口 - 'OPTIONS': { - 'charset': 'utf8mb4', - }, - 'POOL_OPTIONS': { - 'POOL_SIZE': 5, # 连接池的初始大小 - 'MAX_OVERFLOW': 10, # 连接池允许的最大额外连接数 - 'RECYCLE': 3600, # 连接的最大存活时间(秒),超过该时间连接将被回收 - } - } -} - -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': 'unique-snowflake', - } -} - -# Password validation -# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", - }, -] - -# Internationalization -# https://docs.djangoproject.com/en/4.1/topics/i18n/ - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = "UTC" - -USE_I18N = True - -USE_TZ = True - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/4.1/howto/static-files/ - -STATIC_URL = "static/" - -# Default primary key field type -# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - -CORS_ALLOWED_ORIGINS = [ - "http://192.168.0.221", - "http://192.168.0.228", - 'http://your-frontend-url', - -] - -CORS_ALLOW_CREDENTIALS = True -CORS_ALLOW_ALL_ORIGINS = True - -CORS_ALLOW_METHODS = [ - 'DELETE', - 'GET', - 'OPTIONS', - 'PATCH', - 'POST', - 'PUT', -] - -CORS_ALLOW_HEADERS = [ - 'accept', - 'accept-encoding', - 'authorization', - 'content-type', - 'dnt', - 'origin', - 'user-agent', - 'x-csrftoken', - 'x-requested-with', -] - -APPEND_SLASH = True - -# 七牛云存储 -QINIU_ACCESS_KEY = 'CB8j8D9voknWUVendxZi4h-LERDfD0XU3IXtSeEu' -QINIU_SECRET_KEY = 'I3uaom2fiWMBNZQpOIQCdi0N7x1V13hNJBfSmO0C' # 待修改 -QINIU_BUCKET_NAME = 'shuju9' -QINIU_DOMAIN = 'lyamcn.com' -# 使用七牛云作为默认文件存储后端 -DEFAULT_FILE_STORAGE = 'qiniu_storage.storage.QiniuStorage' - -QINIU_STORAGE_OPTIONS = { - 'access_key': QINIU_ACCESS_KEY, - 'secret_key': QINIU_SECRET_KEY, - 'bucket_name': QINIU_BUCKET_NAME, - 'bucket_domain': QINIU_DOMAIN, -} - -MEDIA_ROOT = os.path.join(BASE_DIR, 'media') -MEDIA_URL = '/media/' - diff --git a/下载图片.py b/下载图片.py deleted file mode 100644 index f70f525..0000000 --- a/下载图片.py +++ /dev/null @@ -1,56 +0,0 @@ -import requests -import os -from urllib.parse import urlparse - -def download_image(url, save_path=None): - """ - 下载图片文件 - - Args: - url: 图片URL - save_path: 保存路径,如果为None则使用URL中的文件名 - """ - # 设置请求头 - headers = { - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', - 'Cache-Control': 'no-cache', - 'DNT': '1', - 'Pragma': 'no-cache', - 'Proxy-Connection': 'keep-alive', - 'Upgrade-Insecure-Requests': '1', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0' - } - - try: - # 发送请求,verify=False 相当于 curl 的 --insecure - response = requests.get(url, headers=headers, verify=False, timeout=30) - response.raise_for_status() # 检查HTTP错误 - - # 如果没有指定保存路径,从URL中提取文件名 - if save_path is None: - parsed_url = urlparse(url) - filename = os.path.basename(parsed_url.path) - # 如果URL中没有明确的文件名,使用默认名称 - if not filename or '.' not in filename: - filename = 'downloaded_image.webp' - save_path = filename - - # 保存文件 - with open(save_path, 'wb') as f: - f.write(response.content) - - print(f"图片下载成功!保存路径: {save_path}") - print(f"文件大小: {len(response.content)} 字节") - return save_path - - except requests.exceptions.RequestException as e: - print(f"下载失败: {e}") - return None - -if __name__ == "__main__": - # 图片URL - image_url = 'http://sns-webpic-qc.xhscdn.com/202601141654/fea93045d0792c459a13310ffd9e0cfb/notes_pre_post/1040g3k031r0eua5ng0104a0mknaoroirqoi9ju8!nd_dft_wlteh_webp_3' - - # 下载图片 - download_image(image_url) \ No newline at end of file diff --git a/下载文件.py b/下载文件.py deleted file mode 100644 index fe59528..0000000 --- a/下载文件.py +++ /dev/null @@ -1,4 +0,0 @@ -from DrissionPage import SessionPage - -page = SessionPage() -page.download('https://sns-video-hw.xhscdn.com/stream/110/258/01e6cd08be6e36ad010370019190eceaac_258.mp4') diff --git a/自动化_gui.py b/自动化_gui.py deleted file mode 100644 index b3fc85f..0000000 --- a/自动化_gui.py +++ /dev/null @@ -1,1109 +0,0 @@ -import json -import os -import sys -from datetime import datetime - -from PyQt5.QtCore import Qt, QDateTime, QThread, pyqtSignal, QObject, QTimer -from PyQt5.QtWidgets import ( - QApplication, - QCheckBox, - QDateTimeEdit, - QDialog, - QDialogButtonBox, - QFormLayout, - QGroupBox, - QHBoxLayout, - QLabel, - QLineEdit, - QMainWindow, - QMessageBox, - QPushButton, - QPlainTextEdit, - QTableWidget, - QTableWidgetItem, - QFrame, - QSplitter, - QVBoxLayout, - QWidget, - QFileDialog, -) - -from 自动化_wrapper import PddRunner - - -BASE_DIR = os.path.dirname(__file__) -TASKS_FILE = os.path.join(BASE_DIR, "pdd_tasks.json") -LOG_FILE = os.path.join(BASE_DIR, "pdd_gui.log") -DT_FORMAT = "yyyy-MM-dd HH:mm:ss" -EXCEL_HEADERS = ["用户ID", "文件路径", "话题(以中文“-”分隔)", "定时发布", "间隔时间", "达人链接", "数量", "情况"] -EXCEL_HEADER_MAP = { - "用户ID": "user_id", - "user_id": "user_id", - "文件路径": "file_path", - "file_path": "file_path", - "话题(以中文“-”分隔)": "topics", - "话题": "topics", - "topics": "topics", - "定时发布": "time_start", - "定时": "time_start", - "time_start": "time_start", - "间隔时间": "interval", - "interval": "interval", - "达人链接": "creator_link", - "creator_link": "creator_link", - "数量": "count", - "count": "count", - "情况": "note", - "note": "note", - "启用": "enabled", - "enabled": "enabled", -} - - -def _normalize_task(task): - if not isinstance(task, dict): - return None - file_path = str(task.get("file_path", "")).strip() - user_id = str(task.get("user_id", "")).strip() - normalized = { - "user_id": user_id, - "file_path": file_path, - "topics": str(task.get("topics", "")).strip(), - "interval": task.get("interval"), - "creator_link": str(task.get("creator_link", "")).strip(), - "count": task.get("count"), - "note": str(task.get("note", "")).strip(), - "time_start": task.get("time_start"), - "enabled": bool(task.get("enabled", True)), - "status": task.get("status", ""), - "last_run": task.get("last_run", ""), - } - if not normalized["file_path"] or not normalized["user_id"]: - return None - return normalized - - -class EmittingStream(QObject): - text_written = pyqtSignal(str) - - def __init__(self, original): - super().__init__() - self._original = original - - def write(self, text): - if text: - self.text_written.emit(text) - try: - self._original.write(text) - except Exception: - pass - - def flush(self): - try: - self._original.flush() - except Exception: - pass - - -class TaskDialog(QDialog): - def __init__(self, parent=None, task=None): - super().__init__(parent) - self.setWindowTitle("任务配置") - self._build_ui(task or {}) - - def _build_ui(self, task): - layout = QVBoxLayout() - form = QFormLayout() - - self.user_id_edit = QLineEdit(task.get("user_id", "")) - self.file_path_edit = QLineEdit(task.get("file_path", "")) - self.file_path_btn = QPushButton("选择文件夹") - self.file_path_btn.clicked.connect(self._choose_folder) - self.file_select_btn = QPushButton("选择文件") - self.file_select_btn.clicked.connect(self._choose_file) - file_path_layout = QHBoxLayout() - file_path_layout.addWidget(self.file_path_edit) - file_path_layout.addWidget(self.file_path_btn) - file_path_layout.addWidget(self.file_select_btn) - file_path_container = QWidget() - file_path_container.setLayout(file_path_layout) - - self.topics_edit = QLineEdit(task.get("topics", "")) - self.interval_edit = QLineEdit(str(task.get("interval", "") or "")) - self.interval_edit.setPlaceholderText("例如:5(分钟)/ 30秒 / 1小时") - self.creator_link_edit = QLineEdit(task.get("creator_link", "")) - self.count_edit = QLineEdit(str(task.get("count", "") or "")) - self.note_edit = QLineEdit(task.get("note", "")) - self.enabled_checkbox = QCheckBox("启用该任务") - self.enabled_checkbox.setChecked(task.get("enabled", True)) - - self.time_checkbox = QCheckBox("启用定时") - time_start = task.get("time_start") - self.time_edit = QDateTimeEdit() - self.time_edit.setDisplayFormat(DT_FORMAT) - self.time_edit.setCalendarPopup(True) - self.time_edit.setDateTime(QDateTime.currentDateTime()) - if time_start: - self.time_checkbox.setChecked(True) - self.time_edit.setDateTime(QDateTime.fromString(time_start, DT_FORMAT)) - else: - self.time_checkbox.setChecked(False) - self.time_edit.setEnabled(False) - - self.time_checkbox.stateChanged.connect(self._toggle_time) - - form.addRow("用户ID", self.user_id_edit) - form.addRow("文件路径", file_path_container) - form.addRow("话题(以中文“-”分隔)", self.topics_edit) - form.addRow("间隔时间", self.interval_edit) - form.addRow("达人链接", self.creator_link_edit) - form.addRow("数量", self.count_edit) - form.addRow("情况", self.note_edit) - form.addRow(self.enabled_checkbox) - form.addRow(self.time_checkbox, self.time_edit) - - hint = QLabel("提示:文件名作为标题;话题会自动添加 #;定时为空时会立即发布;存在子文件夹时按文件夹间隔发布。") - hint.setStyleSheet("color: #666;") - - layout.addLayout(form) - layout.addWidget(hint) - - buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - buttons.accepted.connect(self.accept) - buttons.rejected.connect(self.reject) - layout.addWidget(buttons) - - self.setLayout(layout) - - def _toggle_time(self, state): - self.time_edit.setEnabled(state == Qt.Checked) - - def _choose_folder(self): - path = QFileDialog.getExistingDirectory(self, "选择文件夹", BASE_DIR) - if path: - self.file_path_edit.setText(path) - - def _choose_file(self): - path, _ = QFileDialog.getOpenFileName( - self, - "选择文件", - BASE_DIR, - "媒体文件 (*.mp4 *.mov *.avi *.mkv *.wmv *.webm *.m4v *.jpg *.jpeg *.png *.gif *.bmp *.webp *.svg);;所有文件 (*.*)", - ) - if path: - self.file_path_edit.setText(path) - - def showEvent(self, event): - super().showEvent(event) - self._center_dialog() - - def _center_dialog(self): - if self.parent(): - parent_geometry = self.parent().frameGeometry() - center_point = parent_geometry.center() - else: - screen = QApplication.primaryScreen() - if screen: - center_point = screen.availableGeometry().center() - else: - return - frame = self.frameGeometry() - frame.moveCenter(center_point) - self.move(frame.topLeft()) - - def get_task(self): - user_id = self.user_id_edit.text().strip() - file_path = self.file_path_edit.text().strip() - if not file_path or not user_id: - QMessageBox.warning(self, "提示", "文件路径 和 用户ID 不能为空。") - return None - - time_start = None - if self.time_checkbox.isChecked(): - time_start = self.time_edit.dateTime().toString(DT_FORMAT) - - return { - "user_id": user_id, - "file_path": file_path, - "topics": self.topics_edit.text().strip(), - "interval": self.interval_edit.text().strip(), - "creator_link": self.creator_link_edit.text().strip(), - "count": self.count_edit.text().strip(), - "note": self.note_edit.text().strip(), - "time_start": time_start, - "enabled": self.enabled_checkbox.isChecked(), - "status": "", - "last_run": "", - } - - -class TaskRunner(QThread): - task_started = pyqtSignal(int) - task_finished = pyqtSignal(int, bool, str) - log_message = pyqtSignal(str) - run_finished = pyqtSignal() - - def __init__(self, tasks, indices): - super().__init__() - self._tasks = tasks - self._indices = indices - self._stop_requested = False - self._current_runner = None - - def request_stop(self): - """请求停止任务""" - self._stop_requested = True - if self._current_runner: - try: - self._current_runner.request_stop() - except Exception: - pass - - def run(self): - runner = PddRunner() - self._current_runner = runner - try: - for idx in self._indices: - if self._stop_requested: - self.log_message.emit("任务已被用户停止\n") - break - task = self._tasks[idx] - self.task_started.emit(idx) - try: - self.log_message.emit(f"开始任务:{task.get('file_path')}\n") - if self._stop_requested: - self.log_message.emit("任务在开始前被停止\n") - break - - success, message = runner.run( - user_id=task["user_id"], - file_path=task["file_path"], - topics=task.get("topics", ""), - time_start=task.get("time_start"), - interval=task.get("interval"), - creator_link=task.get("creator_link"), - count=task.get("count"), - stop_callback=lambda: self._stop_requested, - ) - - if self._stop_requested: - self.task_finished.emit(idx, False, "已停止") - self.log_message.emit(f"任务已停止:{task.get('file_path')}\n") - break - - self.task_finished.emit(idx, success, message) - if success: - self.log_message.emit(f"任务完成:{task.get('file_path')}\n") - else: - self.log_message.emit(f"任务失败:{task.get('file_path')},原因:{message}\n") - except Exception as exc: - if self._stop_requested: - self.task_finished.emit(idx, False, "已停止") - self.log_message.emit(f"任务已停止:{task.get('file_path')}\n") - break - self.task_finished.emit(idx, False, f"失败:{exc}") - self.log_message.emit(f"任务失败:{task.get('file_path')},原因:{exc}\n") - finally: - self._current_runner = None - self.run_finished.emit() - - -class MainWindow(QMainWindow): - def __init__(self): - super().__init__() - self.setWindowTitle("自动化任务GUI") - self.tasks = [] - self.runner = None - self._stop_after_current = False - self._auto_save_enabled = True - self._last_save_time = None - self._build_ui() - self._load_tasks() - self._refresh_table() - self._setup_streams() - self._apply_style() - # 启动定时自动保存 - self.auto_save_timer = QTimer() - self.auto_save_timer.timeout.connect(self._auto_save) - self.auto_save_timer.start(30000) # 每30秒自动保存一次 - - def _build_ui(self): - central = QWidget() - layout = QVBoxLayout() - layout.setContentsMargins(12, 12, 12, 12) - layout.setSpacing(10) - - self.table = QTableWidget(0, 11) - self.table.setHorizontalHeaderLabels( - ["启用", "用户ID", "文件路径", "话题", "定时发布", "间隔时间", "达人链接", "数量", "情况", "状态", "上次运行"] - ) - self.table.setSelectionBehavior(QTableWidget.SelectRows) - self.table.setSelectionMode(QTableWidget.ExtendedSelection) - self.table.horizontalHeader().setStretchLastSection(True) - self.table.verticalHeader().setVisible(False) - self.table.setAlternatingRowColors(True) - self.table.setFrameShape(QFrame.NoFrame) - self.table.setStyleSheet("QTableWidget { background: #ffffff; }") - - btn_layout = QHBoxLayout() - btn_layout.setSpacing(6) - self.add_btn = QPushButton("新增") - self.edit_btn = QPushButton("编辑") - self.copy_btn = QPushButton("复制") - self.del_btn = QPushButton("删除") - self.up_btn = QPushButton("上移") - self.down_btn = QPushButton("下移") - self.run_btn = QPushButton("运行选中") - self.run_all_btn = QPushButton("运行启用") - self.stop_btn = QPushButton("停止队列") - self.save_btn = QPushButton("保存配置") - self.reload_btn = QPushButton("重新加载") - self.import_btn = QPushButton("导入") - self.export_btn = QPushButton("导出") - self.import_excel_btn = QPushButton("导入Excel") - self.download_template_btn = QPushButton("下载模板") - self.clear_log_btn = QPushButton("清空日志") - - self.add_btn.clicked.connect(self._add_task) - self.edit_btn.clicked.connect(self._edit_task) - self.copy_btn.clicked.connect(self._copy_task) - self.del_btn.clicked.connect(self._delete_task) - self.up_btn.clicked.connect(self._move_up) - self.down_btn.clicked.connect(self._move_down) - self.run_btn.clicked.connect(self._run_selected) - self.run_all_btn.clicked.connect(self._run_enabled) - self.stop_btn.clicked.connect(self._stop_queue) - self.save_btn.clicked.connect(self._save_tasks) - self.reload_btn.clicked.connect(self._reload_tasks) - self.import_btn.clicked.connect(self._import_tasks) - self.export_btn.clicked.connect(self._export_tasks) - self.import_excel_btn.clicked.connect(self._import_tasks_excel) - self.download_template_btn.clicked.connect(self._download_excel_template) - self.clear_log_btn.clicked.connect(self._clear_log) - - manage_group = QGroupBox("任务管理") - manage_layout = QHBoxLayout() - manage_layout.setSpacing(6) - for btn in [ - self.add_btn, - self.edit_btn, - self.copy_btn, - self.del_btn, - self.up_btn, - self.down_btn, - ]: - manage_layout.addWidget(btn) - manage_group.setLayout(manage_layout) - - run_group = QGroupBox("运行控制") - run_layout = QHBoxLayout() - run_layout.setSpacing(6) - for btn in [self.run_btn, self.run_all_btn, self.stop_btn]: - run_layout.addWidget(btn) - run_group.setLayout(run_layout) - - config_group = QGroupBox("配置") - config_layout = QHBoxLayout() - config_layout.setSpacing(6) - for btn in [ - self.save_btn, - self.reload_btn, - self.import_btn, - self.export_btn, - self.import_excel_btn, - self.download_template_btn, - self.clear_log_btn, - ]: - config_layout.addWidget(btn) - config_group.setLayout(config_layout) - - self.status_label = QLabel("就绪(单线程顺序执行)") - self.status_label.setObjectName("statusLabel") - - self.log_view = QPlainTextEdit() - self.log_view.setReadOnly(True) - self.log_view.setMaximumBlockCount(2000) - - table_group = QGroupBox("任务列表") - table_layout = QVBoxLayout() - table_layout.setContentsMargins(8, 8, 8, 8) - table_layout.addWidget(self.table) - table_group.setLayout(table_layout) - - log_group = QGroupBox("运行日志") - log_layout = QVBoxLayout() - log_layout.setContentsMargins(8, 8, 8, 8) - log_layout.addWidget(self.log_view) - log_group.setLayout(log_layout) - - splitter = QSplitter(Qt.Vertical) - splitter.addWidget(table_group) - splitter.addWidget(log_group) - splitter.setStretchFactor(0, 2) - splitter.setStretchFactor(1, 1) - - layout.addWidget(splitter) - layout.addWidget(manage_group) - layout.addWidget(run_group) - layout.addWidget(config_group) - layout.addWidget(self.status_label) - central.setLayout(layout) - self.setCentralWidget(central) - - def _refresh_table(self): - self.table.setRowCount(0) - for task in self.tasks: - row = self.table.rowCount() - self.table.insertRow(row) - enabled_item = QTableWidgetItem() - enabled_item.setFlags(enabled_item.flags() | Qt.ItemIsUserCheckable) - enabled_item.setCheckState(Qt.Checked if task.get("enabled", True) else Qt.Unchecked) - self.table.setItem(row, 0, enabled_item) - self.table.setItem(row, 1, QTableWidgetItem(task.get("user_id", ""))) - self.table.setItem(row, 2, QTableWidgetItem(task.get("file_path", ""))) - self.table.setItem(row, 3, QTableWidgetItem(task.get("topics", ""))) - self.table.setItem(row, 4, QTableWidgetItem(task.get("time_start") or "")) - self.table.setItem(row, 5, QTableWidgetItem(str(task.get("interval") or ""))) - self.table.setItem(row, 6, QTableWidgetItem(task.get("creator_link", ""))) - self.table.setItem(row, 7, QTableWidgetItem(str(task.get("count") or ""))) - self.table.setItem(row, 8, QTableWidgetItem(task.get("note", ""))) - self.table.setItem(row, 9, QTableWidgetItem(task.get("status", ""))) - self.table.setItem(row, 10, QTableWidgetItem(task.get("last_run", ""))) - - def _add_task(self): - dialog = TaskDialog(self) - if dialog.exec_() == QDialog.Accepted: - task = dialog.get_task() - if task: - self.tasks.append(task) - self._refresh_table() - self._save_tasks(silent=True) - self._append_log(f"已添加任务: {task.get('file_path')}\n") - - def _edit_task(self): - row = self._current_single_row() - if row is None: - return - dialog = TaskDialog(self, self._task_from_row(row)) - if dialog.exec_() == QDialog.Accepted: - task = dialog.get_task() - if task: - task["enabled"] = self._row_enabled(row) - self.tasks[row] = task - self._refresh_table() - self._save_tasks(silent=True) - self._append_log(f"已编辑任务: {task.get('file_path')}\n") - - def _delete_task(self): - rows = self._current_rows() - if not rows: - QMessageBox.information(self, "提示", "请先选中一行。") - return - deleted_count = len(rows) - for row in sorted(rows, reverse=True): - del self.tasks[row] - self._refresh_table() - self._save_tasks(silent=True) - self._append_log(f"已删除 {deleted_count} 个任务\n") - - def _run_selected(self): - rows = self._current_rows() - if not rows: - QMessageBox.information(self, "提示", "请先选中一行。") - return - self._start_runner(rows) - - def _run_enabled(self): - rows = [i for i, _ in enumerate(self.tasks) if self._row_enabled(i)] - if not rows: - QMessageBox.information(self, "提示", "没有启用的任务。") - return - self._start_runner(rows) - - def _stop_queue(self): - if self.runner: - self._stop_after_current = True - self.runner.request_stop() - self._append_log("正在停止任务队列...\n") - self.status_label.setText("正在停止...") - - # 等待线程响应停止请求,最多等待2秒 - if not self.runner.wait(2000): - # 如果2秒内没有响应,强制终止线程 - self._append_log("强制终止任务线程...\n") - self.runner.terminate() - self.runner.wait(1000) - if self.runner.isRunning(): - self._append_log("警告:线程未能正常终止\n") - else: - self._append_log("任务线程已强制终止\n") - self._on_run_finished() - - def _set_running(self, running, text): - self.status_label.setText(text) - for btn in [ - self.add_btn, - self.edit_btn, - self.copy_btn, - self.del_btn, - self.up_btn, - self.down_btn, - self.run_btn, - self.run_all_btn, - self.save_btn, - self.reload_btn, - self.import_btn, - self.export_btn, - self.import_excel_btn, - self.download_template_btn, - ]: - btn.setEnabled(not running) - self.stop_btn.setEnabled(running) - self.clear_log_btn.setEnabled(not running) - self.table.setEnabled(not running) - if not running: - self.status_label.setText("就绪(单线程顺序执行)") - - def _current_single_row(self): - rows = self._current_rows() - if len(rows) != 1: - QMessageBox.information(self, "提示", "请选择一行进行编辑。") - return None - return rows[0] - - def _current_rows(self): - indexes = self.table.selectionModel().selectedRows() - return sorted({i.row() for i in indexes}) - - def _row_enabled(self, row): - item = self.table.item(row, 0) - if item is None: - return True - return item.checkState() == Qt.Checked - - def _task_from_row(self, row): - task = self.tasks[row].copy() - task["enabled"] = self._row_enabled(row) - return task - - def _load_tasks(self): - default_excel = os.path.join(BASE_DIR, "配置表(1).xlsx") - if os.path.exists(default_excel): - loaded = self._load_tasks_from_excel(default_excel, show_message=False) - if loaded: - self._append_log(f"从Excel文件加载了 {len(self.tasks)} 个任务\n") - return - - # 尝试从备份文件恢复 - backup_file = f"{TASKS_FILE}.backup" - if not os.path.exists(TASKS_FILE) and os.path.exists(backup_file): - try: - import shutil - shutil.copy2(backup_file, TASKS_FILE) - self._append_log("从备份文件恢复配置\n") - except Exception as e: - self._append_log(f"从备份恢复失败: {e}\n") - - if not os.path.exists(TASKS_FILE): - self.tasks = [] - return - - try: - with open(TASKS_FILE, "r", encoding="utf-8") as f: - data = json.load(f) - if isinstance(data, dict) and isinstance(data.get("tasks"), list): - data = data["tasks"] - if not isinstance(data, list): - self.tasks = [] - return - tasks = [] - for item in data: - normalized = _normalize_task(item) - if normalized: - tasks.append(normalized) - self.tasks = tasks - self._append_log(f"从JSON文件加载了 {len(self.tasks)} 个任务\n") - except json.JSONDecodeError as e: - self._append_log(f"JSON文件格式错误: {e},尝试从备份恢复\n") - # 尝试从备份恢复 - if os.path.exists(backup_file): - try: - import shutil - shutil.copy2(backup_file, TASKS_FILE) - self._load_tasks() # 递归重试 - except Exception: - self.tasks = [] - else: - self.tasks = [] - except (OSError, Exception) as e: - self._append_log(f"加载配置失败: {e}\n") - self.tasks = [] - - def _save_tasks(self, silent=False, backup=True): - self._sync_enabled_flags() - try: - # 创建备份 - if backup and os.path.exists(TASKS_FILE): - try: - backup_file = f"{TASKS_FILE}.backup" - import shutil - shutil.copy2(TASKS_FILE, backup_file) - except Exception as e: - self._append_log(f"创建备份失败: {e}\n") - - # 保存到临时文件,然后原子性替换 - temp_file = f"{TASKS_FILE}.tmp" - with open(temp_file, "w", encoding="utf-8") as f: - json.dump(self.tasks, f, ensure_ascii=False, indent=2) - - # 原子性替换 - if os.path.exists(TASKS_FILE): - os.replace(temp_file, TASKS_FILE) - else: - os.rename(temp_file, TASKS_FILE) - - self._last_save_time = datetime.now() - if not silent: - QMessageBox.information(self, "提示", "配置已保存。") - return True - except OSError as exc: - if os.path.exists(temp_file): - try: - os.remove(temp_file) - except: - pass - if not silent: - QMessageBox.critical(self, "错误", f"保存失败:{exc}") - self._append_log(f"保存配置失败: {exc}\n") - return False - except Exception as exc: - if not silent: - QMessageBox.critical(self, "错误", f"保存失败:{exc}") - self._append_log(f"保存配置失败: {exc}\n") - return False - - def _auto_save(self): - """自动保存配置""" - if self._auto_save_enabled and self.tasks: - try: - self._save_tasks(silent=True, backup=False) - except Exception as e: - self._append_log(f"自动保存失败: {e}\n") - - def _reload_tasks(self): - self._load_tasks() - self._refresh_table() - - def _import_tasks(self): - path, _ = QFileDialog.getOpenFileName(self, "导入配置", BASE_DIR, "JSON 文件 (*.json)") - if not path: - return - try: - with open(path, "r", encoding="utf-8") as f: - data = json.load(f) - if isinstance(data, dict) and isinstance(data.get("tasks"), list): - data = data["tasks"] - if not isinstance(data, list): - raise ValueError("文件结构不正确") - tasks = [] - for item in data: - normalized = _normalize_task(item) - if normalized: - tasks.append(normalized) - self.tasks = tasks - self._refresh_table() - self._save_tasks(silent=True) - QMessageBox.information(self, "提示", "导入完成。") - except Exception as exc: - QMessageBox.critical(self, "错误", f"导入失败:{exc}") - - def _export_tasks(self): - path, _ = QFileDialog.getSaveFileName(self, "导出配置", BASE_DIR, "JSON 文件 (*.json)") - if not path: - return - self._sync_enabled_flags() - try: - with open(path, "w", encoding="utf-8") as f: - json.dump(self.tasks, f, ensure_ascii=False, indent=2) - QMessageBox.information(self, "提示", "导出完成。") - except OSError as exc: - QMessageBox.critical(self, "错误", f"导出失败:{exc}") - - def _get_openpyxl(self): - try: - import openpyxl # type: ignore - return openpyxl - except Exception: - QMessageBox.warning(self, "提示", "未检测到 openpyxl,请先安装:pip install openpyxl") - return None - - def _parse_enabled(self, value): - if value is None or value == "": - return True - if isinstance(value, bool): - return value - if isinstance(value, (int, float)): - return value != 0 - text = str(value).strip().lower() - return text in {"1", "true", "yes", "y", "是", "启用"} - - def _normalize_time_cell(self, value): - if value is None or value == "": - return None - if isinstance(value, datetime): - return value.strftime("%Y-%m-%d %H:%M:%S") - text = str(value).strip() - return text or None - - def _import_tasks_excel(self): - path, _ = QFileDialog.getOpenFileName(self, "导入Excel配置", BASE_DIR, "Excel 文件 (*.xlsx)") - if not path: - return - self._load_tasks_from_excel(path, show_message=True) - - def _load_tasks_from_excel(self, path, show_message=False): - openpyxl = self._get_openpyxl() - if not openpyxl: - return False - try: - workbook = openpyxl.load_workbook(path, data_only=True) - sheet = workbook.active - rows = list(sheet.iter_rows(values_only=True)) - if not rows: - raise ValueError("Excel 内容为空") - headers = [str(cell).strip() if cell is not None else "" for cell in rows[0]] - if not any(headers): - raise ValueError("未识别到表头") - column_map = {} - for idx, header in enumerate(headers): - key = EXCEL_HEADER_MAP.get(header) - if key: - column_map[idx] = key - if "file_path" not in column_map.values() or "user_id" not in column_map.values(): - raise ValueError("表头必须包含 文件路径 和 用户ID") - tasks = [] - for row in rows[1:]: - if not row: - continue - item = {} - for idx, key in column_map.items(): - if idx < len(row): - item[key] = row[idx] - file_path = str(item.get("file_path") or "").strip() - user_id = str(item.get("user_id") or "").strip() - if not file_path or not user_id: - continue - time_start = self._normalize_time_cell(item.get("time_start")) - enabled = self._parse_enabled(item.get("enabled")) - task = { - "user_id": user_id, - "file_path": file_path, - "topics": str(item.get("topics") or "").strip(), - "interval": item.get("interval"), - "creator_link": str(item.get("creator_link") or "").strip(), - "count": item.get("count"), - "note": str(item.get("note") or "").strip(), - "time_start": time_start, - "enabled": enabled, - "status": "", - "last_run": "", - } - normalized = _normalize_task(task) - if normalized: - normalized["enabled"] = enabled - tasks.append(normalized) - self.tasks = tasks - self._refresh_table() - self._save_tasks(silent=True) - if show_message: - QMessageBox.information(self, "提示", f"导入完成,共 {len(tasks)} 条任务。") - return True - except Exception as exc: - if show_message: - QMessageBox.critical(self, "错误", f"导入失败:{exc}") - return False - - def _download_excel_template(self): - path, _ = QFileDialog.getSaveFileName(self, "下载Excel模板", BASE_DIR, "Excel 文件 (*.xlsx)") - if not path: - return - if not path.lower().endswith(".xlsx"): - path = f"{path}.xlsx" - openpyxl = self._get_openpyxl() - if not openpyxl: - return - try: - workbook = openpyxl.Workbook() - sheet = workbook.active - sheet.title = "任务模板" - sheet.append(EXCEL_HEADERS) - workbook.save(path) - QMessageBox.information(self, "提示", "模板已保存。") - except Exception as exc: - QMessageBox.critical(self, "错误", f"模板保存失败:{exc}") - - def _copy_task(self): - row = self._current_single_row() - if row is None: - return - task = self._task_from_row(row) - task["note"] = f"{task.get('note', '')} (副本)".strip() - task["status"] = "" - task["last_run"] = "" - self.tasks.insert(row + 1, task) - self._refresh_table() - self._save_tasks(silent=True) - - def _move_up(self): - row = self._current_single_row() - if row is None or row == 0: - return - self.tasks[row - 1], self.tasks[row] = self.tasks[row], self.tasks[row - 1] - self._refresh_table() - self.table.selectRow(row - 1) - self._save_tasks(silent=True) - - def _move_down(self): - row = self._current_single_row() - if row is None or row >= len(self.tasks) - 1: - return - self.tasks[row + 1], self.tasks[row] = self.tasks[row], self.tasks[row + 1] - self._refresh_table() - self.table.selectRow(row + 1) - self._save_tasks(silent=True) - - def _start_runner(self, rows): - if self.runner: - QMessageBox.information(self, "提示", "已有任务在运行中。") - return - - invalid_rows = self._validate_rows(rows) - if invalid_rows: - return - - # 启动前先保存配置 - try: - self._sync_enabled_flags() - if not self._save_tasks(silent=True): - reply = QMessageBox.question( - self, - "保存失败", - "配置保存失败,是否仍要继续运行任务?", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No - ) - if reply == QMessageBox.No: - return - self._append_log("配置已保存,开始运行任务...\n") - except Exception as e: - self._append_log(f"保存配置时出错: {e}\n") - reply = QMessageBox.question( - self, - "保存失败", - f"配置保存失败:{e}\n是否仍要继续运行任务?", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No - ) - if reply == QMessageBox.No: - return - - self._stop_after_current = False - self.runner = TaskRunner(self.tasks, rows) - self.runner.task_started.connect(self._on_task_started) - self.runner.task_finished.connect(self._on_task_finished) - self.runner.log_message.connect(self._append_log) - self.runner.run_finished.connect(self._on_run_finished) - self._set_running(True, "运行中...") - self.runner.start() - - def _validate_rows(self, rows): - invalid = [] - for row in rows: - if row >= len(self.tasks): - continue - task = self.tasks[row] - file_path = str(task.get("file_path", "")).strip() - user_id = str(task.get("user_id", "")).strip() - if not file_path or not user_id or not os.path.exists(file_path): - invalid.append((row + 1, file_path, user_id)) - if not invalid: - return [] - preview = "\n".join( - f"第{row}行:user_id={user_id or '-'},路径={path or '-'}" - for row, path, user_id in invalid[:5] - ) - if len(invalid) > 5: - preview += f"\n... 还有 {len(invalid) - 5} 行" - QMessageBox.warning( - self, - "任务校验失败", - f"选中任务存在用户ID为空或路径不存在的情况:\n{preview}\n请先修正后再运行。", - ) - return invalid - - def _on_task_started(self, index): - self.tasks[index]["status"] = "运行中" - self.tasks[index]["last_run"] = QDateTime.currentDateTime().toString(DT_FORMAT) - self._refresh_table() - - def _on_task_finished(self, index, success, message): - self.tasks[index]["status"] = "成功" if success else message - self._refresh_table() - - def _on_run_finished(self): - runner = self.runner - if runner: - runner.deleteLater() - self.runner = None - - self._set_running(False, "就绪") - - # 确保保存配置 - try: - self._save_tasks(silent=True) - except Exception as e: - self._append_log(f"保存配置时出错: {e}\n") - - if self._stop_after_current: - self._append_log("任务队列已停止\n") - self._stop_after_current = False - # 不显示消息框,只在日志中记录 - else: - self._append_log("任务队列执行完成\n") - QMessageBox.information(self, "完成", "任务队列执行完成。") - - def _append_log(self, text): - if not text: - return - self.log_view.appendPlainText(text.rstrip("\n")) - try: - with open(LOG_FILE, "a", encoding="utf-8") as f: - f.write(text) - except OSError: - pass - - def _clear_log(self): - self.log_view.clear() - try: - with open(LOG_FILE, "w", encoding="utf-8") as f: - f.write("") - except OSError: - pass - - def _sync_enabled_flags(self): - for row in range(self.table.rowCount()): - if row < len(self.tasks): - self.tasks[row]["enabled"] = self._row_enabled(row) - - def _setup_streams(self): - self._stdout = EmittingStream(sys.__stdout__) - self._stderr = EmittingStream(sys.__stderr__) - self._stdout.text_written.connect(self._append_log) - self._stderr.text_written.connect(self._append_log) - sys.stdout = self._stdout - sys.stderr = self._stderr - - def closeEvent(self, event): - if self.runner: - reply = QMessageBox.question( - self, - "确认关闭", - "任务运行中,确定要关闭吗?\n关闭后正在运行的任务将被中断。", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No - ) - if reply == QMessageBox.Yes: - self._stop_after_current = True - if self.runner: - self.runner.request_stop() - # 等待任务结束 - import time - for _ in range(10): - if not self.runner: - break - time.sleep(0.5) - else: - event.ignore() - return - - # 保存配置 - try: - self._save_tasks(silent=True) - self._append_log("程序关闭,配置已保存\n") - except Exception as e: - self._append_log(f"关闭时保存配置失败: {e}\n") - - # 停止自动保存定时器 - if hasattr(self, 'auto_save_timer'): - self.auto_save_timer.stop() - - event.accept() - - def _apply_style(self): - self.setStyleSheet( - """ - QWidget { - font-family: "Microsoft YaHei"; - font-size: 12px; - } - QGroupBox { - font-weight: 600; - border: 1px solid #e3e6ea; - border-radius: 8px; - margin-top: 10px; - background: #fafbfc; - } - QGroupBox::title { - subcontrol-origin: margin; - left: 10px; - padding: 0 6px; - } - QPushButton { - padding: 6px 12px; - border: 1px solid #d6d9dd; - border-radius: 6px; - background: #ffffff; - } - QPushButton:hover { - background: #f3f6f9; - } - QPushButton:disabled { - color: #9aa1a9; - background: #f6f7f9; - } - QTableWidget { - gridline-color: #eef1f4; - selection-background-color: #e6f0ff; - selection-color: #1f2a44; - } - QHeaderView::section { - background: #f3f5f8; - border: none; - padding: 6px; - font-weight: 600; - } - QPlainTextEdit { - border: 1px solid #e3e6ea; - border-radius: 6px; - background: #ffffff; - } - QLabel#statusLabel { - padding: 6px 8px; - border: 1px solid #e3e6ea; - border-radius: 6px; - background: #ffffff; - } - """ - ) - - -def main(): - app = QApplication(sys.argv) - window = MainWindow() - window.resize(1100, 700) - window.show() - sys.exit(app.exec_()) - - -if __name__ == "__main__": - main() diff --git a/自动化_gui.spec b/自动化_gui.spec deleted file mode 100644 index 6b5550c..0000000 --- a/自动化_gui.spec +++ /dev/null @@ -1,39 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- - - -a = Analysis( - ['自动化_gui.py'], - pathex=[], - binaries=[], - datas=[], - hiddenimports=['自动化_wrapper'], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - noarchive=False, - optimize=0, -) -pyz = PYZ(a.pure) - -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.datas, - [], - name='自动化_gui', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=False, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, - icon=['0.webp'], -) diff --git a/自动化_wrapper.py b/自动化_wrapper.py deleted file mode 100644 index d2d91aa..0000000 --- a/自动化_wrapper.py +++ /dev/null @@ -1,102 +0,0 @@ -import time -from datetime import datetime - -from 自动化 import Pdd - - -class PddRunner: - def __init__(self, retries=1, retry_delay=5): - self.retries = max(0, int(retries)) - self.retry_delay = max(0, int(retry_delay)) - self._stop_requested = False - - def request_stop(self): - """请求停止任务""" - self._stop_requested = True - - def _normalize_time_start(self, time_start): - if not time_start: - return None - if isinstance(time_start, datetime): - return time_start.strftime("%Y-%m-%d %H:%M:%S") - return str(time_start).strip() or None - - def _cleanup(self, pdd): - page = getattr(pdd, "page", None) - if not page: - return - for method in ("quit", "close", "stop", "terminate"): - func = getattr(page, method, None) - if func: - try: - func() - except Exception: - pass - break - - def run( - self, - user_id, - file_path, - topics="", - time_start=None, - interval=None, - creator_link=None, - count=None, - stop_callback=None, - ): - self._stop_requested = False - normalized_time = self._normalize_time_start(time_start) - last_exc = None - pdd = None - def combined_stop(): - if self._stop_requested: - return True - if stop_callback: - try: - return bool(stop_callback()) - except Exception: - return False - return False - for attempt in range(self.retries + 1): - # 检查停止请求 - if combined_stop(): - return False, "已停止" - - try: - pdd = Pdd( - user_id=user_id, - file_path=file_path, - topics=topics, - time_start=normalized_time, - interval=interval, - creator_link=creator_link, - count=count, - ) - pdd.action(stop_callback=combined_stop) - return True, "完成" - except Exception as exc: - # 检查是否是因为停止请求导致的异常 - if combined_stop(): - return False, "已停止" - - last_exc = exc - import traceback - error_msg = f"{type(exc).__name__}: {str(exc)}" - if attempt < self.retries: - print(f"任务失败 (尝试 {attempt + 1}/{self.retries + 1}),{self.retry_delay}秒后重试: {error_msg}") - # 在等待期间也检查停止请求 - for _ in range(self.retry_delay): - if self._stop_requested or (stop_callback and stop_callback()): - return False, "已停止" - time.sleep(1) - else: - print(f"任务最终失败: {error_msg}") - traceback.print_exc() - finally: - if pdd: - try: - self._cleanup(pdd) - except Exception as cleanup_exc: - print(f"清理资源时出错: {cleanup_exc}") - return False, f"失败:{last_exc}"