Python requests与concurrent.futures多线程爬取时as_completed内打印不执行问题
问题排查与修复方案
核心问题原因
- 全局变量竞态:你的
scrape函数直接读写全局offers变量,多线程环境下未加锁的全局可变对象读写会触发竞态条件,轻则返回数据错误,重则导致线程阻塞、流程中断,这是后续不再打印total的主要原因。 - 错误的返回值逻辑:
scrape函数应该返回当前页解析得到的单页offer列表,你当前的代码直接返回全局offers,相当于每次result()拿到的是重复累加的全量列表,逻辑完全错误。 - 无异常捕获机制:
requests请求、bs4解析过程中如果出现超时、4xx/5xx状态码、DOM结构异常等问题,scrape会直接抛出异常,该异常会在result()调用时触发,没有捕获的情况下会直接中断as_completed循环,自然不会输出后续的total。
修复后代码示例
scrape函数修改
import requests from bs4 import BeautifulSoup def scrape(i): try: # 添加超时设置,避免无限等待 resp = requests.get(f'somepage.com/page{i}', timeout=10) # 校验请求状态,遇到4xx/5xx直接抛出异常 resp.raise_for_status() soup = BeautifulSoup(resp.text, 'lxml') # 替换为你自己的单页offer解析逻辑,仅返回当前页的offer列表 page_offers = [a['href'] for a in soup.select('a.offer-link')] # 仅打印当前页的长度,不触碰全局变量 print(len(page_offers)) return page_offers except Exception as e: # 异常兜底,出错返回空列表,不中断主流程 print(f"第{i}页爬取失败: {str(e)}") return []
主逻辑修改
import concurrent.futures offers = [] processes = [] # 替换为你的实际总页数 total_page = 20 with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor: for i in range(total_page): processes.append(executor.submit(scrape, i)) for future in concurrent.futures.as_completed(processes): # 单线程累加,不存在并发安全问题 offers += future.result() print('total:', len(offers))
额外说明
修复后每个线程完全独立处理单页逻辑,和全局变量无耦合,同时异常兜底保证哪怕单页爬取失败也不会中断整个流程,as_completed循环可以正常执行完所有任务,不会再出现后续不打印total的问题。
内容的提问来源于stack exchange,提问作者Maciej Miecznik
相关产品推荐
相关产品推荐

