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

Scrapy Spider中parse更新的类变量在parse_contents中无法读取最新值怎么办

问题根因
  • Scrapy 基于异步并发模型工作,请求的发起、响应返回、回调执行没有严格的顺序保证。如果把线程对应的元数据存在Spider实例的共享属性里,循环遍历线程列表时,循环会快速把所有请求全部提交给调度器,在这些请求的回调真正执行前,共享属性会被循环逐次覆盖,最终所有回调读到的都是循环最后一次写入的值,根本和当初发起请求时对应的线程不匹配。
  • 把 PostsItem 实例直接定义为类变量属于典型错误:所有请求流程共享同一个Item对象,后续的属性赋值会直接覆盖之前存的内容,最终产出的Item全是最后一次循环修改的结果,不存在多线程内容的区分。
修复方案

Scrapy 为每个Request对象提供了独立的meta属性,用来绑定和单个请求一一对应的上下文数据,完全不会出现并发覆盖的问题。搭配每次生成新的独立Item实例,即可彻底解决值不匹配的问题。

核心改动点

  • 删除所有用来跨回调传递数据的Spider类/实例共享变量(包括post_subject_last_message_date、total_posts、类级别的post_item)。
  • 在parse方法里构造每个线程详情页的请求时,把当前遍历到的线程的最后消息时间,通过meta参数绑定到对应请求上。
  • 在parse_contents回调里,直接从当前响应对应的请求元数据里取出绑定好的线程时间,每次生成帖子条目时都新建独立的PostsItem实例,不复用共享对象。
  • 后续如果在线程详情页内翻页构造请求,直接把原请求的meta整体透传即可保证元数据一致。

修复后的核心代码

class SpiderSpider(scrapy.Spider):
    name = 'posts'
    page_count = 1
    forum_count = 0

    start_urls = [
        'https://www.dcurbanmom.com/jforum/forums/show/32.page'
    ]

    def parse(self, response):
        for next_forum in response.xpath('//span[@class="topictitle"]'):
            next_forum_link = next_forum.xpath('.//a/@href')
            next_forum_url = response.urljoin(next_forum_link.extract_first())

            last_message = next_forum.xpath('.//ancestor::td[1]/following-sibling::td[4]/span/text()').get()
            # 把线程元数据绑定到当前请求,和请求生命周期完全绑定
            yield scrapy.Request(
                url=next_forum_url,
                callback=self.parse_contents,
                meta={"thread_last_message_date": last_message}
            )

    def parse_contents(self, response):
        # 从当前请求绑定的元数据中读取对应线程的信息
        current_thread_last_date = response.request.meta["thread_last_message_date"]
        all_posts = response.xpath('//table[@class="forumline"]//tr')
        post_text = ""

        for post in all_posts:
            post_text_response = post.xpath(
                ".//div[@class='postbody']/br/following-sibling::text()[1] | 
                 .//div[@class='postbody']/br/following-sibling::a[1]/text() | 
                 .//div[@class='postbody']/text() | 
                 .//div[@class='postbody']/a/text()"
            )
            if len(post_text_response.getall())>0:
                post_text = "".join(re.sub('\r','',x) for x in post_text_response.getall()).strip()
            
            if not bool(re.search(r'^\s*$', post_text)):
                # 每次新建独立的Item实例,避免共享对象被覆盖
                current_item = PostsItem()
                current_item['post_message'] = post_text
                current_item['post_subject_last_message_date'] = current_thread_last_date
                post_text = ""
                yield current_item

        # 线程内翻页的请求构造示例:透传元数据保证值正确
        # next_thread_page_url = response.urljoin(下一页链接)
        # yield scrapy.Request(
        #     url=next_thread_page_url,
        #     callback=self.parse_contents,
        #     meta=response.request.meta
        # )

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.01 21:54:05