容器化爬虫部署:基于K8s的任务调度与自动扩缩容设计

摘要

随着业务复杂度提升,单纯依靠定时任务和手工扩缩容已无法满足高并发、实时性和资源利用效率需求。本篇文章比较了两种基于 Kubernetes 的容器化爬虫调度与扩缩容方案:一种是利用 Kubernetes 原生的 CronJob 与 Horizontal Pod Autoscaler(HPA);另一种是基于 KEDA(Kubernetes Event‑Driven Autoscaling)的事件驱动扩缩容。文章从调度灵活性、扩缩容粒度、实现难度、成本效率和生态成熟度五个维度进行对比,并给出完整的 YAML+Python 对比示例及推荐场景,帮助读者在不同业务场景下做出最佳选型。


1. 选型背景

在分布式爬虫项目中,常见需求包括:

  • 定时触发:夜间全量抓取或定时增量更新
  • 动态扩缩容:根据任务队列积压或业务高峰动态调度更多爬虫容器
  • 资源管控:控制并发爬取速率,避免目标站点封禁
  • 监控告警:对失败率、时延、资源使用进行实时监控

传统做法常借助单机定时脚本 + Docker Compose,或自行编写调度器。但随着爬取量和业务复杂度增长,出现以下痛点:

  1. 扩缩容延迟高:无法根据实时负载灵活伸缩,资源浪费或爬取缓慢。
  2. 监控耦合度高:自研监控难与容器平台打通。
  3. 维护成本大:需要额外开发和运维管理。

因此,引入 Kubernetes 原生能力或 KEDA 事件驱动扩缩容成为可选方案。


2. 技术对比维度

维度 CronJob + HPA KEDA Event‑Driven
调度灵活性 基于 Cron 表达式,适合固定时段触发 支持多种事件源(队列长度、Kafka、Prometheus 指标等)
扩缩容粒度 HPA 支持基于 CPU/内存等指标 支持自定义指标(队列长度、HTTP 请求数)
实现难度 配置相对简单,Kubernetes 原生,无额外组件 需部署 KEDA 控制器,多一层依赖
成本效率 只付 Kubernetes 成本 多部署 KEDA 组件,成本略增
社区生态成熟度 Kubernetes 官方,生态完善 KEDA 社区活跃,官方背书

3. 代码对比示例

3.1 方案 A:Kubernetes CronJob+HorizontalPodAutoscaler

3.1.1 Kubernetes 资源定义
yaml 复制代码
# cronjob-hpa.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: tianyancha-crawler
spec:
  schedule: "0 2 * * *"                  # 每天凌晨 2 点触发
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: crawler
            image: your-registry/tianyancha-crawler:latest
            env:
            - name: PROXY_HOST # 参考亿牛云代理 www.16yun.cn
              value: "proxy.16yun.cn" # 代理域名
            - name: PROXY_PORT
              value: "12345"              # 端口
            - name: PROXY_USER
              value: "16YUN"      # 用户名
            - name: PROXY_PASS
              value: "16IP"      # 密码
          restartPolicy: OnFailure
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: crawler-hpa
spec:
  scaleTargetRef:
    apiVersion: batch/v1
    kind: CronJob
    name: tianyancha-crawler
  minReplicas: 1
  maxReplicas: 5
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50        # CPU 利用率超过 50% 时扩容
3.1.2 Python 爬虫脚本(crawler.py
python 复制代码
# -*- coding: utf-8 -*-
import os
import requests
from bs4 import BeautifulSoup
import json

# ------ 参考亿牛云代理配置 www.16yun.cn ------
PROXY_HOST = os.getenv("proxy.16yun.cn")
PROXY_PORT = os.getenv("8100")
PROXY_USER = os.getenv("16YUN")
PROXY_PASS = os.getenv("16IP")
proxy_url = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
proxies = {"http": proxy_url, "https": proxy_url}

# ------ HTTP 请求头 & Cookie ------
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                  "(KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36",
}
cookies = {
    # 如果需要登录后 Cookie,可在此添加,如 {"_csrf_token": "xxx"}
}

def fetch_company_info(name):
    """根据公司名称抓取天眼查信息并返回 dict"""
    url = f"https://www.tianyancha.com/search?key={name}"
    resp = requests.get(url, headers=headers, cookies=cookies, proxies=proxies, timeout=15)
    soup = BeautifulSoup(resp.text, "lxml")

    # 示例解析逻辑(请根据真实页面结构调整)
    item = soup.select_one(".search-result-single")
    data = {
        "name": name,
        "credit_code": item.select_one(".credit-code").get_text(strip=True),
        "phone": item.select_one(".phone").get_text(strip=True),
        "legal_person": item.select_one(".legal-person").get_text(strip=True),
        "email": item.select_one(".email").get_text(strip=True),
        "registered_capital": item.select_one(".registered-capital").get_text(strip=True),
        "website": item.select_one(".website a")["href"],
        "established_date": item.select_one(".established-date").get_text(strip=True),
        "address": item.select_one(".address").get_text(strip=True),
        "description": item.select_one(".description").get_text(strip=True),
    }
    return data

