You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scrapy下载中间件返回响应致__del__不执行 无法关闭Selenium浏览器

问题:Scrapy对接Selenium的下载中间件返回Response时__del__方法不执行

现象描述

编写Selenium对接的Scrapy下载中间件时,会出现两种完全不同的运行表现:

  • 当process_request方法返回自定义的HtmlResponse对象时,控制台仅输出打印的1和爬虫解析到的<200 http://httpbin.org/>,中间件的__del__方法完全不执行,Chrome浏览器进程残留无法正常关闭。
  • 注释掉返回HtmlResponse的代码、让process_request返回None时,程序可以正常执行__del__方法,输出Browser close~并关闭浏览器。

原因说明

出现差异的核心逻辑原因有两点:

  • Python的__del__方法本身没有可靠的执行时机保障,它只会在对象的引用计数降到0、被垃圾回收时才会触发,解释器正常退出阶段、存在循环引用、对象被框架持有引用的场景下,都可能不会执行。
  • 当process_request返回自定义的HtmlResponse时,这个响应对象会沿着Scrapy引擎、爬虫的全链路流转,整个爬虫运行周期内,这条请求/响应的处理链会隐式持有对创建响应的中间件实例的引用,导致中间件对象的引用计数始终无法归零;加上Scrapy主流程退出阶段的清理逻辑优先级更高,就会出现爬虫全部跑完,__del__也没被触发的情况。
  • 当不返回自定义响应、process_request返回None时,Scrapy不会在后续处理链路中持有中间件的引用,运行结束后中间件对象引用计数清零,垃圾回收时就可以正常触发__del__。

可行的收尾方案

不要依赖__del__做资源清理,直接利用Scrapy内置的spider_closed信号,在爬虫完全运行结束时触发浏览器关闭逻辑即可,不管中间件是否返回自定义响应都能稳定执行。

修改步骤

  1. 引入Scrapy的信号模块
  2. 在中间件的from_crawler类初始化方法中,把自定义的浏览器关闭方法绑定到spider_closed信号
  3. 实现绑定的关闭方法,替代原来的__del__逻辑

对应的核心修改代码如下:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from scrapy.http import HtmlResponse
from scrapy import signals


class SeleniumMiddleware:
    """对接Selenium的下载中间件"""

    def __init__(self):
        self.browser = webdriver.Chrome()
        self.browser.maximize_window()
        self.wait = WebDriverWait(self.browser, 10)

    @classmethod
    def from_crawler(cls, crawler):
        # 初始化中间件实例
        middleware = cls()
        # 绑定爬虫关闭信号到浏览器关闭方法
        crawler.signals.connect(middleware.spider_closed, signal=signals.spider_closed)
        return middleware

    def process_request(self, request, spider):
        try:
            # 匹配需要Selenium访问的链接
            if request.url in spider.start_urls:
                self.browser.get(request.url)
                # 等待页面元素加载完成
                self.wait.until(EC.presence_of_element_located((
                    By.ID, 'operations-tag-HTTP_Methods')))
                page_text = self.browser.page_source
                print('1')
                return HtmlResponse(
                    url=request.url,
                    body=page_text,
                    encoding='utf-8',
                    request=request,
                    status=200
                )
        except TimeoutException:
            print('2')
            return HtmlResponse(url=request.url, status=500, request=request)

    def process_exception(self, request, exception, spider):
        print(f'Error: {exception}')
        return None

    def spider_closed(self):
        """爬虫运行结束时自动触发,关闭浏览器"""
        print('Browser close~')
        self.browser.quit()

注意:原有的__del__方法可以直接删除,在Web框架场景下依赖它做资源清理非常不可靠。

内容的提问来源于stack exchange,提问作者wananzuiqingfeng

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.24 20:45:08