cookie池系统设计和实现

什么是cookie池?

用于维护多条目标站点的有效cookie以便爬虫获取使用的服务(类似IP池)。

为什么要设计cookie池

  • 目标站点要求登录才能访问(如:知乎、淘宝、facebook, etc.)
  • 单账号会受到访问频率限制(多账号管理)
  • 模拟登录逻辑复杂 (将模拟登录单独做成服务)
  • 登录脚本采用不同编程语言开发(如:node.js的puppeteer, javascript的cypress)
  • 一些第三方库不支持同一种语言的不同版本(如:zheye对python3.6和python3.7)

cookie池的优点

  • 服务分离:多语言开发,类似微服务的理念。
  • 组件分离:比如redis可以换成mysql、Kafka等。
  • 各个服务分别部署:防止网站变化导致爬虫宕机。

cookie池系统设计

一个简易的cookie池系统架构如下图所示:
简易的cookie池

首先,最基本的功能是通过模拟登录服务获取cookie。

其次,因为cookie正常情况下都有过期时间,为保证cookie池系统独立稳定运行,需要实现Cookie检测服务。

然后,通过检测的有效cookie存入Redis中,以备爬虫运行时获取。

实现cookie池系统面临的挑战

  • 如何发现cookie池不够用。
  • 各个网站的cookie如何分开管理,代码如何更好的分离。
  • 如何及时发现某个cookie失效了。
  • 如何保证新加入的网站快速接入cookie池系统。
  • 如何统一管理配置。

实现cookie池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#1. cookie保存在redis中应该使用什么数据结构
#2. 数据结构应该满足: 1. 可以随机获取 2. 可以防止重复 - set
import json
import time
from concurrent.futures import ThreadPoolExecutor,as_completed
from functools import partial
import redis

#1. 如何确保每一个网站都会被单独的运行
class CookieServer():
def __init__(self,settings):
#加上decode_responses=True,写入的键值对中的value为str类型
self.redis_cli = redis.Redis(host=settings.REDIS_HOST,port=settings.REDIS_PORT,decode_responses=True)
self.service_list = []
self.settings = settings

def register(self,cls):
self.service_list.append(cls)

def login_service(self,srv):
while 1:
srv_cli = srv(self.settings)
srv_name = srv_cli.name
#Redis Scard 命令返回集合中元素的数量
cookie_nums = self.redis_cli.scard(self.settings.Accounts[srv_name]["cookie_key"])
if cookie_nums < self.settings.Accounts[srv_name]["max_cookie_nums"]:
cookie_dict = srv_cli.login()
#Redis Sadd 命令将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略。
self.redis_cli.sadd(self.settings.Accounts[srv_name]["cookie_key"], json.dumps(cookie_dict))
else:
print("{srv_name} 的cookie池已满,等待10s".format(srv_name=srv_name))
time.sleep(10)

#检测cookie是否可用
def check_cookie_service(self, srv):
while 1:
print("开始检测cookie是否可用")
srv_cli = srv(self.settings)
srv_name = srv_cli.name
all_cookies = self.redis_cli.smembers(self.settings.Accounts[srv_name]["cookie_key"])
print("目前可用cookie数量: {}".format(len(all_cookies)))
for cookie_str in all_cookies:
print("获取到cookie: {}".format(cookie_str))
cookie_dict = json.loads(cookie_str)
valid = srv_cli.check_cookie(cookie_dict)
if valid:
print("cookie 有效")
else:
print("cookie已经失效, 删除cookie")
self.redis_cli.srem(self.settings.Accounts[srv_name]["cookie_key"], cookie_str)
# 设置间隔,防止出现请求过于频繁,导致本来没失效的cookie失效了
interval = self.settings.Accounts[srv_name]["check_interval"]
print("{}s 后重新开始检测cookie".format(interval))
time.sleep(interval)

def start(self):
task_list = []
print("启动登录服务")
login_executor = ThreadPoolExecutor(max_workers=5)
for srv in self.service_list:
task = login_executor.submit(partial(self.login_service, srv))
task_list.append(task)

print("启动cookie检测服务")
check_executor = ThreadPoolExecutor(max_workers=5)
for srv in self.service_list:
task = check_executor.submit(partial(self.check_cookie_service, srv))
task_list.append(task)

for future in as_completed(task_list):
data = future.result()
print(data)

完整代码详见:https://github.com/yujiewong/CookieService


本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!