Python爬虫中,网络获取数据快而本地写入慢,应该使用什么数据结构来缓冲?

面试遇到的问题
Python爬虫中,网络获取数据快而本地写入慢,应该使用什么数据结构来缓冲?

15 回复

丢缓存慢慢写


用队列(queue.Queue)就行,这是典型的生产者-消费者模型。

爬虫线程(生产者)快速抓取数据后,直接扔进队列,然后立刻去抓下一个。另一个专门的写入线程(消费者)从队列里慢慢取数据,按自己的节奏写入本地文件或数据库。这样网络请求就不会被慢速的I/O操作阻塞。

下面是个简单示例,用了标准库的queuethreading

import queue
import threading
import time
import requests
from bs4 import BeautifulSoup

class BufferedCrawler:
    def __init__(self, buffer_size=100):
        self.data_queue = queue.Queue(maxsize=buffer_size)
        self.stop_flag = False

    def fetch_data(self, url):
        """生产者:模拟快速抓取数据"""
        try:
            # 模拟网络请求
            # response = requests.get(url)
            # soup = BeautifulSoup(response.content, 'html.parser')
            # data = soup.title.string if soup.title else 'No Title'
            time.sleep(0.1)  # 模拟网络延迟
            data = f"Data from {url}"
            return data
        except Exception as e:
            return f"Error fetching {url}: {e}"

    def producer(self, url_list):
        """生产者线程函数"""
        for url in url_list:
            if self.stop_flag:
                break
            data = self.fetch_data(url)
            # 如果队列满,这里会阻塞,直到消费者取走数据
            self.data_queue.put(data)
            print(f"Produced: {data}")
        # 发送结束信号
        self.data_queue.put(None)

    def consumer(self, output_file):
        """消费者线程函数:慢速写入"""
        with open(output_file, 'w', encoding='utf-8') as f:
            while True:
                # 如果队列空,这里会阻塞,直到生产者放入数据
                item = self.data_queue.get()
                if item is None:  # 结束信号
                    self.data_queue.task_done()
                    break
                # 模拟慢速写入
                time.sleep(0.5)
                f.write(item + '\n')
                f.flush()
                print(f"Consumed: {item}")
                self.data_queue.task_done()

    def run(self, url_list, output_file):
        """启动爬虫"""
        # 启动生产者线程
        producer_thread = threading.Thread(target=self.producer, args=(url_list,))
        # 启动消费者线程
        consumer_thread = threading.Thread(target=self.consumer, args=(output_file,))

        producer_thread.start()
        consumer_thread.start()

        producer_thread.join()
        consumer_thread.join()

if __name__ == "__main__":
    # 示例URL列表
    urls = [f"http://example.com/page{i}" for i in range(10)]
    crawler = BufferedCrawler(buffer_size=5)
    crawler.run(urls, "output.txt")

关键点:

  1. queue.Queue是线程安全的,不用自己加锁。
  2. 通过设置maxsize可以控制内存使用,防止数据积压。
  3. 生产者放None作为结束信号,消费者收到就退出。
  4. task_done()join()可以用来等待所有数据处理完成。

如果数据量很大或者要持久化缓冲,可以考虑用multiprocessing.Queue或者直接上消息队列(如RabbitMQ、Redis),但一般爬虫用线程队列就够了。

总结:用队列解耦抓取和写入。

中间加一个 redis

如果不是面试的话……把获取数据的频率放慢点

数据结构差别不大吧……用最简单的数组?多加几个硬盘应该更好使

看获取到的数据量,如果多无解的,除非降低爬虫爬取速度。少可以缓存队列啥的。

如果进来的数据量远大于能写入的,队列只能缓解。还是要想办法优化数据写入的速度才行。

网络 IO 比本地 IO 快?还真没碰到过。一般都爬取的时候多开点,扔队列里慢慢写倒是有的。

保存到数据库,开多线程写。
关数据结构什么事?

意思应该是这样,比如从网络上获取每一千份数据,写入一次本地数据,所以前者频率更高。可以使用双 buffer,一个满后,切换 buffer,将满的写入硬盘后清空,这样互不干扰。具体 buffer 是数组、队列还是更复杂的结构,要看具体数据格式。

多买点固态硬盘

说错了,
没看清楚,如果写比较慢,如果不管也可以实时写;
不过估计题主的意思是写的放在一起写, 那可以做一个 batch 写,每个一段时间写;其他时间写线程休眠即可.

生产者消费者模型,blockingqueue,redis 缓存,kafka 队列,要不加内存,要不加硬盘

是否可以这样理解这个题目:
有一个爬虫,本地采用数据结构 A 存数据,
由于从网络上获取数据的频率快,本地写入数据的频率慢,这里做一个假设:
十秒钟获取 1000 条数据,但是十秒钟才能插入一次数据这样的频率,
因此,在插入数据之前,这 1000 条数据采用数据结构 B 来保存
意味着必须批量插入到本地的数据结构 A 中,
试着思考一下这个需求:用什么数据结构好,也就是 A 和 B 应该是什么数据结构呢,什么数据结构实现了这种场景下最优时间复杂度和空间复杂度?


这个题目还真不好理解…

回到顶部