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

Scrapy下载中间件中如何返回多次滚动后的HtmlResponse?

解决思路

Scrapy的DownloaderMiddleware的process_request方法只能返回单个Response对象或者None,没法像Spider的parse方法那样yield多个结果,所以你之前的两种尝试都走不通。给你两个可行的解决方案:

方案一:把滚动逻辑移到Spider中(推荐)

直接在Spider的parse方法里用Selenium控制滚动,每次滚动后直接处理当前页面的内容,这样可以灵活处理每次滚动后的page_source,还能直接yield解析出的数据或者后续请求。

示例代码:

import scrapy
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import logging

class YourSpider(scrapy.Spider):
    name = 'your_spider'
    start_urls = ['目标网址']

    def __init__(self):
        self.driver = webdriver.Chrome()  # 根据你的浏览器配置调整
        self.wait = WebDriverWait(self.driver, 10)

    def closed(self, reason):
        self.driver.quit()  # 爬虫结束时关闭浏览器,避免资源泄漏

    def parse(self, response):
        self.driver.get(response.url)
        
        for i in range(20):
            try:
                # 执行滚动操作
                self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
                logging.error(f'第{i+1}次滚动')
                # 等待页面内容更新(等待条件要根据页面实际情况调整)
                self.wait.until(EC.presence_of_element_located((By.XPATH, '//div/a[@role = "link"]')))
                time.sleep(2)
                logging.error(f'等待第{i+1}次滚动完成')
                
                # 获取当前页面源码并解析处理
                current_page_source = self.driver.page_source
                selector = scrapy.Selector(text=current_page_source)
                # 示例:提取数据并yield
                target_items = selector.xpath('//你的目标元素XPath').getall()
                for item in target_items:
                    yield {'content': item}

            except Exception as e:
                logging.error(f'第{i+1}次滚动失败: {str(e)}')
                break

方案二:在Middleware中收集所有滚动后的page_source,一次性传给Spider

如果一定要用DownloaderMiddleware,那可以在滚动过程中把每次的page_source收集到列表里,然后放到返回的HtmlResponse的meta字段中,Spider拿到后再遍历这个列表处理每个页面的内容。

修改后的Middleware代码:

from scrapy.http import HtmlResponse
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import logging

class SeleniumScrollMiddleware:
    def __init__(self):
        self.driver = webdriver.Chrome()
        self.wait = WebDriverWait(self.driver, 10)

    def process_request(self, request, spider):
        # 只处理标记了需要滚动的请求
        if 'scroll_needed' in request.meta:
            self.driver.get(request.url)
            page_sources = []  # 收集每次滚动后的页面源码
            
            for i in range(20):
                try:
                    self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
                    logging.error(f'scroll{i+1}times')
                    self.wait.until(EC.presence_of_element_located((By.XPATH, '//div/a[@role = "link"]')))
                    time.sleep(2)
                    logging.error(f'waiting scroll{i+1}times')
                    
                    # 保存当前页面源码到列表
                    current_source = self.driver.page_source
                    page_sources.append(current_source)
                    logging.error(f'get page_source{i+1}times')

                except Exception as e:
                    logging.error(f'scroll failed: {str(e)}')
                    break
            
            # 返回包含所有page_source的Response
            return HtmlResponse(
                body=self.driver.page_source,  # 这里的body仅作为占位,核心是meta里的列表
                url=request.url,
                encoding='utf-8',
                meta={'page_sources': page_sources}
            )
        return None

然后在Spider里这样处理:

def parse(self, response):
    page_sources = response.meta.get('page_sources', [])
    for source in page_sources:
        selector = scrapy.Selector(text=source)
        # 解析每个页面的内容并yield
        target_items = selector.xpath('//你的目标元素XPath').getall()
        for item in target_items:
            yield {'content': item}

注意事项

  • 等待条件要根据页面实际更新的元素调整,不要盲目套用固定XPath,确保等待的是滚动后确实会更新的内容。
  • time.sleep可以根据页面加载速度调整,或者用更智能的等待方式(比如等待元素文本变化)代替固定休眠。
  • 记得在爬虫结束时关闭Selenium的driver,避免资源泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 18:45:15