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

多起始URL下Python Scrapy脚本运行异常问题求助

问题原因与解决办法

问题核心原因

  1. Scrapy异步特性与共享Driver冲突:Scrapy是异步并发框架,多个请求会同时执行,但你在__init__中初始化了单个全局ChromeDriver实例,所有请求共享这个driver。当第二个请求调用self.driver.get(response.url)时,会直接覆盖第一个请求正在操作的页面,导致第一个请求的后续滚动、分页查找等操作全部在错误页面执行,最终出现爬取页数错误、中途停止的异常。
  2. allowed_domains配置不全:你的allowed_domains仅包含www.audimv.com,另外两个目标域名不在列表中,Scrapy会自动过滤这些域名下的请求,这也是部分URL无法正常爬取的原因之一。

解决办法

方案一:使用scrapy-selenium中间件(推荐)

这个中间件会自动为每个请求分配独立的ChromeDriver实例,彻底避免共享冲突,同时保留Scrapy的异步优势。

步骤1:安装依赖

pip install scrapy-selenium

步骤2:修改项目settings.py配置

# 启用Selenium下载中间件
DOWNLOADER_MIDDLEWARES = {
    'scrapy_selenium.SeleniumMiddleware': 800
}

# 配置Selenium参数
SELENIUM_DRIVER_NAME = 'chrome'
SELENIUM_DRIVER_EXECUTABLE_PATH = '/usr/local/bin/chromedriver'  # 替换为你的chromedriver实际路径
SELENIUM_DRIVER_ARGUMENTS = ['--headless']

步骤3:修改Spider代码

import scrapy
from scrapy_selenium import SeleniumRequest
from scrapy.selector import Selector
from time import sleep
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class UsedaudiSpider(scrapy.Spider):
    name = 'usedaudi'
    allowed_domains = [
        'www.audimv.com',
        'www.audioxnard.com',
        'www.montereyaudi.com'
    ]
    start_urls = [
        'https://www.audioxnard.com/used-inventory/index.htm',
        'https://www.audimv.com/used-inventory/index.htm',
        'https://www.montereyaudi.com/used-inventory/index.htm',
    ]

    def start_requests(self):
        for url in self.start_urls:
            yield SeleniumRequest(
                url=url,
                callback=self.parse,
                wait_time=2
            )

    def parse(self, response):
        driver = response.meta['driver']
        try:
            # 显式等待列表视图按钮出现再点击,替代固定sleep
            but = WebDriverWait(driver, 10).until(
                EC.element_to_be_clickable((By.XPATH, '//button[@aria-label="switch to List view"]'))
            )
            but.click()
        except:
            pass
        
        # 滚动加载页面
        for _ in range(10):
            driver.execute_script("window.scrollBy(0, 700);")
            sleep(0.25)
        
        sel = Selector(text=driver.page_source)
        listings = sel.xpath('//li[@class="box box-border vehicle-card vehicle-card-detailed vehicle-card-horizontal"]')
        
        for listing in listings:
            yr_brand = listing.xpath('.//span[@class="ddc-font-size-small"]/text()').extract_first()
            year = yr_brand.split(' ')[0] if yr_brand else ''
            brand = yr_brand.split(' ')[1] if yr_brand else ''
            
            model = listing.xpath('.//h2/a/text()').extract_first()
            model = model.strip() if model else ''
            base_model = model.split(' ')[0] if model else ''
            
            link = listing.xpath('.//h2/a/@href').extract_first()
            url2 = response.urljoin(link) if link else ''
            price = listing.xpath('.//span[@class="price-value"]/text()').extract_first()
            miles = listing.xpath('.//li[@class="odometer"]/text()').extract_first()
            engine = listing.xpath('.//li[@class="engine"]/text()').extract_first()
            awd = listing.xpath('.//li[@class="normalDriveLine"]/text()').extract_first()
            stock = listing.xpath('.//li[@class="stockNumber"]/text()').extract_first()
            
            # 提取经销商名称
            dealership = driver.current_url.replace('https://', '').split("/")[0].replace("www.", "").replace(".com", "")
            
            yield {
                'dealership': dealership,
                'year': year,
                'brand': brand,
                'base_model': base_model,
                'model_detail': model,
                'price': price,
                'miles': miles,
                'engine': engine,
                'awd': awd,
                'stock': stock,
                'link': url2,
            }

        # 处理下一页
        try:
            next_elem = WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.XPATH, '//li[@class="pagination-next"]/a'))
            )
            next_url = next_elem.get_attribute("href")
            yield SeleniumRequest(
                url=next_url,
                callback=self.parse,
                wait_time=2
            )
        except:
            self.logger.info('No more pages to load.')

方案二:为每个请求创建独立Driver(适合小量URL)

如果不想依赖第三方中间件,可以在每个parse请求中单独初始化Driver,处理完成后关闭。但这种方式会为每个请求启动一个Chrome进程,资源消耗较大,不适合大量URL爬取。

def parse(self, response):
    options = webdriver.ChromeOptions()
    options.add_argument('headless')
    driver = webdriver.Chrome(options=options)
    try:
        driver.get(response.url)
        # 后续页面操作、数据提取逻辑和原代码一致
    finally:
        driver.quit()  # 确保请求处理完成后关闭Driver

方案三:用锁控制Driver访问(放弃异步优势)

通过线程锁让同一时间只有一个请求操作Driver,但会把Scrapy变成同步执行,完全失去异步框架的效率优势,仅作为临时应急方案:

from threading import Lock

class UsedaudiSpider(scrapy.Spider):
    def __init__(self):
        options = webdriver.ChromeOptions()
        options.add_argument('headless')
        self.driver = webdriver.Chrome(options=options)
        self.driver_lock = Lock()

    def parse(self, response):
        with self.driver_lock:
            self.driver.get(response.url)
            # 后续页面操作、数据提取逻辑和原代码一致

额外优化建议

  • 用Selenium显式等待替代固定sleep:显式等待会等待目标元素出现后再执行操作,比固定时间等待更稳定,也能减少不必要的等待时间。
  • 增加反爬策略:可以随机调整滚动间隔、添加User-Agent池,避免被目标网站封禁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 02:47:06