多起始URL下Python Scrapy脚本运行异常问题求助
问题原因与解决办法
问题核心原因
- Scrapy异步特性与共享Driver冲突:Scrapy是异步并发框架,多个请求会同时执行,但你在
__init__中初始化了单个全局ChromeDriver实例,所有请求共享这个driver。当第二个请求调用self.driver.get(response.url)时,会直接覆盖第一个请求正在操作的页面,导致第一个请求的后续滚动、分页查找等操作全部在错误页面执行,最终出现爬取页数错误、中途停止的异常。 - 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
相关产品推荐
相关产品推荐

