Python多线程爬虫中线程假死问题如何解决?

这几天在搞 boss 直聘,全国数据.

写了个多线程爬虫, 晚上挂到服务器上爬虫速度杠杠滴, 第二天来看爬虫速度巨慢无比,

我是开的 300 线程,

查看服务器状态, cpu 内存, 带宽 非常正常,

然后网上查查资料 可能是 TCP 的端口占用 TIME_WAIT 太多, 导致服务器可用端口耗尽,

但是我测试了一下 10 个线程, 发现还是会变慢,

然后考虑到可能是线程假死了,,

代码大概: requests 请求全部加了 timeout ;

需要对数据库操作的方法都写了对 mysql 的连接; 更新,查找操作都加了线程锁;

while true 循环 break 加好了都

那么问题来了,大佬们,我怎么判断出是哪个步骤假死了,

如果判断不出来, 有没有操作 可以重启这个线程..

或者大佬们有没有遇到过这种情况,求指点


Python多线程爬虫中线程假死问题如何解决?

3 回复

多线程爬虫里线程“假死”通常是因为没处理好网络请求超时、资源竞争或者异常捕获。核心就两点:设置合理的超时和用队列管理任务。

下面这个例子用concurrent.futuresThreadPoolExecutor,配合队列和超时设置,能有效避免线程卡住:

import concurrent.futures
import requests
import queue
from threading import Lock
import time

class RobustCrawler:
    def __init__(self, max_workers=5, timeout=10):
        self.timeout = timeout
        self.task_queue = queue.Queue()
        self.lock = Lock()
        self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=max_workers)
        
    def add_task(self, url):
        self.task_queue.put(url)
    
    def worker(self):
        while True:
            try:
                url = self.task_queue.get(timeout=3)  # 队列获取超时
                if url is None:
                    break
                    
                try:
                    # 关键:设置请求超时
                    resp = requests.get(url, timeout=self.timeout)
                    with self.lock:
                        print(f"Success: {url} - Status: {resp.status_code}")
                except requests.exceptions.Timeout:
                    print(f"Timeout: {url}")
                except Exception as e:
                    print(f"Error: {url} - {str(e)[:50]}")
                    
            except queue.Empty:
                break
            finally:
                self.task_queue.task_done()
    
    def run(self, urls):
        for url in urls:
            self.add_task(url)
        
        # 提交任务
        futures = [self.executor.submit(self.worker) for _ in range(self.executor._max_workers)]
        
        # 等待队列清空
        self.task_queue.join()
        
        # 停止工作线程
        for _ in range(self.executor._max_workers):
            self.add_task(None)
        
        concurrent.futures.wait(futures)
        self.executor.shutdown()

# 使用示例
if __name__ == "__main__":
    urls = [
        "https://httpbin.org/delay/2",
        "https://httpbin.org/delay/5",
        "https://httpbin.org/status/404",
        "https://not-a-real-site.com",
        "https://httpbin.org/delay/8"
    ] * 2
    
    crawler = RobustCrawler(max_workers=3, timeout=6)
    crawler.run(urls)

关键点:

  1. 请求超时requests.get(timeout=)必须设置,这是防止网络阻塞的核心
  2. 队列超时queue.get(timeout=)避免线程空等
  3. 异常隔离:每个请求的异常单独捕获,不会影响其他线程
  4. 优雅退出:用None作为停止信号,避免线程无法结束

如果要用threading模块直接写,记得给每个线程加daemon=True或者设计明确退出机制。不过现在更推荐用concurrent.futures,它内置了更好的异常处理和线程管理。

简单说就是:设好超时,管好异常,别让一个请求卡死整个程序。


代码呢?

回到顶部