Python 多线程爬虫停止条件如何设置?

多线程爬虫,如何在特定的深度让线程停止呢?
每个线程目前爬取页面后,还会从页面中抓取新的 url 入队
Python 多线程爬虫停止条件如何设置?

8 回复

可以每次开新的线程的时候传入一个变量并+1,记录当前的深度,达到一个阈值就不要再开新的线程。


核心思路: 用队列控制任务流,主线程监控队列空 + 所有工作线程空闲作为停止信号。

具体实现:

  1. queue.Queue 存放待爬取的 URL。
  2. 工作线程从队列取 URL 执行,新发现的 URL 再放回队列。
  3. 主线程通过 queue.join() 等待所有任务完成,并设置线程为守护线程或发送停止信号。

代码示例:

import threading
import queue
import requests
from urllib.parse import urljoin
from bs4 import BeautifulSoup

class ThreadedCrawler:
    def __init__(self, start_url, max_threads=5):
        self.start_url = start_url
        self.max_threads = max_threads
        self.base_url = '/'.join(start_url.split('/')[:3])
        self.visited = set()
        self.url_queue = queue.Queue()
        self.url_queue.put(start_url)
        self.workers = []
        self.lock = threading.Lock()

    def crawl(self):
        # 启动工作线程
        for _ in range(self.max_threads):
            t = threading.Thread(target=self.worker)
            t.daemon = True  # 设为守护线程,主线程结束即退出
            t.start()
            self.workers.append(t)

        # 等待队列清空
        self.url_queue.join()
        print("所有任务完成,爬虫停止")

    def worker(self):
        while True:
            try:
                url = self.url_queue.get(timeout=3)  # 设置超时避免永久阻塞
                if url not in self.visited:
                    self.process_url(url)
                self.url_queue.task_done()
            except queue.Empty:
                break  # 队列空且超时,线程退出

    def process_url(self, url):
        with self.lock:
            if url in self.visited:
                return
            self.visited.add(url)
        
        try:
            print(f"爬取: {url}")
            resp = requests.get(url, timeout=5)
            soup = BeautifulSoup(resp.text, 'html.parser')
            
            # 提取新链接(示例)
            for link in soup.find_all('a', href=True):
                new_url = urljoin(self.base_url, link['href'])
                if new_url.startswith(self.base_url) and new_url not in self.visited:
                    self.url_queue.put(new_url)
        except Exception as e:
            print(f"处理 {url} 时出错: {e}")

if __name__ == "__main__":
    crawler = ThreadedCrawler("https://example.com", max_threads=3)
    crawler.crawl()

关键点说明:

  • queue.join() 会阻塞直到队列中所有任务被标记为 task_done()
  • 工作线程设为守护线程(daemon=True)后,主线程结束时它们会自动退出。
  • 线程中的 queue.get(timeout=3) 设置超时,避免队列空时线程永久阻塞。

一句话总结: 用队列管理任务,靠 queue.join() 和守护线程实现自然停止。

如果我抓 2 层,那就是抓主页里的所有 url,然后这些 url 页面里的 url 再取出来抓下来,这个过程中 url 不断入队,我如何判断在什么地方停止呢?

downloder 接受 url 参数时顺便传入这个 url 的深度啊,downloader 吐 response 的时候把这个参数吐出来再

谢谢~刚想到了您的这种解决方案。python 里如果想效率高一些,url 去重用 url 做 md5 hash 放到 set 里; url 存储用什么方式呢?

维护一个 tasks 队列,tasks 队列存 task 类,深度,url,解析规则存在 task 里,
再维护一个线程池,线程池只从 tasks 中取 task,执行 task。

task 是一个线程池,深度和 url 存在 task 里是什么意思

回到顶部