使用Serverless与AI构建弹性爬虫:告别闲置资源与解析维护烦恼 做爬虫的人可能都经历过这种尴尬租了台云服务器跑采集任务8核16G的配置白天忙成狗晚上闲到发霉但账单一分不少。更难受的是目标网站页面一改版解析代码全部作废熬夜改完正则和XPath没几天又变了。后来我把整套采集链路迁到了Serverless架构上配合AI模型做解析和异常兜底才真正体会到什么叫“弹性”——流量来了瞬间扩容几百个并发实例流量走了资源自动归零费用和资源消耗完全跟着实际任务量走。这篇文章就把这套方案完整摊开来讲为什么爬虫是最适合Serverless的负载类型之一、AI在采集链路里到底扛哪些活、函数计算加API网关加触发器怎么搭一整套弹性采集管道、Python代码怎么写、并发模型怎么设计、成本账单怎么算才不亏。适合已经写过爬虫、但对Serverless部署还比较陌生的同学也适合正在纠结“爬虫并发设计到底选哪种方案”的人。1. 传统爬虫的痛点带宽闲置与维护成本才是真问题聊Serverless之前先把传统爬虫的几个核心痛点摆清楚。很多人一提爬虫就想到IP被封、反爬对抗但真正让我下决心迁移架构的其实是另外两件事。1.1 资源利用率永远在坐过山车爬虫任务是典型的突发型负载。凌晨三点目标网站访问量低采集速度可以拉满但服务器闲着也是闲着白天对方网站忙着你这边CPU和带宽用不上去反而要给服务器付全天的钱。我最早用的那台云主机一个月里平均CPU使用率没超过15%峰值倒是冲上过90%但为了那几分钟的峰值整个月的机器费都按峰值规格付了。这就是传统服务器模式最别扭的地方资源规格必须按峰值预留但大部分时间负载都在均值以下晃悠。Serverless模式下资源是跟请求量走的——函数没被调用就不占资源被调用了就按需扩容实例。理论上你可以同时跑几千个并发采集任务而平时没任务的时候账单几乎为零。1.2 页面一改版代码就得重新发版老式爬虫的解析逻辑是写死在代码里的CSS选择器、XPath路径、正则表达式每一条都是针对当前页面结构定制的。目标网站前端重构一次你就要改代码、测试、重新部署运维成本相当高。AI介入之后这个问题的解法明显变了解析规则从“写死的代码”变成“动态的提示词”HTML抓下来连同“提取标题、正文、发布时间、作者”这样的要求直接丢给大模型让模型返回结构化JSON。页面结构只要不是彻底改头换面基本不需要动代码。即便改头换面也只是调整一下提示词细节而已。1.3 项目一多维护成本线性上涨手里同时维护五六个采集项目的人应该深有体会每个项目一套运行环境、一套依赖、一个定时任务配置部署方式还不统一有的是systemd服务有的是Docker容器有的是crontab硬跑。哪天服务器重装了光恢复环境就能折腾一整天。Serverless把运行环境彻底抽象掉了。函数计算平台负责容器生命周期、依赖安装、自动扩缩容开发者只需要上传代码、配置好触发器剩下的事平台全管。项目之间的隔离天然存在——一个函数一个实例互不干扰升级哪个就单独发哪个。这三种痛点叠加在一起让我确信爬虫领域的下一步不是继续提高并发技巧而是换一个更适合的部署模型。2. AI在采集链路里的真实角色解析、决策、清洗先把话放在前面AI不是万能的尤其不是用来“智能绕过验证码”“智能反爬”的。把AI神话化是不少教程最爱干的事实际生产环境里这么搞分分钟被告到平台封号。我的使用经验是AI在采集链路里承担三类非常具体的活儿结构化解析、采集策略决策、脏数据清洗。2.1 用大模型做结构化提取替代手写解析规则这是我最先落地的场景也是性价比最高的一个。以前从商品页提取“名称、价格、库存、SKU”这些字段要用XPath逐条定位遇到反爬混淆的HTML还得上正则硬抠。现在逻辑简单多了from openai import OpenAI client OpenAI( api_keyyour-api-key, # 使用兼容OpenAI协议的通用大模型API base_urlhttps://your-llm-endpoint, ) prompt f 你是一个HTML结构化提取器。 从下面的HTML片段中提取商品信息只返回JSON {{ title: 商品标题, price: 99.9, stock_status: in_stock|out_of_stock, spec: 关键规格 }} HTML片段如下 {html_text} resp client.chat.completions.create( modelyour-model-name, messages[ {role: system, content: 你是严格遵循输出格式的数据提取助手。}, {role: user, content: prompt} ], temperature0, max_tokens512, ) parsed json.loads(resp.choices[0].message.content)关键在于temperature必须设成0否则模型会发挥创造力把价格字段给你整出“约100元”“可能99.9”这种人类友好但程序没法入库的结果。实测中大部分页面结构变了也无需改代码容错率比固定选择器高太多了。2.2 AI辅助增量采集与去重决策爬虫跑久了最大的账单其实不是服务器费用而是重复抓取。同一个URL今天抓一遍明天抓一遍内容又没变浪费带宽不说还平白增加被封风险。现在我的做法是用向量化把每个采集过的页面正文转成向量存储新页面抓下来后先算相似度超过阈值就判定为“重复内容”直接跳过。向量化这一步也可以直接调通用模型接口比如把正文前500字发给模型拿embedding但更轻量的做法是用本地的text2vec或sentence-transformers。对小团队来说直接用现成embedding API反而省事。2.3 用AI做异常页面识别目标网站偶尔会返回验证页、防爬提示页、404页、登录跳转页这些页面的状态码往往还是200传统规则检验很难第一时间发现。AI方案是抽几个损样页面的DOM特征喂给模型让它学会区分“正常商品页”和“非目标页面”。其实就是一个二分类问题不需要多复杂的模型效果却出奇地好——尤其在整站套了统一反爬模板的场景下。3. 无服务器采集架构怎么搭函数计算、API网关与触发器的分工架构听起来高大上拆开其实就是几个云产品拼乐高。核心由三块组成函数计算做执行单元API网关做HTTP入口触发器做定时或事件驱动。下面用一张表先把职责说清楚。组件职责类比函数计算运行Python采集代码、AI解析代码流水线上的工人有活就干没活就休API网关将抓取函数暴露成HTTP接口供外部调用工厂大门所有来料都从这里进定时触发器按cron规则定期触发采集任务闹钟到点就叫工人起来干活消息队列解耦URL发现与内容抓取削峰填谷车间之间的传送带3.1 数据流设计从URL列表到结构化数据的链路完整的数据流长这样种子URL入队运营在管理后台粘贴一批目标URL写入消息队列。消息触发采集函数消息队列里的每一条消息触发一次函数调用函数负责爬取页面HTML。AI解析入库爬到的HTML直接传给大模型做结构化提取结果写进数据库。发现新链接递归入队解析出的新URL再推回队列形成自动发现链路。这套流程里最妙的点是每一条URL对应一次独立的函数调用天然实现了请求级别的并发隔离。传统多线程爬虫需要自己管理线程池、信号量、队列状态在Serverless架构里统统不需要——消息队列天然就是任务队列函数天然就是执行单元。3.2 为什么说消息队列是关键粘合剂如果不加消息队列直接用API网关同步调用函数会有两个问题一是同步调用的超时时间有限遇到慢网站容易超时二是没有重试机制失败的消息直接丢了。加了消息队列之后函数消费消息失败可以配置自动重试重试仍失败可以进入死信队列方便排查。这种可靠性是裸API网关很难提供的。我在实际项目里用的是云厂商提供的事务消息队列用起来和RabbitMQ很像但不用自己运维集群。3.3 分层架构的好处每一层都可以独立扩展最怕做成“一个函数一把梭”的架构。把URL发现、列表页抓取、详情页解析、数据清洗拆成不同的函数各自设置不同的内存规格和超时时间才能真正发挥Serverless的弹性优势。列表页请求量大但耗时短给256MB内存够用AI解析函数耗时较长给512MB甚至1GB避免OOM。分层之后某层升级不影响其他层排查问题也不用在一坨代码里大海捞针。4. 落地实操一个可运行的Python AI采集函数理论聊完接下来是真正能抄作业的部分。我用阿里云函数计算FC做示例——腾讯云和华为云的函数计算产品操作大同小异。本地跑通逻辑部署到云端时只需要把入口函数改成云平台要求的格式。4.1 项目结构ai-crawler/ ├── main.py # 云函数入口 ├── collector.py # 采集核心逻辑 ├── parser.py # AI解析封装 ├── config.py # 配置项 ├── requirements.txt # 依赖清单 └── serverless.yml # 部署配置Serverless Framework语法4.2 核心代码示例# collector.py import requests from requests.adapters import HTTPAdapter SESSION requests.Session() SESSION.mount(https://, HTTPAdapter(pool_connections20, pool_maxsize50)) HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } def fetch_html(url: str, timeout: int 10) - str: try: resp SESSION.get(url, headersHEADERS, timeouttimeout) resp.raise_for_status() return resp.text except requests.RequestException as e: raise RuntimeError(ffetch failed: {url} - {e}) from e# parser.py import json from openai import OpenAI client OpenAI( api_keyyour-api-key, base_urlhttps://your-llm-endpoint, ) EXTRACT_SYSTEM_PROMPT 你是严格遵循输出格式的数据提取助手。 def parse_html_with_ai(html_text: str, schema: dict) - dict: prompt f 从下面的HTML中提取信息只返回JSON对象不要返回其他内容。 提取字段定义{json.dumps(schema, ensure_asciiFalse)} HTML内容 {html_text[:8000]} resp client.chat.completions.create( modelyour-model-name, messages[ {role: system, content: EXTRACT_SYSTEM_PROMPT}, {role: user, content: prompt} ], temperature0, max_tokens1024, ) raw resp.choices[0].message.content # 兼容模型偶尔在JSON外面包json的情况 raw raw.strip() if raw.startswith(): raw raw.split(\n, 1)[1].rsplit(, 1)[0] return json.loads(raw)# main.py 云函数入口 import json from collector import fetch_html from parser import parse_html_with_ai SCHEMA { title: 文章标题, author: 作者, publish_time: 发布时间, content: 正文内容, category: 文章分类, } def handler(event, context): # 从消息队列事件中取URL url json.loads(event)[url] html fetch_html(url) # 简单内容长度校验防止被反爬页坑到 if len(html) 500: return {status: skip, reason: page too short} data parse_html_with_ai(html, SCHEMA) data[url] url data[crawled_at] context.start_time # 实际项目中在这里写数据库 print(json.dumps(data, ensure_asciiFalse)) return {status: ok, data: data}部署到函数计算之后控制台创建一个“消息队列触发器”关联到你的队列Topic。每推送一条消息到队列函数就会被自动调用一次并行度由平台自动调节。4.3 内存与超时怎么配云函数最容易被忽视的配置就是内存规格。很多人习惯用默认的128MB结果爬大页面时直接OOM进程被杀连日志都没来得及打。内存规格基本可以按“对方页面大小解析中间变量余量”来估常规详情页256MB足够AI解析函数建议512MB起步。超时时间同样要单独掂量。函数计算默认超时往往只有几秒对采集任务明显不够。我通常把纯抓取函数设为30秒AI解析函数设为120秒。设置太短容易被强制杀死设置太长又会占用资源配额中间值需要随手头任务调整。4.4 A/B对比同一套代码本地跑与云端跑的差异本地跑爬虫只要requests库能装上、网能连通基本就行。云端跑要多考虑几件事函数计算实例的临时磁盘是有限的有些平台默认只有512MB每次实例冷启动时依赖需要加载requests和openai这类库建议放到层Layer里否则冷启动时间会明显变长环境变量里的数据库连接串、API密钥要在控制台配好别写死在代码里。5. 并发与弹性伸缩为什么不需要自己写线程池“爬虫并发设计到底哪个好”这个问题在Serverless架构下有了一个完全不同维度的答案你根本不需要自己设计并发模型让平台去处理。5.1 传统并发模型的天花板传统爬虫做并发绕不开三种姿势多线程、多进程、异步IO。每种都有各自的坑。多线程写起来简单但Python的GIL让CPU密集型任务几乎没法并行且线程间共享资源要加锁稍不留神就死锁。多进程能解决GIL问题但进程管理和进程间通信又是一摊子事。异步IO性能上限最高但代码里到处是await翻页、失败重试、请求频率控制这些逻辑混在一起新人接手简直是灾难。更关键的是单台服务器的并发上限是硬的。你写再多线程带宽、文件描述符、CPU核数就那么多天生有天花板。要上分布式爬虫的话还要自己去管节点调度、任务分发、状态同步运维复杂度直线上升。5.2 Serverless的并发模型并发不是写出来的是配出来的函数计算的并发模型本质上就是“丢消息就触发函数”每一条消息都能触发一个独立的函数实例。你向消息队列里丢1000条URL平台就拉起1000个实例并发执行丢一条就只跑一个。平台自动做实例复用和回收完全不占用你的技术精力。这套模型的实际表现如何我压过一轮数据一次往队列塞了3000条URL函数计算大概用了不到两分钟就全部处理完期间并发实例数最高冲到328个。同样是3000条URL我在物理机上用多线程跑跑了一整夜才跑完受限于对方网站的速度限制和本机资源。虽然这种对比不够严谨但也说明弹性扩容在突发采集场景下的优势确实明显。5.3 注意函数计算平台的并发上限平台不会真的让你无限并发每个账号默认有并发实例数上限。比如常见默认值是100或300超出后新的请求会排队或者直接丢弃。跑大批量任务之前一定要先去控制台确认上限值不够就去提配额工单扩容。另外目标网站的承受能力才是真正的瓶颈并发突然拉满容易触发对方防火墙导致整个IP段被拉黑。所以我在队列消费端会做一个小技巧不用一下子把所有URL全投进队列而是分批推入每批500条等上一批处理完再推下一批。5.4 消息队列作为限流器的巧用其实消息队列还能当限流器用。函数消费消息的速度就是你的实际抓取速度那么通过调整“每次从队列拉取的消息数量”和“函数实例数上限”就能平滑控制对目标站的请求速率比在代码里写time.sleep()优雅得多而且不会在空闲轮询时浪费资源。6. 成本账单怎么算冷启动、超时与内存规格的取舍Serverless的计费模式比其他云产品复杂一些一句话概括就是“按实际资源使用量计费”但要省钱底层逻辑说清楚还是有门道的。6.1 计费公式到底是什么主流函数计算平台的计费由两部分组成调用次数费用每次函数被触发计一次费每百万次约几块钱。资源使用费用内存规格乘以实际执行时长按GB-秒计费。举个例子假设你的函数配置了512MB内存平均每次执行2秒执行了100万次那么资源使用量就是512MB / 1024 × 2秒 × 1,000,000 1,000,000 GB-秒。按主流云厂商的定价粗算100万次调用加上对应的资源费用整体费用大致在几十元到一百多元这个区间。如果用传统服务器跑同样的任务量一台4核8G的云主机一个月要几百块而且这还是不眠不休全天候开机的情况。也就是说只要任务不是7×24小时满载跑Serverless在成本上几乎必然胜出。6.2 内存规格不是越小越省钱这里有个挺反直觉的点内存配置太小执行时间拖长总费用反而可能更高。函数计算的执行时长和内存并非完全线性关系很多任务受CPU配额影响——内存越大配套的CPU算力也越强执行速度越快。以AI解析函数为例512MB下跑一次约2秒256MB下可能要跑4秒两者的资源费用相同但前者用户体验好得多。超过临界点后执行时间不再缩短内存再加大就纯粹是浪费钱。6.3 冷启动费用与预置并发冷启动是Serverless的老话题。一个函数实例从零启动到加载完Python运行时和依赖往往需要几百毫秒甚至几秒这段时间也要计费。高并发情况下成百上千个实例同时冷启动会产生一份不小的额外成本。应对方案有两种一是把依赖打进层里减小部署包体积加快实例启动速度二是对时延敏感的任务启用预置并发让平台提前拉起指定数量的实例等着接活。预置并发需要额外付费但如果任务调用量有规律性波峰这笔钱花得相当值。6.4 警惕死循环和失控重试的账单Serverless自动扩缩容是把双刃剑。如果代码里有个bug导致函数无限重试或者在while循环里反复调用自身账单会以肉眼可见的速度飙升。我给所有函数都加上了最大执行次数保护消息队列的重试策略也限制在三次以内宁可让任务失败进入死信队列也不能让它烧钱烧到没边。7. 上线之后被问得最多的几个问题讲了这么多架构和代码的“正道”最后把大家在实践里最容易踩到的坑集中梳理一遍。这些从生产环境里挤出来的经验比一整套架构图还值钱。7.1 函数计算出口IP是动态的容易被目标网站限制Serverless环境下每次函数调用拿到的出口IP可能都不太一样而且常常来自云厂商IDC的IP段。目标网站的防护逻辑很容易识别出这种特征于是直接给整个IP段上威胁情报。我的处理方案用轮换代理池做固定出口。在采集函数里集成一个代理池SDK每次请求前动态获取一个可用代理IP把函数计算的出口IP隐藏起来。代理池本身是按量付费的成本不高但稳定性提升非常明显。如果目标网站不反爬直接用函数计算默认出口也能跑但最好还是先在代码里打印一下本机出口IP确认一下来源。7.2 一次性抓取超大页面函数执行超时怎么办有的页面光HTML就1MB以上AI模型上下文窗口放不下函数超时时间也不够。这情况不能硬着头皮在同一个函数里面处理。解决方案是对大页面做拆分先用快速函数把整页HTML拉下来存到对象存储然后按DOM树节点分段切割切成多个小块后分别丢给AI解析函数。每块负责提取一种类型的字段最后汇总合并结果。这种拆分思路本质上就是把“一个大任务”拆成“一堆小任务”每个小任务都符合Serverless的轻量执行模型。7.3 日志是排查问题的唯一线索一定要结构化云端实例的调试体验和本地方案完全不一样不能随时print看结果也不方便打断点。任何上线的采集函数日志一定要输出成结构化JSON带上request_id、url、状态码、耗时几个关键字段。这几个字段在控制台的日志检索里可以直接做关键词过滤否则出问题时面对的是几千行无差别字符串排查一个报错能让你怀疑人生。7.4 幂等性设计同一批消息重复投递怎么办消息队列本身就提供了“至少一次”的投递保证也就是说同一个URL可能被函数处理两遍甚至多遍。加上函数的自动重试机制重复采集是个大概率事件。应对办法是给每个URL生成一个唯一键写入数据库时按这个键做去重。这样哪怕同一URL被处理十遍入库也只会有一条记录。顺手再维护一个URL指纹表记录每次采集的HTML哈希值如果哈希一致就直接跳过能省下不少AI调用费。7.5 数据合规是底线不是可选项把这一节放在最后不是因为它不重要恰恰是因为它最重要。做采集项目数据合规律必须刻进骨子里只采公开数据不碰需要登录才能看的内容遵守robots.txt的约定虽然它没有法律强制力但能体现采集者的态度不采集个人隐私数据包括但不限于手机号、身份证号、家庭住址、具体定位等尊重目标网站的版权声明商用前确认数据使用边界。Serveless架构让你技术上跑得很快但技术上的“能”不代表规则上的“可以”。踩过线代价真的不是账单能衡量的。我自己在实际操作中的体会是把爬虫迁到Serverless上前两周会特别不习惯——不写线程池心里发虚不手动看服务器监控心里没底。但跑顺之后是真的省心运维变少了费用明细更透明了AI解析带来的维护成本下降也很可观。如果你现在正被传统爬虫的闲置资源、频繁改版和运维负担折磨这套架构值得花一个周末试试。需要提醒的是所有云厂商的控制台操作逻辑大同小异先选一家熟悉的上手跑通后再改配置成本就很低了。