AI大模型训练数据告急?用Redis+动态代理采集数据集

0 阅读5分钟

随着大语言模型(LLM)参数量飙升到万亿级别,高质量的公共数据已经被各家大厂“刮地三尺”。想要获取更垂直、更新鲜的行业数据,必须深入互联网的毛细血管。但在严苛的限制机制下,单机爬虫面临着算力瓶颈和极易被限制的死局。

破局的最优解只有一个:Redis分布式任务队列 + 多线程并发 + 动态代理IP池

为什么是 Redis?分布式爬虫的“最强大脑”

在分布式架构中,我们需要多台服务器(Worker)同时去抓取数据。这就引出了一个核心问题:如何保证大家不抓重复的网页?又如何把成千上万的URL分配给不同的机器?

这就是 Redis 发挥作用的地方:

  1. 任务分发(中央调度): 我们可以把 Redis 的 List 当作一个巨大的任务队列。一台主服务器(Master)负责把需要抓取的 URL 塞进队列,其他所有的爬虫服务器(Worker)都盯着这个队列,谁有空谁就去“抢”一个 URL 来抓。
  2. 极高的读写性能: Redis 基于内存操作,能够轻松扛住几万甚至十几万的并发读写,绝不会成为爬虫的瓶颈。

架构升级:多线程 + Redis + 动态代理

在这个架构中,每台 Worker 机器不仅要从 Redis 抢任务,还要在自己机器上开启多线程来最大化压榨 CPU 和网络带宽。同时,为了防止单台 Worker 触发目标网站的反爬策略,我们必须给每个线程挂上动态代理IP

下面是结合了 Redis 队列、Python concurrent.futures 线程池以及动态代理的实战代码:

import requests
import redis
import time
from concurrent.futures import ThreadPoolExecutor
from requests.exceptions import RequestException

# ==========================================
# 16YUN爬虫代理配置信息 (请替换为实际账户)
# ==========================================
PROXY_HOST = "proxy.16yun.cn"  # 代理服务器域名
PROXY_PORT = "31111"           # 代理服务器端口
PROXY_USER = "16YUNxxxx"       # 代理用户名 (16YUN开头)
PROXY_PASS = "YOUR_PASSWORD"   # 代理密码

# ==========================================
# Redis 配置信息
# ==========================================
REDIS_HOST = 'localhost'       # Redis服务器IP,实际分布式部署时填公网或内网IP
REDIS_PORT = 6379              # Redis端口
REDIS_QUEUE_NAME = 'crawler:url_queue' # 存放任务的队列名称

# 初始化 Redis 连接池
redis_client = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True)

def fetch_data(url):
    """
    Worker 线程执行的具体抓取任务
    每次请求都会通过亿牛云爬虫代理自动切换IP
    """
    proxies = {
        "http": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}",
        "https": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
    }

    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "Connection": "keep-alive"
    }

    try:
        print(f"[*] 线程启动,正在通过代理抓取: {url}")
        # 设置 timeout 防止死链卡住线程
        response = requests.get(url, headers=headers, proxies=proxies, timeout=10)
        
        if response.status_code == 200:
            print(f"[+] 抓取成功: {url} | 响应截取: {response.text[:50]}...")
            # 注意:实际业务中这里会把清洗好的数据存入 MongoDB 或 Elasticsearch
            return True
        else:
            print(f"[-] 抓取失败 {url},状态码: {response.status_code}")
            # 容错处理:抓取失败的 URL 可以重新塞回 Redis 队列末尾重试
            # redis_client.rpush(REDIS_QUEUE_NAME, url)
            return False

    except RequestException as e:
        print(f"[-] 请求异常 {url}: {e}")
        # redis_client.rpush(REDIS_QUEUE_NAME, url)
        return False

def worker_process():
    """
    Worker 节点的主进程:不断从 Redis 拉取任务,并派发给线程池执行
    """
    print("[-] Worker 节点启动,等待接收任务...")
    
    # 初始化线程池,根据服务器配置调整 max_workers(例如 10 到 50)
    with ThreadPoolExecutor(max_workers=5) as executor:
        while True:
            # blpop: 阻塞式弹出。如果队列为空,程序会在这里“睡觉”等待,直到有新任务,不会空耗 CPU
            task = redis_client.blpop(REDIS_QUEUE_NAME, timeout=0)
            
            if task:
                # task 是一个元组,task[0] 是队列名,task[1] 是取出的 URL
                url = task[1]
                # 将任务提交给线程池异步执行
                executor.submit(fetch_data, url)

def seed_master_urls():
    """
    模拟 Master 主节点:负责生产数据,将待抓取的 URL 推送到 Redis 队列
    """
    print("[-] Master 正在向队列推送任务...")
    # 模拟推送 10 个测试任务
    for i in range(1, 11):
        # 利用 httpbin 测试我们的代理IP是否生效
        url = f"https://httpbin.org/ip?task_id={i}"
        redis_client.rpush(REDIS_QUEUE_NAME, url)
    print("[-] 任务推送完成!")

if __name__ == "__main__":
    # -----------------------------------------------------------------
    # 注意:在真实的分布式环境中,Master 和 Worker 是运行在不同服务器上的两套代码。
    # 这里为了演示,我们放在同一个脚本中顺序执行。
    # -----------------------------------------------------------------
    
    # 1. 主节点推入初始 URL 种子
    seed_master_urls()
    
    # 2. 启动 Worker 节点(多线程开始疯狂拉取并抓取数据)
    worker_process()

技术难点拆解

  • 生产者-消费者模型: 这是一个经典的 Producer-Consumer 模式。seed_master_urls() 是生产者,负责发现并下发URL;worker_process() 是消费者。两者通过 Redis 的 blpoprpush 完全解耦。你可以随时增加或减少 Worker 服务器的数量,完全不需要改动代码。
  • 阻塞式队列 (blpop): 这是一个非常优雅的细节。传统的 pop 如果取不到数据会返回空,你需要写一个死循环加 time.sleep。而 blpop 在队列为空时会自动休眠线程,直到 Master 推入新数据,它会立刻被唤醒,最大程度节约了系统资源。
  • 隧道代理的双重保险: 为什么有了分布式还要代理IP?因为一台 16 核 32G 的 Worker 服务器,哪怕开 50 个线程,对外的公网出口依然只有一个 IP。一旦并发量上来,目标网站立刻就能识别出这台机器的异常。爬虫代理让每次请求在服务端被重定向到不同的真实 IP,配合线程池的高并发,可以说是“隐形战斗机群”。

结语

当你把单机爬虫重构为 Redis 分发 + 多线程并发 + 动态隧道代理 时,你就跨过了“爬虫新手村”的门槛。这套架构具备极强的横向扩展能力,能为你源源不断地输送高质量的 AI 训练语料。