从v0到上线:一个双非学生的云原生爬虫优化实战

威武_山峰
2026-03-28 10:36
阅读 1692

说实话,写这篇文章的时候我还有点心虚。毕竟我才大二,靠着自学混进了这家创业公司做后端开发——没错,就是那种简历上写着“熟悉K8s”、实际连etcd都没碰过几次的野生选手。两个月前入职时,mentor看我用Vim写代码还调侃说:“现在年轻人还玩复古?”但谁让我在学校里穷得装不起JetBrains全家桶呢?不过现在回头看,Vim真香,特别是当你要在容器里临时改配置的时候。

事情的起因是上周产品经理甩过来的需求:“我们要做一个竞品价格监控系统,每天抓取几万个商品页面,数据要准、延迟要低、成本还得控制住。”我一听就头大——这不就是个爬虫嘛,但加了“云原生”和“高可用”两个buff之后,难度直接拉满。更绝的是,deadline定在下周三,理由是“老板要在投资人会议上展示”。

初版方案(v0):天真得可爱

第一天晚上,我吭哧吭哧撸了个Python脚本,用requests+BeautifulSoup,本地跑起来挺顺。我还得意地跟同事说:“这不就完了?”结果第二天就被运维小哥按在地上摩擦——“你这玩意儿怎么部署?单机跑?崩了咋办?日志在哪?”

于是v0诞生了:一个简单的Flask应用,挂了个Celery队列,用Redis做任务分发。代码结构大概是这样:

# v0_crawler.py
import requests
from bs4 import BeautifulSoup
from celery import Celery

app = Celery('crawler', broker='redis://redis:6379/0')

@app.task
def fetch_product(url):
    resp = requests.get(url, timeout=10)
    soup = BeautifulSoup(resp.text, 'html.parser')
    price = soup.select_one('.price').text
    return {'url': url, 'price': price}

本地测试没问题,一上K8s就翻车。Pod频繁OOMKilled,日志里全是ConnectionResetError,而且因为没做去重,同一个URL被反复抓取。最惨的是,某天半夜,目标网站突然加了Cloudflare,我的爬虫直接被ban,整个集群CPU飙到90%——就因为我没处理异常,任务无限重试。

当时真的想砸电脑。但转念一想,这不就是成长的机会吗?

ChatGPT不是万能药,但能救命

崩溃之际,我打开了ChatGPT(别笑,我们这种小公司可没预算买Copilot)。我问它:“K8s里运行高并发爬虫的最佳实践是什么?”它给了一堆建议:用Headless Chrome?不行,太重;用Scrapy?学习成本高;最后它提到一个关键点:分布式爬虫的核心不是抓得多快,而是如何优雅降级和自愈

这句话点醒了我。我开始重新设计架构:

  1. 任务调度层:用Kafka替代Redis,天然支持分区和回溯
  2. 执行层:每个爬虫Pod只负责单一站点,避免互相影响
  3. 代理池:集成免费代理IP轮换,绕过反爬
  4. 指标监控:暴露Prometheus metrics,比如成功率、响应时间

最关键的是,我学会了不要追求一次完美。先让系统跑起来,再迭代优化。这大概就是所谓“工程思维”吧——学校里没人教这个,全靠踩坑悟出来。

架构升级:拥抱云原生

第二周,我推翻了v0,搞了个叫crawler-v1的新版本。核心思想是:把爬虫当成微服务来治理

1. 任务分片与状态管理

我用Kafka的topic分区来实现任务分片。比如jd-products topic有8个分区,每个分区对应一个商品类目。这样即使某个类目被封,也不影响其他类目。

# crawler-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: crawler-jd
spec:
  replicas: 4
  template:
    spec:
      containers:
      - name: crawler
        image: my-registry/crawler:v1
        env:
        - name: KAFKA_TOPIC
          value: "jd-products"
        - name: SITE_NAME
          value: "jd"
        resources:
          limits:
            memory: "512Mi"
            cpu: "500m"

2. 动态代理与User-Agent轮换

反爬是最大痛点。我写了个简单的代理中间件:

# proxy_manager.py
import random

PROXIES = [
    "http://1.2.3.4:8080",
    "http://5.6.7.8:3128",
    # ... 从免费代理网站爬来的IP
]

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36...",
]

def get_random_proxy():
    return random.choice(PROXIES)

def get_random_ua():
    return random.choice(USER_AGENTS)

虽然简陋,但配合随机延时(time.sleep(random.uniform(1, 3))),成功率从40%提升到了75%。

3. 指标监控不能少

我在代码里加了Prometheus client:

from prometheus_client import Counter, Histogram

REQUEST_COUNT = Counter('crawler_requests_total', 'Total requests', ['site', 'status'])
REQUEST_DURATION = Histogram('crawler_request_duration_seconds', 'Request duration', ['site'])

def fetch_with_metrics(url, site):
    start = time.time()
    try:
        # ... 发起请求
        REQUEST_COUNT.labels(site=site, status='success').inc()
    except Exception as e:
        REQUEST_COUNT.labels(site=site, status='error').inc()
    finally:
        REQUEST_DURATION.labels(site=site).observe(time.time() - start)

然后在Grafana上建了个面板,实时看各站点的成功率。运维大哥看到后居然夸我:“小伙子,有点东西!”

性能对比:数字不会骗人

折腾一周后,我做了个简单对比:

指标 v0 (单机) v1 (K8s集群)
日抓取量 ~5k ~50k
平均成功率 42% 78%
内存占用/Pod N/A 380Mi
故障恢复时间 手动重启 < 30秒(K8s自愈)
运维复杂度 高(需盯日志) 低(看Grafana就行)

最爽的是,现在加新站点只需要:

  1. 创建新的Kafka topic
  2. 部署一个新Deployment(改个env变量)
  3. 在Grafana加个filter

完全不用动核心代码。这就是云原生的魅力啊!

开发心得:别怕从v0开始

回顾这段经历,我最大的感悟是:所有伟大的系统,都是从一个烂得不能再烂的v0开始的

在学校时,我总想写出“完美代码”,结果经常卡在设计阶段。工作后才发现,业务不等人,先跑起来再说。K8s给了我们快速试错的能力——Pod挂了?自动重建。配置错了?滚动更新回滚。这种安全感,是本地开发无法比拟的。

另外,别迷信工具。ChatGPT帮我打开了思路,但具体实现还是得自己动手。比如它建议用Headless Browser,但我评估后发现内存开销太大,果断放弃。技术选型没有银弹,只有权衡。

最后,感谢我的mentor没在我v0翻车时把我开了。也感谢那个“不讲武德”的产品经理,逼我两周内从爬虫小白进化成(伪)云原生工程师。


后记:昨天老板在会上演示了我们的监控系统,投资人问:“这数据准吗?”老板拍胸脯:“绝对准,我们有个大二实习生写的!” 全场哄笑,但我知道,这笑声里有认可。
(完)

注:本文所有代码均为简化示例,实际项目中还需处理Cookie、验证码、动态渲染等更复杂场景。另外,爬虫请遵守robots.txt,别干坏事!

评论 0

最热最新
暂无评论
威武_山峰Lv.1
0
影响力
0
文章
0
粉丝