什么是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. 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协议 。转载请注明出处!

通过定时器获取电影资料 上一篇
Scrapy-redis分布式爬虫 下一篇