dy_cookie_manager.py 9.4 KB

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