Python线程类无法退出:多线程爬虫脚本无法响应Ctrl+C终止
解决threading爬虫Ctrl+C无响应问题
你的问题核心在于:主线程启动子线程后立即退出,不再处于活跃状态,无法接收SIGINT信号;同时KeyboardInterrupt异常只会发送给主线程,子线程无法捕获,导致按Ctrl+C后程序无法终止。
问题原因分析
- 主线程启动8个子线程后没有阻塞逻辑,直接结束运行,无法检测到信号。
- 子线程默认是非守护线程,主线程退出后子线程会继续运行,且子线程无法捕获主线程收到的KeyboardInterrupt。
- 原代码中在子线程里捕获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
相关产品推荐
相关产品推荐

