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

Python线程类无法退出:多线程爬虫脚本无法响应Ctrl+C终止

解决threading爬虫Ctrl+C无响应问题

你的问题核心在于:主线程启动子线程后立即退出,不再处于活跃状态,无法接收SIGINT信号;同时KeyboardInterrupt异常只会发送给主线程,子线程无法捕获,导致按Ctrl+C后程序无法终止。

问题原因分析

  1. 主线程启动8个子线程后没有阻塞逻辑,直接结束运行,无法检测到信号。
  2. 子线程默认是非守护线程,主线程退出后子线程会继续运行,且子线程无法捕获主线程收到的KeyboardInterrupt。
  3. 原代码中在子线程里捕获KeyboardInterrupt的逻辑无效,因为该异常只会触发在主线程。

解决方案与改进代码

通过全局线程安全的停止事件让主线程控制子线程退出,同时让主线程保持活跃以接收信号:

import threading
import signal
import time

# 全局停止事件,用于通知所有爬虫线程终止
stop_event = threading.Event()

class Crawler(threading.Thread):
    def __init__(self, num):
        super().__init__()
        self.num = num
        self.links_list = ["url_1", "url_2", "url_3"]  # 替换为实际链接列表

    def run(self):
        try:
            self.crawl()
        except Exception as e:
            print(f"线程{self.num}运行异常: {str(e)}")
        finally:
            self.exit()
    
    def crawl(self):
        for url in self.links_list:
            # 每次循环前检查是否需要停止
            if stop_event.is_set():
                break
            try:
                # 模拟爬取操作,替换为实际逻辑
                print(f"线程{self.num}正在爬取: {url}")
                time.sleep(1)  # 模拟网络请求耗时
                # 获取页面、解析链接并添加到links_list的逻辑
                pass
            except AssertionError:
                print(f"线程{self.num}断言失败,终止爬取")
                break
    
    def exit(self):
        # 保存进度的逻辑,比如写入文件或数据库
        print(f"线程{self.num}保存当前进度并退出")

def handler(signum, frame):
    print("\n收到终止信号,正在停止所有爬虫线程...")
    stop_event.set()

if __name__ == "__main__":
    # 注册SIGINT信号处理函数
    signal.signal(signal.SIGINT, handler)
    
    crawlers = []
    # 创建并启动所有爬虫线程
    for i in range(8):
        crawler = Crawler(i)
        crawlers.append(crawler)
        crawler.start()
    
    # 主线程等待所有子线程完成后再退出
    for crawler in crawlers:
        crawler.join()
    
    print("所有爬虫线程已正常退出,程序结束")

关键改进点

  • 全局停止事件:用threading.Event()实现线程安全的终止通知,子线程在循环中定期检查该事件,收到停止信号后主动退出。
  • 主线程保持活跃:通过join()方法让主线程等待所有子线程结束,确保主线程处于活跃状态,能接收到Ctrl+C触发的SIGINT信号。
  • 异常处理优化:去掉子线程中捕获KeyboardInterrupt的无效逻辑,改用finally块保证exit()方法一定会执行,确保进度保存。
  • 明确终止流程:信号处理函数设置停止事件,通知所有子线程优雅终止,避免强制杀死线程导致数据丢失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 08:09:17