Browse Source

初始化项目:首次提交本地代码

zhangliang 2 weeks ago
commit
d7c0bdc52e

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 8 - 0
.idea/douyin-cookie-generator.iml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="jdk" jdkName="Python 3.11 (爬虫相关)" jdkType="Python SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 20 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,20 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
+      <option name="ignoredErrors">
+        <list>
+          <option value="N806" />
+          <option value="N801" />
+        </list>
+      </option>
+    </inspection_tool>
+    <inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="ignoredIdentifiers">
+        <list>
+          <option value="int.*" />
+        </list>
+      </option>
+    </inspection_tool>
+  </profile>
+</component>

+ 6 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 7 - 0
.idea/misc.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Black">
+    <option name="sdkName" value="Python 3.11 (cyber_crawler)" />
+  </component>
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (爬虫相关)" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/douyin-cookie-generator.iml" filepath="$PROJECT_DIR$/.idea/douyin-cookie-generator.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 43 - 0
README.md

@@ -0,0 +1,43 @@
+# 抖音 Cookie 生成器 - 多定时任务版
+
+一个自动生成抖音 Cookie 的工具,支持多定时任务和不同的执行策略。
+
+## 功能特性
+
+- ✅ 自动获取抖音 Cookie
+- ✅ 支持无头模式(服务器部署)
+- ✅ Redis 存储和管理 Cookie
+- ✅ 多定时任务,支持不同策略
+- ✅ 实时任务状态监控
+- ✅ 批量生成多个 Cookie
+- ✅ 自动复制到剪贴板
+
+## 任务配置
+
+系统预配置了三个任务:
+
+1. **日常维护任务** (task_daily)
+   - 时间: 每天凌晨2点
+   - 数量: 5个Cookie
+   - 间隔: 10分钟
+   - 用途: 日常维护,生成高质量Cookie
+
+2. **高频补充任务** (task_hourly)
+   - 时间: 每6小时执行一次
+   - 数量: 2个Cookie
+   - 间隔: 5分钟
+   - 用途: 高频补充,保持Cookie池活跃
+
+3. **测试任务** (task_test)
+   - 时间: 每30分钟执行一次
+   - 数量: 1个Cookie
+   - 间隔: 1分钟
+   - 用途: 测试任务,用于调试(默认禁用)
+
+## 快速开始
+
+### 安装依赖
+
+```bash
+pip install -r requirements.txt
+playwright install chromium

BIN
__pycache__/config.cpython-311.pyc


BIN
__pycache__/cookie_generator.cpython-311.pyc


BIN
__pycache__/dy_cookie_manager.cpython-311.pyc


BIN
__pycache__/scheduler.cpython-311.pyc


+ 44 - 0
config.py

@@ -0,0 +1,44 @@
+# 双任务独立配置
+
+# Redis 配置
+REDIS_CONFIG = {
+    'host': 'r-t4n023zec9wyjeer0spd.redis.singapore.rds.aliyuncs.com',
+    'port': 6379,
+    'db': 15,
+    'username': 'denet_crawler',
+    'password': 'denet_crawler2023'
+}
+
+# 双任务独立配置
+TASKS_CONFIG = {
+    # 搜索 Cookie 任务
+    "search_task": {
+        "cookie_key": "cookies:douyin:search",  # 存储键
+        "enabled": True,  # 是否启用
+        "check_interval": 10,  # 检查间隔(分钟)
+        "target_count": 100,  # 目标数量
+        "batch_size": 100,  # 每次补充数量
+        "description": "抖音搜索 Cookie"  # 任务描述
+    },
+
+    # 浏览 Cookie 任务
+    "detail_task": {
+        "cookie_key": "cookies:douyin:detail",  # 存储键
+        "enabled": True,  # 是否启用
+        "check_interval": 20,  # 检查间隔(分钟)
+        "target_count": 80,  # 目标数量
+        "batch_size": 80,  # 每次补充数量
+        "description": "抖音详情 Cookie"  # 任务描述
+    }
+}
+
+# 浏览器配置
+BROWSER_CONFIG = {
+    "headless": True,
+    "args": [
+        "--no-sandbox",
+        "--disable-setuid-sandbox",
+        "--disable-dev-shm-usage",
+        "--disable-blink-features=AutomationControlled",
+    ]
+}

+ 138 - 0
cookie_generator.py

@@ -0,0 +1,138 @@
+#!/usr/bin/env python3
+# Cookie 管理器 - 同步版本
+
+import random
+import time
+from loguru import logger
+from playwright.sync_api import sync_playwright
+from config import BROWSER_CONFIG
+from dy_cookie_manager import DouyinCookieManager
+
+
+class CookieGenerator:
+    def __init__(self):
+        self.user_agents = [
+            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.7339.81 Safari/537.36",
+            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.7339.81 Safari/537.36",
+        ]
+
+        # 为两个任务创建管理器实例
+        self.search_manager = DouyinCookieManager("cookies:douyin:search")
+        self.detail_manager = DouyinCookieManager("cookies:douyin:detail")
+
+    def check_environment(self):
+        """检查环境"""
+        try:
+            with sync_playwright() as p:
+                browser = p.chromium.launch(headless=True, args=["--no-sandbox"])
+                browser.close()
+            logger.info("✅ 环境检查通过")
+            return True
+        except Exception as e:
+            logger.error(f"❌ 环境检查失败: {e}")
+            return False
+
+
+
+    def generate_cookie(self):
+        """生成一个 Cookie """
+        with sync_playwright() as p:
+            browser = None
+            try:
+                browser = p.chromium.launch(**BROWSER_CONFIG)
+
+                context = browser.new_context(
+                    user_agent=random.choice(self.user_agents),
+                    viewport={"width": 1920, "height": 1080}
+                )
+                page = context.new_page()
+
+                # 反检测
+                page.add_init_script("""
+                    Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
+                """)
+
+                # 访问抖音
+                page.goto("https://www.douyin.com", timeout=30000, wait_until="domcontentloaded")
+                time.sleep(5)
+
+                # 获取 Cookie
+                cookies = context.cookies("https://www.douyin.com")
+                cookie_str = self._cookies_to_string(cookies)
+
+                if cookie_str and len(cookie_str) > 50:
+                    logger.info(f"✅ Cookie 生成成功\n{cookie_str}")
+                    return cookie_str
+                else:
+                    logger.error("❌ Cookie 生成失败")
+                    return None
+
+            except Exception as e:
+                logger.error(f"❌ 生成过程出错: {e}")
+                return None
+            finally:
+                if browser:
+                    browser.close()
+
+    def save_cookie(self, cookie, cookie_key):
+        """保存 Cookie - 使用 DouyinCookieManager"""
+        try:
+            # 使用 DouyinCookieManager 保存 Cookie
+            self.get_cookie_manager(cookie_key).add_cookie(cookie)
+            logger.info(f"💾 Cookie 保存到: {cookie_key}")
+            return True
+
+        except Exception as e:
+            logger.error(f"❌ 保存失败 {cookie_key}: {e}")
+            return False
+
+    def genera_cookies(self, task_config):
+        """根据任务配置补充 Cookie """
+        cookie_key = task_config["cookie_key"]
+        target_count = task_config["target_count"]
+        batch_size = task_config["batch_size"]
+
+        current_count = self.get_cookie_manager(cookie_key).get_cookie_count()
+
+        if current_count >= target_count:
+            logger.info(f"✅ {cookie_key} 数量充足: {current_count}/{target_count}")
+            return 0, current_count
+
+        need_count = target_count - current_count
+        generate_count = min(need_count, batch_size)
+
+        if generate_count <= 0:
+            return 0, current_count
+
+        logger.info(f"🔄 补充 {cookie_key}: {current_count} -> {target_count}, 本次生成 {generate_count} 个")
+
+        success_count = 0
+        for i in range(generate_count):
+            cookie = self.generate_cookie()
+            if cookie and self.save_cookie(cookie, cookie_key):
+                success_count += 1
+                logger.info(f"✅ {cookie_key} 第 {i + 1}/{generate_count} 个生成成功")
+            else:
+                logger.error(f"❌ {cookie_key} 第 {i + 1}/{generate_count} 个生成失败")
+
+            # 短暂间隔
+            if i < generate_count - 1:
+                time.sleep(2)
+
+        new_count = self.get_cookie_manager(cookie_key).get_cookie_count()
+        logger.info(f"🎯 {cookie_key} 补充完成: {current_count} -> {new_count}, 成功 {success_count} 个")
+        return success_count, new_count
+
+    def _cookies_to_string(self, cookies):
+        """Cookie 列表转字符串"""
+        return '; '.join([f"{c['name']}={c['value']}" for c in cookies if c.get('name')])
+
+    # 获取 Cookie 管理器实例
+    def get_cookie_manager(self, cookie_key):
+        """获取指定任务的 Cookie 管理器"""
+        if cookie_key == "cookies:douyin:search":
+            return self.search_manager
+        elif cookie_key == "cookies:douyin:detail":
+            return self.detail_manager
+        else:
+            return None

+ 100 - 0
dual_task_scheduler.log

@@ -0,0 +1,100 @@
+2025-11-19 15:57:23 | INFO | 🎯 启动抖音 Cookie 双任务系统
+2025-11-19 15:57:23 | ERROR | ❌ 环境检查失败: Error 61 connecting to localhost:6379. Connection refused.
+2025-11-19 15:57:23 | ERROR | ❌ 环境检查失败,系统退出
+2025-11-19 16:51:10 | INFO | ✅ 环境检查通过
+2025-11-19 16:52:19 | INFO | ✅ 环境检查通过
+2025-11-19 16:52:19 | ERROR | 获取数量失败 cookies:douyin:search: Error 61 connecting to localhost:6379. Connection refused.
+2025-11-19 16:52:19 | ERROR | 获取数量失败 cookies:douyin:detail: Error 61 connecting to localhost:6379. Connection refused.
+2025-11-19 16:52:19 | INFO | 
+🎯 抖音 Cookie 双任务状态
+============================================================
+📋 search_task (搜索专用 Cookie)
+   🔄 状态: 0/100
+   ⏰ 检查: 每 15 分钟
+   📦 批量: 100 个/次
+   🔑 存储: cookies:douyin:search
+📋 detail_task (浏览专用 Cookie)
+   🔄 状态: 0/80
+   ⏰ 检查: 每 25 分钟
+   📦 批量: 80 个/次
+   🔑 存储: cookies:douyin:detail
+============================================================
+2025-11-19 16:52:19 | INFO | ⏰ 设置独立任务调度...
+2025-11-19 16:52:19 | INFO | ✅ 设置任务: search_task
+2025-11-19 16:52:19 | INFO |    📝 搜索专用 Cookie
+2025-11-19 16:52:19 | INFO |    ⏰ 间隔: 每 15 分钟
+2025-11-19 16:52:19 | INFO |    🎯 目标: 100 个
+2025-11-19 16:52:19 | INFO |    📦 批量: 100 个/次
+2025-11-19 16:52:19 | INFO | ✅ 设置任务: detail_task
+2025-11-19 16:52:19 | INFO |    📝 浏览专用 Cookie
+2025-11-19 16:52:19 | INFO |    ⏰ 间隔: 每 25 分钟
+2025-11-19 16:52:19 | INFO |    🎯 目标: 80 个
+2025-11-19 16:52:19 | INFO |    📦 批量: 80 个/次
+2025-11-19 16:52:19 | INFO | 🔄 系统运行中...
+2025-11-19 16:52:19 | INFO | 🚀 立即执行所有任务...
+2025-11-19 16:52:19 | ERROR | 获取数量失败 cookies:douyin:search: Error 61 connecting to localhost:6379. Connection refused.
+2025-11-19 16:52:19 | INFO | 🔄 补充 cookies:douyin:search: 0 -> 100, 本次生成 100 个
+2025-11-19 16:52:26 | INFO | ✅ Cookie 生成成功
+2025-11-19 16:52:26 | ERROR | 添加Cookie异常: Error 61 connecting to localhost:6379. Connection refused.
+2025-11-19 16:52:26 | INFO | 💾 Cookie 保存到: cookies:douyin:search
+2025-11-19 16:52:26 | INFO | ✅ cookies:douyin:search 第 1/100 个生成成功
+2025-11-19 16:52:36 | INFO | ✅ Cookie 生成成功
+2025-11-19 16:52:36 | ERROR | 添加Cookie异常: Error 61 connecting to localhost:6379. Connection refused.
+2025-11-19 16:52:36 | INFO | 💾 Cookie 保存到: cookies:douyin:search
+2025-11-19 16:52:36 | INFO | ✅ cookies:douyin:search 第 2/100 个生成成功
+2025-11-19 17:04:51 | INFO | ✅ 环境检查通过
+2025-11-19 17:04:51 | INFO | 
+🎯 抖音 Cookie 双任务状态
+============================================================
+任务现状:
+📋 search_task (搜索专用 Cookie)
+   🔄 状态: 99/100
+   ⏰ 检查: 每 15 分钟
+   📦 批量: 100 个/次
+   🔑 存储: cookies:douyin:search
+任务现状:
+📋 detail_task (浏览专用 Cookie)
+   ✅ 状态: 89/80
+   ⏰ 检查: 每 25 分钟
+   📦 批量: 80 个/次
+   🔑 存储: cookies:douyin:detail
+============================================================
+2025-11-19 17:04:51 | INFO | ⏰ 设置独立任务调度...
+2025-11-19 17:04:51 | INFO | ✅ 设置任务: search_task
+2025-11-19 17:04:51 | INFO |    📝 搜索专用 Cookie
+2025-11-19 17:04:51 | INFO |    ⏰ 间隔: 每 15 分钟
+2025-11-19 17:04:51 | INFO |    🎯 目标: 100 个
+2025-11-19 17:04:51 | INFO |    📦 批量: 100 个/次
+2025-11-19 17:04:51 | INFO | ✅ 设置任务: detail_task
+2025-11-19 17:04:51 | INFO |    📝 浏览专用 Cookie
+2025-11-19 17:04:51 | INFO |    ⏰ 间隔: 每 25 分钟
+2025-11-19 17:04:51 | INFO |    🎯 目标: 80 个
+2025-11-19 17:04:51 | INFO |    📦 批量: 80 个/次
+2025-11-19 17:04:51 | INFO | 🔄 系统运行中...
+2025-11-19 17:04:51 | INFO | 🚀 立即执行所有任务...
+2025-11-19 17:04:51 | INFO | 🔄 补充 cookies:douyin:search: 99 -> 100, 本次生成 1 个
+2025-11-19 17:04:58 | INFO | ✅ Cookie 生成成功
+2025-11-19 17:04:59 | INFO | [✅] 新增 Cookie 成功,ID: fcfae3ee6f47d6843b68716686843723
+2025-11-19 17:04:59 | INFO | 💾 Cookie 保存到: cookies:douyin:search
+2025-11-19 17:04:59 | INFO | ✅ cookies:douyin:search 第 1/1 个生成成功
+2025-11-19 17:04:59 | INFO | 🎯 cookies:douyin:search 补充完成: 99 -> 100, 成功 1 个
+2025-11-19 17:04:59 | INFO | ✅ cookies:douyin:detail 数量充足: 89/80
+2025-11-19 17:04:59 | INFO | 🎊 所有任务完成! 共生成 1 个Cookie, 耗时 8.2秒
+2025-11-19 17:19:59 | INFO | 🔍 开始检查任务: search_task
+2025-11-19 17:19:59 | INFO | ✅ cookies:douyin:search 数量充足: 100/100
+2025-11-19 17:19:59 | INFO | ✅ 任务完成: search_task
+2025-11-19 17:19:59 | INFO |    📊 结果: 0 个生成成功
+2025-11-19 17:19:59 | INFO |    📦 当前: 100/100 个
+2025-11-19 17:19:59 | INFO |    ⏱️  耗时: 0.1 秒
+2025-11-19 17:29:59 | INFO | 🔍 开始检查任务: detail_task
+2025-11-19 17:29:59 | INFO | ✅ cookies:douyin:detail 数量充足: 89/80
+2025-11-19 17:29:59 | INFO | ✅ 任务完成: detail_task
+2025-11-19 17:29:59 | INFO |    📊 结果: 0 个生成成功
+2025-11-19 17:29:59 | INFO |    📦 当前: 89/80 个
+2025-11-19 17:29:59 | INFO |    ⏱️  耗时: 0.1 秒
+2025-11-19 17:34:59 | INFO | 🔍 开始检查任务: search_task
+2025-11-19 17:34:59 | INFO | ✅ cookies:douyin:search 数量充足: 100/100
+2025-11-19 17:34:59 | INFO | ✅ 任务完成: search_task
+2025-11-19 17:34:59 | INFO |    📊 结果: 0 个生成成功
+2025-11-19 17:34:59 | INFO |    📦 当前: 100/100 个
+2025-11-19 17:34:59 | INFO |    ⏱️  耗时: 0.1 秒

+ 249 - 0
dy_cookie_manager.py

@@ -0,0 +1,249 @@
+import asyncio
+import time
+import json
+import hashlib
+from typing import Optional
+from loguru import logger
+import redis
+from config import REDIS_CONFIG
+
+
+class DouyinCookieManager:
+    def __init__(self, cookie_key: str):
+        """
+        Douyin Cookie 管理器
+        :param cookie_key: Redis 键名,如 "cookies:douyin:detail"
+        """
+        self.redis = redis.Redis(**REDIS_CONFIG)
+        self.redis.ping()
+        self.key_list = cookie_key
+        self.key_info = f"{cookie_key}:info"
+
+    # Cookie 管理操作
+
+    def add_cookie(self, cookie: str):
+        """手动新增一个 Cookie"""
+        try:
+            if isinstance(cookie, bytes):
+                cookie = cookie.decode("utf-8")
+
+            cookie_id = self._generate_cookie_id(cookie)
+
+            # 检查是否已存在
+            existing_info = self.redis.hget(self.key_info, cookie_id)
+            if existing_info:
+                logger.info(f"[⚠️] Cookie 已存在,跳过: {cookie_key}")
+                return
+
+            # 添加到列表和哈希表
+            self.redis.lpush(self.key_list, cookie)
+            info = {
+                "cookie": cookie,
+                "use_count": 0,
+                "status": "ok",
+                "fail": 0,
+                "first_use": 0,
+                "last_use": 0,
+                "last_fail_time": 0,
+                "added_time": int(time.time())
+            }
+            self.redis.hset(self.key_info, cookie_id, json.dumps(info))
+            logger.info(f"[✅] 新增 Cookie 成功,ID: {cookie_id}")
+        except Exception as e:
+            logger.error(f"添加Cookie异常: {e}")
+
+    def get_cookie(self) -> Optional[str]:
+        """获取一个 Cookie 并更新使用信息"""
+        try:
+            # 从列表尾部取出并放回头部,实现轮询
+            cookie = self.redis.rpoplpush(self.key_list, self.key_list)
+            if not cookie:
+                logger.info("[❗] 当前无可用 Cookie")
+                return None
+
+            # 解码为字符串
+            cookie_str = cookie.decode() if isinstance(cookie, bytes) else cookie
+            cookie_id = self._generate_cookie_id(cookie_str)
+
+            now = int(time.time())
+
+            # 获取 info
+            info_raw = self.redis.hget(self.key_info, cookie_id)
+            if not info_raw:
+                # 若 info 不存在,自动补建
+                info_data = {
+                    "cookie": cookie_str,
+                    "use_count": 1,
+                    "status": "ok",
+                    "fail": 0,
+                    "first_use": now,
+                    "last_use": now,
+                    "last_fail_time": 0,
+                    "added_time": now
+                }
+            else:
+                info_data = json.loads(info_raw.decode() if isinstance(info_raw, bytes) else info_raw)
+                info_data["use_count"] = info_data.get("use_count", 0) + 1
+                info_data["last_use"] = now
+                if not info_data.get("first_use"):
+                    info_data["first_use"] = now
+
+            self.redis.hset(self.key_info, cookie_id, json.dumps(info_data))
+            logger.info(f"[✅] 获取 Cookie 成功,ID: {cookie_id}")
+            return cookie_str
+
+        except Exception as e:
+            logger.error(f"获取Cookie异常: {e}")
+            return None
+
+    def mark_fail(self, cookie: str):
+        """标记 Cookie 使用失败"""
+        self._update_info(cookie, success=False)
+
+    def _update_info(self, cookie: str, success: bool):
+        """更新 Cookie 使用状态"""
+        try:
+            if isinstance(cookie, bytes):
+                cookie = cookie.decode("utf-8")
+
+            cookie_id = self._generate_cookie_id(cookie)
+            info_raw = self.redis.hget(self.key_info, cookie_id)
+
+            if not info_raw:
+                logger.warning(f"[⚠️] Cookie 信息不存在: {cookie_id}")
+                return
+
+            info_data = json.loads(info_raw.decode() if isinstance(info_raw, bytes) else info_raw)
+            now = int(time.time())
+
+            if not success:
+                info_data["fail"] = info_data.get("fail", 0) + 1
+                if not info_data.get("first_fail_time"):
+                    info_data["first_fail_time"] = now
+                info_data["last_fail_time"] = now
+                if info_data["fail"] >= 10:
+                    info_data["status"] = "banned"
+                    self.redis.lrem(self.key_list, 0, cookie)
+
+            info_data["last_use"] = now
+            self.redis.hset(self.key_info, cookie_id, json.dumps(info_data))
+        except Exception as e:
+            logger.error(f"更新Cookie信息异常: {e}")
+
+    def delete_cookie(self, cookie: str):
+        """删除 Cookie(从列表与 info 中同时移除)"""
+        try:
+            if isinstance(cookie, bytes):
+                cookie = cookie.decode("utf-8")
+
+            cookie_id = self._generate_cookie_id(cookie)
+
+            # 删除 info
+            self.redis.hdel(self.key_info, cookie_id)
+
+            # 删除列表中对应 cookie(兼容 bytes)
+            raw_list = self.redis.lrange(self.key_list, 0, -1)
+            for item in raw_list:
+                item_str = item.decode("utf-8") if isinstance(item, bytes) else item
+                if item_str == cookie:
+                    self.redis.lrem(self.key_list, 0, item)
+                    logger.info(f"[✅] 删除 Cookie 成功,ID: {cookie_id}")
+                    return
+
+            logger.warning(f"[⚠️] 未找到列表中对应 Cookie: {cookie_id}")
+
+        except Exception as e:
+            logger.error(f"删除Cookie异常: {e}")
+
+    def list_cookies(self):
+        """列出所有 Cookie 信息"""
+        try:
+            data = self.redis.hgetall(self.key_info)
+            result = {}
+            for k, v in data.items():
+                try:
+                    key = k.decode() if isinstance(k, bytes) else k
+                    value = v.decode() if isinstance(v, bytes) else v
+                    result[key] = json.loads(value)
+                except Exception:
+                    continue
+
+            # 格式化时间戳为可读格式
+            for cookie_info in result.values():
+                if "first_use" in cookie_info and cookie_info["first_use"] > 0:
+                    import datetime
+                    cookie_info["first_use_formatted"] = datetime.datetime.fromtimestamp(
+                        cookie_info["first_use"]).strftime('%Y-%m-%d %H:%M:%S')
+                else:
+                    cookie_info["first_use_formatted"] = "从未使用"
+
+                if "last_use" in cookie_info and cookie_info["last_use"] > 0:
+                    import datetime
+                    cookie_info["last_use_formatted"] = datetime.datetime.fromtimestamp(
+                        cookie_info["last_use"]).strftime('%Y-%m-%d %H:%M:%S')
+                else:
+                    cookie_info["last_use_formatted"] = "从未使用"
+
+                if "last_fail_time" in cookie_info and cookie_info["last_fail_time"] > 0:
+                    import datetime
+                    cookie_info["last_fail_time_formatted"] = datetime.datetime.fromtimestamp(
+                        cookie_info["last_fail_time"]).strftime('%Y-%m-%d %H:%M:%S')
+                else:
+                    cookie_info["last_fail_time_formatted"] = "从未失败"
+
+                if "added_time" in cookie_info and cookie_info["added_time"] > 0:
+                    import datetime
+                    cookie_info["added_time_formatted"] = datetime.datetime.fromtimestamp(
+                        cookie_info["added_time"]).strftime('%Y-%m-%d %H:%M:%S')
+                else:
+                    cookie_info["added_time_formatted"] = "未知时间"
+            return result
+        except Exception as e:
+            logger.error(f"列出Cookie异常: {e}")
+            return {}
+
+    def get_cookie_count(self):
+        """获取 Cookie 数量"""
+        try:
+            return self.redis.llen(self.key_list)
+        except Exception as e:
+            logger.error(f"获取数量失败 {self.key_list}: {e}")
+            return 0
+
+    def _generate_cookie_id(self, cookie: str) -> str:
+        """使用 MD5 生成固定长度的 cookie_id"""
+        return hashlib.md5(cookie.encode("utf-8")).hexdigest()
+
+
+# 测试函数
+def main():
+    # 创建两个管理器实例,对应两个任务
+    search_mgr = DouyinCookieManager("cookies:douyin:search")
+    detail_mgr = DouyinCookieManager("cookies:douyin:detail")
+
+    print("🎯 Douyin Cookie 管理器测试")
+    print("=" * 60)
+
+    # 显示当前状态
+    search_count = search_mgr.get_cookie_count()
+    detail_count = detail_mgr.get_cookie_count()
+
+    print(f"搜索任务 Cookie 数量: {search_count}")
+    print(f"浏览任务 Cookie 数量: {detail_count}")
+
+    # 列出所有 Cookie 信息(如果有的话)
+    if search_count > 0:
+        print("\n搜索任务 Cookie 详情:")
+        search_cookies = search_mgr.list_cookies()
+        for cookie_id, info in list(search_cookies.items())[:3]:  # 只显示前3个
+            print(f"  - {cookie_id[:8]}...: 使用次数={info.get('use_count', 0)}, 状态={info.get('status', 'unknown')}")
+
+    if detail_count > 0:
+        print("\n浏览任务 Cookie 详情:")
+        detail_cookies = detail_mgr.list_cookies()
+        for cookie_id, info in list(detail_cookies.items())[:3]:  # 只显示前3个
+            print(f"  - {cookie_id[:8]}...: 使用次数={info.get('use_count', 0)}, 状态={info.get('status', 'unknown')}")
+
+
+if __name__ == "__main__":
+    main()

+ 9 - 0
redis_config.py

@@ -0,0 +1,9 @@
+# Redis 配置文件
+
+REDIS_CONFIG = {
+    'host': 'r-t4n023zec9wyjeer0spd.redis.singapore.rds.aliyuncs.com',
+    'port': 6379,
+    'db': 15,
+    'username': 'denet_crawler',
+    'password': 'denet_crawler2023'
+}

+ 4 - 0
requirements.txt

@@ -0,0 +1,4 @@
+loguru~=0.7.3
+schedule~=1.2.0
+redis~=7.0.1
+playwright~=1.55.0

+ 38 - 0
run.py

@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+# 启动脚本
+
+import sys
+import os
+
+# 添加当前路径
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+
+from scheduler import DualTaskScheduler
+
+
+def main():
+
+    # 显示任务配置
+    from config import TASKS_CONFIG
+
+    print("📋 任务配置:")
+    for task_name, config in TASKS_CONFIG.items():
+        if config["enabled"]:
+            print(f"  🎯 {task_name}:")
+            print(f"     📝 {config['description']}")
+            print(f"     ⏰ 检查间隔: 每 {config['check_interval']} 分钟")
+            print(f"     🎯 目标数量: {config['target_count']} 个")
+            print(f"     📦 批量大小: {config['batch_size']} 个/次")
+            print(f"     🔑 存储键: {config['cookie_key']}")
+            print()
+
+    print("=" * 60)
+    print("🚀 启动系统...")
+
+    # 启动调度器
+    scheduler = DualTaskScheduler()
+    scheduler.run()
+
+
+if __name__ == "__main__":
+    main()

+ 33 - 0
run.sh

@@ -0,0 +1,33 @@
+#!/bin/bash
+# 运行脚本
+
+echo "🎯 启动抖音 Cookie 生成器"
+
+# 检查Python
+if ! command -v python3 &> /dev/null; then
+    echo "❌ Python3 未安装"
+    exit 1
+fi
+
+# 检查虚拟环境
+if [ -d "venv" ]; then
+    echo "🔧 激活虚拟环境"
+    source venv/bin/activate
+fi
+
+# 检查依赖
+echo "📦 检查依赖..."
+pip install -r requirements.txt
+
+# 检查Playwright浏览器
+echo "🔍 检查浏览器..."
+python3 -c "from cookie_generator import check_playwright_browser; exit(0 if check_playwright_browser() else 1)"
+
+if [ $? -ne 0 ]; then
+    echo "🔄 安装 Playwright 浏览器..."
+    playwright install chromium
+fi
+
+# 运行主程序
+echo "🚀 启动主程序..."
+python3 main.py

+ 147 - 0
scheduler.py

@@ -0,0 +1,147 @@
+#!/usr/bin/env python3
+# 双任务独立调度器
+
+import time
+import schedule
+from datetime import datetime
+from loguru import logger
+from cookie_generator import CookieGenerator
+from config import TASKS_CONFIG
+
+
+class DualTaskScheduler:
+    def __init__(self):
+        self.manager = CookieGenerator()
+        self.setup_logging()
+
+    def setup_logging(self):
+        """日志配置"""
+        logger.add(
+            "dual_task_scheduler.log",
+            rotation="10 MB",
+            level="INFO",
+            format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
+        )
+
+    def setup_individual_schedules(self):
+        """为每个任务设置独立的调度"""
+        logger.info("⏰ 设置独立任务调度...")
+
+        for task_name, task_config in TASKS_CONFIG.items():
+            if not task_config["enabled"]:
+                logger.info(f"⏭️  跳过未启用任务: {task_name}")
+                continue
+
+            interval = task_config["check_interval"]
+
+            # 为每个任务设置独立的定时任务
+            schedule.every(interval).minutes.do(
+                self.run_single_task,
+                task_name=task_name,
+                task_config=task_config
+            )
+
+            logger.info(f"✅ 设置任务: {task_name}")
+            logger.info(f"   📝 {task_config['description']}")
+            logger.info(f"   ⏰ 间隔: 每 {interval} 分钟")
+            logger.info(f"   🎯 目标: {task_config['target_count']} 个")
+            logger.info(f"   📦 批量: {task_config['batch_size']} 个/次")
+
+    def run_single_task(self, task_name, task_config):
+        """运行单个任务"""
+        logger.info(f"🔍 开始检查任务: {task_name}")
+
+        start_time = datetime.now()
+
+        try:
+            success_count, new_count = self.manager.genera_cookies(task_config)
+
+            duration = (datetime.now() - start_time).total_seconds()
+            target_count = task_config["target_count"]
+
+            logger.info(f"✅ 任务完成: {task_name}")
+            logger.info(f"   📊 结果: {success_count} 个生成成功")
+            logger.info(f"   📦 当前: {new_count}/{target_count} 个")
+            logger.info(f"   ⏱️  耗时: {duration:.1f} 秒")
+
+        except Exception as e:
+            logger.error(f"❌ 任务失败 {task_name}: {e}")
+
+    def run_all_tasks_once(self):
+        """立即执行所有任务一次"""
+        logger.info("🚀 立即执行所有任务...")
+
+        total_generated = 0
+        start_time = datetime.now()
+
+        for task_name, task_config in TASKS_CONFIG.items():
+            if task_config["enabled"]:
+                success_count, _ = self.manager.genera_cookies(task_config)
+                total_generated += success_count
+
+        duration = (datetime.now() - start_time).total_seconds()
+        logger.info(f"🎊 所有任务完成! 共生成 {total_generated} 个Cookie, 耗时 {duration:.1f}秒")
+
+    def run_single_task_once(self, task_name):
+        """立即执行单个任务"""
+        if task_name not in TASKS_CONFIG:
+            logger.error(f"❌ 未知任务: {task_name}")
+            return
+
+        task_config = TASKS_CONFIG[task_name]
+        if not task_config["enabled"]:
+            logger.warning(f"⚠️  任务未启用: {task_name}")
+
+        logger.info(f"🚀 立即执行任务: {task_name}")
+        self.run_single_task(task_name, task_config)
+
+    def show_status(self):
+        """显示所有任务状态"""
+        status_report = f"""
+任务现状:
+{'=' * 60}"""
+
+        for task_name, task_config in TASKS_CONFIG.items():
+            if task_config["enabled"]:
+                cookie_key = task_config["cookie_key"]
+                current_count = self.manager.get_cookie_manager(cookie_key).get_cookie_count()
+                target_count = task_config["target_count"]
+                interval = task_config["check_interval"]
+
+                status_icon = "✅" if current_count >= target_count else "🔄"
+
+                status_report += f"""
+📋 {task_name} ({task_config['description']})
+   {status_icon} COOKIE数量: {current_count}/{target_count}
+   ⏰ 检查频率: 每 {interval} 分钟
+   📦 批量: {task_config['batch_size']} 个/次
+   🔑 存储: {cookie_key}"""
+
+        status_report += f"\n{'=' * 60}"
+        logger.info(status_report)
+
+    def run(self):
+        """运行调度器"""
+
+        # 环境检查
+        if not self.manager.check_environment():
+            logger.error("❌ 环境检查失败,系统退出")
+            return
+
+        # 显示初始状态
+        self.show_status()
+
+        # 设置独立调度
+        self.setup_individual_schedules()
+
+        logger.info("🔄 系统运行中...")
+
+        # 立即执行一次所有任务
+        self.run_all_tasks_once()
+
+        try:
+            while True:
+                schedule.run_pending()
+                time.sleep(60)  # 每分钟检查一次
+        except KeyboardInterrupt:
+            logger.info("⏹️ 系统停止")