Middleware

SpiderMiddleware

这是项目中间件(可通过 spider 访问到对应的属性)

  • spider_start:爬虫启动时干什么
  • spider_error:爬虫出错时干什么
  • spider_close:爬虫结束时干什么(必定会执行,一般用来资源回收)
  • spider_end:爬虫结束时干什么(分布式时只执行一次,一般用来发送信息)

结构

import palp
from loguru import logger


class SpiderMiddleware(palp.SpiderMiddleware):
    def spider_start(self, spider) -> None:
        """
        spider 开始时的操作

        :param spider:
        :return:
        """

    def spider_error(self, spider, exception: Exception) -> None:
        """
        spider 出错时的操作

        :param spider:
        :param exception: 错误的详细信息
        :return:
        """
        logger.error(exception)

    def spider_close(self, spider) -> None:
        """
        spider 结束的操作

        :param spider:
        :return:
        """

    def spider_close(self, spider) -> None:
        """
        spider 结束的操作(分布式时只执行一次)

        :param spider:
        :return:
        """

设置

注意:这里的 1 代表顺序

SPIDER_MIDDLEWARE = {
    1: 'middlewares.middleware.SpiderMiddleware',
}

RequestMiddleware

这是请求中间件(可通过 spider 访问到对应的属性)

  • request_in:请求创建时干什么
  • request_error:请求出错时干什么(默认重试 3 次 settings.REQUEST_RETRY_TIMES)
  • request_failed:请求失败时干什么(3 次后还失败,走这里)
  • request_close:请求结束时干什么(执行完毕后走这里)
  • request_record:请求结束的结果记录(执行完毕后走这里,分布式只走一次,JumpSpider 不走该函数)

注意:

  • request_failed 和 request_close 只有走其中一个
  • 不要的请求可直接抛出 DropRequestException 错误
  • 请求可以原地修改
  • request_error 可以判断错误进行修改,或者直接 return 新的请求,旧请求自动丢弃
  • request_close 可以判断响应是否符合预期,或者直接 return 新的请求,旧请求自动丢弃

提示:

  • 分布式爬虫时,设置开启以下两个选项,即可自动保存错误请求,并存放 redis,下次请求自动继续
  • REQUEST_FAILED_SAVE = True # 分布式时保存失败的请求(重试之后仍然失败的)
  • REQUEST_RETRY_FAILED = True # 分布式时启动重试失败请求

结构

import palp
from typing import Union
from palp import settings
from loguru import logger
from palp.network.request import Request

class RequestMiddleware(palp.RequestMiddleware):
    def request_in(self, spider, request) -> None:
        """
        请求进入时的操作

        :param spider:
        :param request:
        :return:
        """
        if settings.REQUEST_PROXIES_TUNNEL_URL:
            request.proxies = {
                'http': settings.REQUEST_PROXIES_TUNNEL_URL,
                'https': settings.REQUEST_PROXIES_TUNNEL_URL,
            }

    def request_error(self, spider, request, exception: Exception) -> Union[Request, None]:
        """
        请求出错时的操作

        :param spider:
        :param request: 该参数可返回(用于放弃当前请求,并发起新请求)
        :param exception: 错误的详细信息
        :return: [Request, None]
        """
        logger.error(exception)

        return

    def request_failed(self, spider, request) -> None:
        """
        超过最大重试次数时的操作

        :param spider:
        :param request:
        :return:
        """
        logger.warning(f"失败的请求:{request}")

    def request_close(self, spider, request, response) -> Union[Request, None]:
        """
        请求结束时的操作

        :param spider:
        :param request: 该参数可返回(用于放弃当前请求,并发起新请求)
        :param response:
        :return: [Request, None]
        """
        return

    def request_record(self, spider, record: dict) -> None:
        """
        请求结果记录(在 spider 结束时调用)

        :param spider:
        :param record:
        :return:
        """
        logger.info("{} 执行完毕,请求量统计:{}", spider.spider_name, record)

设置

注意:这里的 1 代表顺序

REQUEST_MIDDLEWARE = {
    1: "middlewares.middleware.RequestMiddleware",
}

添加代理

注意:这里基于默认的 ResponseDownloaderByRequests 请求器(requests)

class RequestMiddleware(palp.RequestMiddleware):
    def request_in(self, spider, request) -> None:
        """
        请求进入时的操作

        :param spider:
        :param request:
        :return:
        """

        # 给所有 url 都添加代理
        if settings.REQUEST_PROXIES_TUNNEL_URL and not request.proxies:
            request.proxies = {
                'http': settings.REQUEST_PROXIES_TUNNEL_URL,
                'https': settings.REQUEST_PROXIES_TUNNEL_URL,
            }

        # 指定域名添加代理
        allow_domains = ['xxx']

        if settings.REQUEST_PROXIES_TUNNEL_URL and not request.proxies:
            if request.domain in allow_domains:
                request.proxies = {
                    'http': settings.REQUEST_PROXIES_TUNNEL_URL,
                    'https': settings.REQUEST_PROXIES_TUNNEL_URL,
                }