def main():
    company_list = ["示例公司A", "示例公司B"]  # 可从文件、消息队列读取
    results = []
    for name in company_list:
        info = fetch_company_info(name)
        results.append(info)
    # 分类存储:根据注册资本大小分文件(示例)
    big = [r for r in results if float(r["registered_capital"].rstrip("万")) > 1000]
    small = [r for r in results if float(r["registered_capital"].rstrip("万")) <= 1000]
    with open("/data/big_company.json", "w", encoding="utf-8") as f:
        json.dump(big, f, ensure_ascii=False, indent=2)
    with open("/data/small_company.json", "w", encoding="utf-8") as f:
        json.dump(small, f, ensure_ascii=False, indent=2)

if __name__ == "__main__":
    main()

3.2 方案 B:KEDA 事件驱动扩缩容

3.2.1 Kubernetes 资源定义
yaml 复制代码
# crawler-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tianyancha-crawler
spec:
  replicas: 1
  selector:
    matchLabels:
      app: crawler
  template:
    metadata:
      labels:
        app: crawler
    spec:
      containers:
      - name: crawler
        image: your-registry/tianyancha-crawler:latest
        env:
        - name: PROXY_HOST
          value: "proxy.yiniuyun.com"
        - name: PROXY_PORT
          value: "12345"
        - name: PROXY_USER
          value: "your_username"
        - name: PROXY_PASS
          value: "your_password"
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: crawler-scaledobject
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: tianyancha-crawler
  minReplicaCount: 1
  maxReplicaCount: 10
  triggers:
  - type: prometheus                   # 基于 Prometheus 自定义指标
    metadata:
      serverAddress: http://prometheus-operated.monitoring.svc:9090
      metricName: crawler_queue_length
      threshold: "50"
      query: sum(queue_length{job="tianyancha"})

说明:将爬虫任务推入消息队列(如 Kafka/RabbitMQ)或对 Prometheus 指标埋点,KEDA 根据队列深度或自定义指标自动扩缩容。

3.2.2 Python 爬虫脚本(同 3.1.2,可复用)

脚本可与方案 A 完全一致,部署方式和扩缩容策略不同,无需调整业务代码。


4. 场景推荐

  • 固定定时全量抓取,资源消耗可预估:选择 CronJob+HPA。配置简单、易于运维。
  • 异步消息队列触发或高峰期秒级拓展:选择 KEDA。支持多种事件源,扩缩容更灵活。
  • 对运维成本敏感,已有 Prometheus 监控体系:CronJob+HPA 即可满足多数需求。
  • 需支持复杂业务触发(如 Kafka 消息、数据库表行数等):KEDA 更能"一站式"接入。

5. 结论

  • 方案 A(CronJob+HPA):上手快、依赖少,适合业务固定且可预估资源的定时爬取场景。
  • 方案 B(KEDA):扩缩容粒度更灵活,事件驱动,适合高并发、异步触发或对接多种指标源的场景。

在实际生产中,可根据任务触发方式、并发峰值、监控体系成熟度及运维成本综合评估,亦可混合使用:核心夜间全量跑 CronJob,增量或高峰实时跑 KEDA。

相关推荐
lichenyang4532 天前
Docker 学习笔记(四):Dockerfile,把项目打成自己的镜像
docker·容器
lichenyang4532 天前
Docker 学习笔记(三):Docker 网络、bridge、子网和容器互通
docker·容器
lichenyang4532 天前
Docker 学习笔记(二):docker run 的参数到底在控制什么?
docker·容器
运维开发故事4 天前
基于 Arthas 的多集群在线诊断系统设计与实现
kubernetes
Patrick_Wilson6 天前
从「改个端口」到 502:Next.js on k8s 的容器端口、Service 映射与 env 覆盖
docker·kubernetes·next.js
探索云原生7 天前
K8s 1.36 这个 GA 特性,把 initContainer 拉模型的 hack 干掉了
ai·云原生·kubernetes
云恒要逆袭7 天前
运行你的第一个Docker容器
后端·docker·容器
Java之美8 天前
一次k8s升级引发的DevicePlugin注册失败
云原生·kubernetes
程序员老赵8 天前
10 分钟部署 OpenCode:Docker 一键安装,浏览器打开就能用 AI 写代码(附完整命令与排错)
docker·容器·ai编程