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

Scrapy+Selenium异步爬取时asyncio.sleep()报无事件循环错误求助

解决Scrapy结合Selenium使用async/await时的RuntimeError: no running event loop错误

问题背景

我尝试用Scrapy结合Selenium,并用async/await语法爬取网站,代码可能不够优雅,但在get_lat_long_from_url()方法中调用asyncio.sleep()时遇到了RuntimeError: no running event loop错误。用asyncio.sleep()是为了等待一段时间,检查Selenium加载的URL是否发生重定向。补充说明:我是通过scrapy crawl命令运行脚本,不是直接执行python filename.py,希望代码在asyncio.sleep()执行时保持异步运行。

原因分析

Scrapy基于Twisted异步框架,和asyncio的事件循环模型不兼容。你手动调用asyncio.get_event_loop()和loop.run_until_complete()的方式,会和Scrapy自身的Twisted事件环冲突,导致没有可用的asyncio事件环,从而触发错误。另外,Selenium的API是同步的,直接在async函数里调用会阻塞Scrapy的异步流程,违背了异步的初衷。

解决方案

  • 不要在Scrapy回调中手动管理asyncio事件循环,改用Scrapy兼容的异步方式处理。
  • 将Selenium的同步操作包装到异步线程中,避免阻塞Scrapy的事件环。
  • 用asyncio.to_thread(Python3.9+)把Selenium的URL检查逻辑转为异步,或者用Twisted的defer机制实现延迟检查。

修改后的代码示例

import scrapy
import asyncio
import time

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class MySpider(scrapy.Spider):
    name: str = 'some_name'

    def __init__(self):
        self.options = Options()
        self.options.add_argument('--headless')
        # 注意:Selenium实例线程不安全,生产环境建议用线程池管理实例
        self.driver = webdriver.Chrome(options=self.options)

    def __del__(self):
        self.driver.quit()

    def parse(self, response):
        # 爬虫入口,示例调用parse2
        yield scrapy.Request(url='some_initial_url', callback=self.parse2, cb_kwargs={'state': 'initial_state'})

    def parse2(self, response, state):
        links = ['link1', 'link2', 'link3', 'link4', 'link5']
        state = city = 'some random value'

        # 直接yield Request,Scrapy会自动异步处理,无需手动创建asyncio任务
        for link in links:
            yield scrapy.Request(
                url=link,
                callback=self.parse3,
                cb_kwargs={'state': state, 'city': city}
            )

    async def parse3(self, response, state, city):
        # 同步代码逻辑
        link = response.url  # 假设此处获取需要检查的链接
        # 用asyncio.to_thread将同步Selenium操作转为异步,避免阻塞Scrapy事件环
        result = await asyncio.to_thread(self.get_lat_long_from_url, link)

        if result:
            # 处理重定向后的URL逻辑
            self.logger.info(f"重定向后的URL: {result}")

        # 返回Scrapy输出数据
        yield {'state': state, 'city': city, 'redirected_url': result}

    def get_lat_long_from_url(self, href: str):
        self.driver.get(href)
        
        total = 0 
        # 同步线程中用time.sleep替代asyncio.sleep
        while total <= 10:
            time.sleep(1)
            total += 1
            new_url = self.driver.current_url
            if new_url != href:
                break

        # 更推荐用Selenium内置等待替代手动循环,效率更高
        # wait = WebDriverWait(self.driver, 10)
        # wait.until(EC.url_changes(href))
        # new_url = self.driver.current_url

        new_url = self.driver.current_url
        return new_url

关键优化点说明

  1. 移除手动asyncio事件环管理:Scrapy的回调本身基于Twisted异步模型,直接yield Request即可让Scrapy异步处理任务,无需手动创建和运行asyncio事件环。
  2. 异步线程包装Selenium操作:用asyncio.to_thread将同步的Selenium代码放到单独线程执行,避免阻塞Scrapy的主事件循环。
  3. 线程安全注意事项:Selenium的WebDriver实例线程不安全,如果需要多线程调用,建议为每个线程单独创建实例,或用线程池统一管理。
  4. 优先使用Selenium内置等待:代码中注释的WebDriverWait是更高效的URL变化检测方式,能在URL变更时立即返回,无需等待满10秒。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 19:40:33