dy_cookie_manager.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import asyncio
  2. import time
  3. import json
  4. import hashlib
  5. from typing import Optional
  6. from loguru import logger
  7. import redis
  8. from config import REDIS_CONFIG
  9. class DouyinCookieManager:
  10. def __init__(self, cookie_key: str):
  11. """
  12. Douyin Cookie 管理器
  13. :param cookie_key: Redis 键名,如 "cookies:douyin:detail"
  14. """
  15. self.redis = redis.Redis(**REDIS_CONFIG)
  16. self.key_list = cookie_key
  17. self.key_info = f"{cookie_key}:info"
  18. # Cookie 管理操作
  19. def add_cookie(self, cookie: str):
  20. """手动新增一个 Cookie"""
  21. try:
  22. if isinstance(cookie, bytes):
  23. cookie = cookie.decode("utf-8")
  24. cookie_id = self._generate_cookie_id(cookie)
  25. # 检查是否已存在
  26. existing_info = self.redis.hget(self.key_info, cookie_id)
  27. if existing_info:
  28. logger.info(f"[⚠️] Cookie 已存在,跳过: {cookie_key}")
  29. return
  30. # 添加到列表和哈希表
  31. self.redis.lpush(self.key_list, cookie)
  32. info = {
  33. "cookie": cookie,
  34. "use_count": 0,
  35. "status": "ok",
  36. "fail": 0,
  37. "first_use": 0,
  38. "last_use": 0,
  39. "last_fail_time": 0,
  40. "added_time": int(time.time())
  41. }
  42. self.redis.hset(self.key_info, cookie_id, json.dumps(info))
  43. logger.info(f"[✅] 新增 Cookie 成功,ID: {cookie_id}")
  44. except Exception as e:
  45. logger.error(f"添加Cookie异常: {e}")
  46. def get_cookie(self) -> Optional[str]:
  47. """获取一个 Cookie 并更新使用信息"""
  48. try:
  49. # 从列表尾部取出并放回头部,实现轮询
  50. cookie = self.redis.rpoplpush(self.key_list, self.key_list)
  51. if not cookie:
  52. logger.info("[❗] 当前无可用 Cookie")
  53. return None
  54. # 解码为字符串
  55. cookie_str = cookie.decode() if isinstance(cookie, bytes) else cookie
  56. cookie_id = self._generate_cookie_id(cookie_str)
  57. now = int(time.time())
  58. # 获取 info
  59. info_raw = self.redis.hget(self.key_info, cookie_id)
  60. if not info_raw:
  61. # 若 info 不存在,自动补建
  62. info_data = {
  63. "cookie": cookie_str,
  64. "use_count": 1,
  65. "status": "ok",
  66. "fail": 0,
  67. "first_use": now,
  68. "last_use": now,
  69. "last_fail_time": 0,
  70. "added_time": now
  71. }
  72. else:
  73. info_data = json.loads(info_raw.decode() if isinstance(info_raw, bytes) else info_raw)
  74. info_data["use_count"] = info_data.get("use_count", 0) + 1
  75. info_data["last_use"] = now
  76. if not info_data.get("first_use"):
  77. info_data["first_use"] = now
  78. self.redis.hset(self.key_info, cookie_id, json.dumps(info_data))
  79. logger.info(f"[✅] 获取 Cookie 成功,ID: {cookie_id}")
  80. return cookie_str
  81. except Exception as e:
  82. logger.error(f"获取Cookie异常: {e}")
  83. return None
  84. def mark_fail(self, cookie: str):
  85. """标记 Cookie 使用失败"""
  86. self._update_info(cookie, success=False)
  87. def _update_info(self, cookie: str, success: bool):
  88. """更新 Cookie 使用状态"""
  89. try:
  90. if isinstance(cookie, bytes):
  91. cookie = cookie.decode("utf-8")
  92. cookie_id = self._generate_cookie_id(cookie)
  93. info_raw = self.redis.hget(self.key_info, cookie_id)
  94. if not info_raw:
  95. logger.warning(f"[⚠️] Cookie 信息不存在: {cookie_id}")
  96. return
  97. info_data = json.loads(info_raw.decode() if isinstance(info_raw, bytes) else info_raw)
  98. now = int(time.time())
  99. if not success:
  100. info_data["fail"] = info_data.get("fail", 0) + 1
  101. if not info_data.get("first_fail_time"):
  102. info_data["first_fail_time"] = now
  103. info_data["last_fail_time"] = now
  104. if info_data["fail"] >= 10:
  105. info_data["status"] = "banned"
  106. self.redis.lrem(self.key_list, 0, cookie)
  107. info_data["last_use"] = now
  108. self.redis.hset(self.key_info, cookie_id, json.dumps(info_data))
  109. except Exception as e:
  110. logger.error(f"更新Cookie信息异常: {e}")
  111. def delete_cookie(self, cookie: str):
  112. """删除 Cookie(从列表与 info 中同时移除)"""
  113. try:
  114. if isinstance(cookie, bytes):
  115. cookie = cookie.decode("utf-8")
  116. cookie_id = self._generate_cookie_id(cookie)
  117. # 删除 info
  118. self.redis.hdel(self.key_info, cookie_id)
  119. # 删除列表中对应 cookie(兼容 bytes)
  120. raw_list = self.redis.lrange(self.key_list, 0, -1)
  121. for item in raw_list:
  122. item_str = item.decode("utf-8") if isinstance(item, bytes) else item
  123. if item_str == cookie:
  124. self.redis.lrem(self.key_list, 0, item)
  125. logger.info(f"[✅] 删除 Cookie 成功,ID: {cookie_id}")
  126. return
  127. logger.warning(f"[⚠️] 未找到列表中对应 Cookie: {cookie_id}")
  128. except Exception as e:
  129. logger.error(f"删除Cookie异常: {e}")
  130. def list_cookies(self):
  131. """列出所有 Cookie 信息"""
  132. try:
  133. data = self.redis.hgetall(self.key_info)
  134. result = {}
  135. for k, v in data.items():
  136. try:
  137. key = k.decode() if isinstance(k, bytes) else k
  138. value = v.decode() if isinstance(v, bytes) else v
  139. result[key] = json.loads(value)
  140. except Exception:
  141. continue
  142. # 格式化时间戳为可读格式
  143. for cookie_info in result.values():
  144. if "first_use" in cookie_info and cookie_info["first_use"] > 0:
  145. import datetime
  146. cookie_info["first_use_formatted"] = datetime.datetime.fromtimestamp(
  147. cookie_info["first_use"]).strftime('%Y-%m-%d %H:%M:%S')
  148. else:
  149. cookie_info["first_use_formatted"] = "从未使用"
  150. if "last_use" in cookie_info and cookie_info["last_use"] > 0:
  151. import datetime
  152. cookie_info["last_use_formatted"] = datetime.datetime.fromtimestamp(
  153. cookie_info["last_use"]).strftime('%Y-%m-%d %H:%M:%S')
  154. else:
  155. cookie_info["last_use_formatted"] = "从未使用"
  156. if "last_fail_time" in cookie_info and cookie_info["last_fail_time"] > 0:
  157. import datetime
  158. cookie_info["last_fail_time_formatted"] = datetime.datetime.fromtimestamp(
  159. cookie_info["last_fail_time"]).strftime('%Y-%m-%d %H:%M:%S')
  160. else:
  161. cookie_info["last_fail_time_formatted"] = "从未失败"
  162. if "added_time" in cookie_info and cookie_info["added_time"] > 0:
  163. import datetime
  164. cookie_info["added_time_formatted"] = datetime.datetime.fromtimestamp(
  165. cookie_info["added_time"]).strftime('%Y-%m-%d %H:%M:%S')
  166. else:
  167. cookie_info["added_time_formatted"] = "未知时间"
  168. return result
  169. except Exception as e:
  170. logger.error(f"列出Cookie异常: {e}")
  171. return {}
  172. def get_cookie_count(self):
  173. """获取 Cookie 数量"""
  174. try:
  175. return self.redis.llen(self.key_list)
  176. except Exception as e:
  177. logger.error(f"获取数量失败 {self.key_list}: {e}")
  178. return 0
  179. def _generate_cookie_id(self, cookie: str) -> str:
  180. """使用 MD5 生成固定长度的 cookie_id"""
  181. return hashlib.md5(cookie.encode("utf-8")).hexdigest()
  182. # 测试函数
  183. def main():
  184. # 创建两个管理器实例,对应两个任务
  185. search_mgr = DouyinCookieManager("cookies:douyin:search")
  186. detail_mgr = DouyinCookieManager("cookies:douyin:detail")
  187. print("🎯 Douyin Cookie 管理器测试")
  188. print("=" * 60)
  189. # 显示当前状态
  190. search_count = search_mgr.get_cookie_count()
  191. detail_count = detail_mgr.get_cookie_count()
  192. print(f"搜索任务 Cookie 数量: {search_count}")
  193. print(f"浏览任务 Cookie 数量: {detail_count}")
  194. # 列出所有 Cookie 信息(如果有的话)
  195. if search_count > 0:
  196. print("\n搜索任务 Cookie 详情:")
  197. search_cookies = search_mgr.list_cookies()
  198. for cookie_id, info in list(search_cookies.items())[:3]: # 只显示前3个
  199. print(f" - {cookie_id[:8]}...: 使用次数={info.get('use_count', 0)}, 状态={info.get('status', 'unknown')}")
  200. if detail_count > 0:
  201. print("\n浏览任务 Cookie 详情:")
  202. detail_cookies = detail_mgr.list_cookies()
  203. for cookie_id, info in list(detail_cookies.items())[:3]: # 只显示前3个
  204. print(f" - {cookie_id[:8]}...: 使用次数={info.get('use_count', 0)}, 状态={info.get('status', 'unknown')}")
  205. if __name__ == "__main__":
  206. main()