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
相关产品推荐
相关产品推荐

