# 自建爬虫 vs 官方 API:成本、稳定性、风险一次算清(附 Python 源码)
每个做电商数据的团队都纠结过这个问题:接官方 API 要走审核、要等权限,看着就麻烦;自己写个爬虫,两周就能出数据,还不要钱。
这篇把这笔账彻底算清。先说结论:
**爬虫不是"省钱的方案",是把成本换了个形式——从"花钱"变成"花人 + 担风险"。** 算完三年总账,爬虫通常是官方 API 的 2~4 倍,而最大的一项成本是"养一个懂爬虫的人"。
但我也不会一边倒。爬虫有它合理的场景,第四节专门讲哪些情况该爬。先把账摆出来,你自己判断。
---
## 一、成本:把"看起来免费"的东西标上价
爬虫最大的迷惑性在于:**它的成本几乎全部是隐性的**。没有账单,没有报价单,全部体现在"某个人每个月花在它身上的时间"里。
逐项拆开:
**① 首次开发**。大多数团队的预估是"一周",实际是 2~4 周。因为真正花时间的不是写解析器(那确实只要两天),是处理长尾:懒加载、翻页截断、字段在不同商品页上的十几种变体。
**② 持续维护**。这是大头,也是最容易被漏算的。页面会改版——改成版式、改类目页结构、加一层前端渲染,平均每月 1~~2 次,每次修复 0.5~~2 天。算下来,**养一个爬虫需要 0.2~0.4 个人力,常年不停**。
**③ 反爬对抗的设备成本**。IP 代理池、请求频率控制、失败重试——合法合规的部分同样有真金白银的月费。
**④ 人的风险**。爬虫系统往往只存在于一个人的脑子里。这个人休假、离职,系统跟着停摆——而你甚至不知道它哪天停的(下一节讲这个)。
**官方 API 的成本**则几乎全部是显性的:接入人力(1~3 周,含等审核的并行期)、持续维护(平台接口变更频率远低于页面改版,且有公告期)、调用费用。
同一个需求(三平台商品 + 订单同步),我们按一名中级后端的全成本 3.5 万/月算三年账:
| 成本项 | 自建爬虫 | 官方 API |
| --------- | -------------------------- | ---------------------- |
| 首次开发 / 接入 | 2.6 万(3 周) | 3.5 万(1 人月,含审核并行期) |
| 持续维护 | **31.5 万**(0.25 人力 × 36 月) | 10.1 万(0.08 人力 × 36 月) |
| 代理 / 调用费 | 2.9 万(代理池月费) | 3.6 万(调用与服务费) |
| **三年合计** | **≈ 37 万** | **≈ 17.2 万** |
差距主要来自②:**API 的"维护"是读公告和改字段名,爬虫的"维护"是重新理解一个被改过的页面**。前者有文档,后者只有你的猜想。
这张账能算,是因为它有模型。下面的计算器你可以拿去代入自己的数字:
```python
# tco.py —— 三年总拥有成本计算器
# 参数按你们自己的实际情况改,模型不变
from dataclasses import dataclass
@dataclass
class Team:
"""团队参数"""
salary_month: float = 35000 # 一名相关岗位的全成本/月(工资 × 1.4 起步)
@dataclass
class CrawlerPlan:
"""爬虫方案:大部分成本是隐性的,所以要显式建模"""
build_weeks: float = 3.0 # 首次开发(现实值 2~4 周,别用 1 周)
maintain_fte: float = 0.25 # 常年维护人力占比(0.2~0.4,低于 0.2 通常意味着没人在管)
proxy_month: float = 800 # 代理与设备月费
months: int = 36
def cost(self, t: Team) -> dict:
# 按周折算:每月 21.75 个工作日,每周 5 天
build = t.salary_month / 21.75 * 5 * self.build_weeks
maintain = t.salary_month * self.maintain_fte * self.months / 12
infra = self.proxy_month * self.months
return {"首次开发": round(build, 1),
"持续维护": round(maintain, 1),
"代理与设备": round(infra, 1),
"合计": round(build + maintain + infra, 1)}
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
@dataclass
class ApiPlan:
"""官方 API:成本几乎全部显性"""
build_months: float = 1.0 # 含与审核期并行的开发
maintain_fte: float = 0.08 # 读公告、改字段名、处理限流
api_month: float = 1000 # 调用费 + 软件服务费
months: int = 36
def cost(self, t: Team) -> dict:
build = t.salary_month * self.build_months
maintain = t.salary_month * self.maintain_fte * self.months / 12
fees = self.api_month * self.months
return {"首次接入": round(build, 1),
"持续维护": round(maintain, 1),
"调用与服务费": round(fees, 1),
"合计": round(build + maintain + fees, 1)}
if __name__ == "__main__":
t = Team()
c = CrawlerPlan().cost(t)
a = ApiPlan().cost(t)
w = max(len(k) for k in c)
for k in c:
print(f"{k:<{w}} 爬虫 {c[k]:>8} 万 API {a[k]:>8} 万")
print(f"\n倍数:{c['合计'] / a['合计']:.1f}x")
```
跑出来就是上面那张表。**你团队的实际数字可能不同,但倍数关系通常落在 2~4 倍**——因为决定倍数的是"页面改版频率 ÷ 接口变更频率",这个比值对任何平台都在 10 倍以上。
> 有一种情况会让这个计算器失真:你只爬**一个**页面、只用**三个月**。那爬虫确实便宜。计算器的隐含前提是"长期、多页面、商用"——这也是大多数人纠结这个问题的真实场景。
---
## 二、稳定性:不是"能不能跑",是"断了你知不知道"
这是两种方案差距最大、又最少被认真比较的维度。
### 爬虫的故障模式:静默失败
爬虫最危险的特性是**它的失败经常不报错**。
页面改版后,CSS 选择器取到的价格字段是空的——但程序不崩溃,只是默默写进去一行空数据。三天后财务发现报表价格对不上,往前追,发现已经空了三天。
第 9 篇讲过一句话:**返回可解释的结果,比返回正确数据更重要**。爬虫恰恰相反:它给不出可解释的结果,页面改版后它连"我现在是坏的"都不告诉你。
另一个爬虫特有的问题是**数据可信度**。你爬到的是"页面展示价"——但页面展示价不一定是成交价:会员价、叠加优惠券、区域差异、千人千面的推荐价。商品价格同步这种场景,展示价和 API 返回的实际价格能差出 5%~15%,而这个差异你很难系统性发现。
### API 的故障模式:有状态、可归因
API 也会挂——平台故障、限流、字段弃用。区别在于:
| | 爬虫 | 官方 API |
| ------ | ----------------- | ---------------- |
| 失败形态 | 静默,返回空或错数据 | 报错,有错误码 |
| 能否定位原因 | 靠猜(页面又改了?IP 被封了?) | 错误码直接告诉你 |
| 恢复 | 人工修解析器,小时~天级 | 限流自动恢复 / 平台公告修字段 |
| 可监控性 | 极难(没有"正常"的基准) | 成功率、错误码分布都是现成指标 |
### 共同的必修课:数据校验
不管数据从哪来,校验都得做。这段代码对两种来源都适用——尤其是对爬虫,它是你发现"静默失败"的唯一手段:
```python
# integrity.py —— 数据完整性与静默失败检测
# 对 API 和爬虫的数据一视同仁地校验
from dataclasses import dataclass
@dataclass
class Issue:
item_id: str
field: str
kind: str # missing / out_of_range / mismatch
detail: str
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
REQUIRED = ("item_id", "title", "price", "stock")
PRICE_RANGE = (0.01, 10_000_000)
def validate(items: list[dict], source: str) -> list[Issue]:
"""单源校验:字段缺失 + 数值越界"""
issues = []
for it in items:
iid = str(it.get("item_id", "?"))
for f in REQUIRED:
v = it.get(f)
if v is None or v == "":
# 爬虫静默失败最典型的形态:字段存在但为空
issues.append(Issue(iid, f, "missing", f"{source} 返回空"))
p = it.get("price")
if isinstance(p, (int, float)) and not (PRICE_RANGE[0] <= p <= PRICE_RANGE[1]):
issues.append(Issue(iid, "price", "out_of_range", f"{source} 价格 {p}"))
return issues
def cross_check(primary: list[dict], secondary: list[dict],
tol: float = 0.01) -> list[Issue]:
"""
双源比对:比如 API 数据 vs 3 天前的快照。
同一商品价格波动超过 tol(1%),要么真降价了,要么有一边坏了。
配合告警,这是抓"静默失败"最有效的一招——
因为静默失败不会自己报警,只有比对能暴露它。
"""
ref = {str(x["item_id"]): x for x in secondary}
issues = []
for it in primary:
iid = str(it["item_id"])
old = ref.get(iid)
if not old:
continue
p_new, p_old = it.get("price"), old.get("price")
if p_new is None or p_old in (None, 0):
continue
if abs(p_new - p_old) / p_old > tol:
issues.append(Issue(
iid, "price", "mismatch",
f"{p_old} -> {p_new}(变动 {abs(p_new - p_old) / p_old:.1%})",
))
return issues
```
`cross_check` 是我们生产环境里跑了很多年的一个函数。它不关心数据从哪来,只关心**数据是不是还讲得通**。爬虫团队尤其需要它——因为没有它,你可能三个月都不知道自己的解析器坏了。
---
## 三、风险:这一项没有"权衡",只有红线
前两项是算术题,这一项是规则题。
把话说在前面:**我不会在这篇文章里讨论任何"绕过反爬"的技术**。不是因为怕事,是因为在国内的法律环境下,那已经不是技术选型的问题了。
### 商用场景下,爬虫面临三层风险,一层比一层重
**第一层:民事(反不正当竞争)**
绕开平台的访问控制抓数据、违反 robots 协议或用户协议抓取,在司法实践中普遍被认定为不正当竞争。这类判例非常多(大众点评诉百度、淘宝诉美景等都是公开判例),判赔金额从几十万到数千万不等。
**第二层:刑事**
这一层很多人不知道,但它真实存在:**如果抓取涉及突破或规避技术保护措施**(破解加密参数、伪造签名、打码平台过验证码),可能触及《刑法》第 285 条"非法获取计算机信息系统数据罪"——这是**刑事**罪名,有真实获刑判例,个人刑期、单位罚金都有。
爬取的内容一旦涉及**用户个人信息**(买家昵称、地址、手机号),还叠加《个人信息保护法》,责任更重。
**第三层:业务连续性**
就算前两层都侥幸没发生,平台识别到爬虫行为后封 IP、封账号、法律函三连——你的数据管道说断就断,而你的业务是建立在这条管道上的。**用爬虫做商用数据源,等于把业务的命脉押在一个随时可能被合法切断的东西上。**
### 两个常见误区,顺手拆掉
**"大厂也在爬"** —— 大厂爬大厂,是法务团队规模相当的对抗,输了一起谈,赔得起也耗得起。小团队没有这个筹码,同样的行为,风险完全不对等。
**"我只是自用,不商用"** —— 自用研究的风险确实低得多(这是真的,第四节也会说爬虫的合理场景)。但"自用"和"自用着自用着就给客户看了"之间的距离,通常只有一个业务增长周期。**数据一旦进入商业流程,就回不去了。**
### API 的风险也要诚实列出来
API 不是零风险:接口会下线、类目会调整、政策会变。但关键区别是——**平台对这些变更有公告期**(通常 30~90 天),你可以提前改代码;而页面改版不会通知你。另外,用 API 也有合规要求(超范围使用数据同样会被封),这条红线两边都存在。
所以风险这一项的结论是:**商用场景下它是一票否决项,不是加权项。**
---
## 四、什么时候爬虫是合理选择
说完了不合理的,得把合理的说清楚,不然这篇就成了立场文。
**爬虫合理的场景:**
1. **数据在官方 API 里根本不存在**。全网竞品价格监控、搜索引擎数据、社媒舆情——这些没有"官方 API"可接,公开信息抓取 + 合规使用是正当的。
2. **一次性研究 / 学习**。跑一次分析、写一篇报告、验证一个想法,不进入业务流程、不对外提供。这个场景爬虫完全成立。
3. **平台没有开放能力的长尾站点**。有些小平台根本没有开放平台,无 API 可接。
4. **你自己店铺的展示层信息**。详情页装修、页面呈现效果这类"展示层"数据,API 里没有,爬自家页面没有争议。
**不合理的场景只有一个,但最常见**:拿爬虫当 API 的"免费替代"去商用——尤其是数据明明在 API 里有的情况。这不是技术选型,是**把风险转移给三个月后的自己**。
一个快速自查清单,五个问题:
1. 这份数据官方 API 里有没有?(有 → API)
2. 用途会不会进入商业流程?(会 → API)
3. 有没有人能长期养它?(没有 → API)
4. 数据错了的代价是什么?(影响客户/财务 → API)
5. 需要多高的更新频率?(高频 → API,因为高频爬取必然撞上对抗)
**五个问题里有两个以上答案是括号里的那个,这个决策就已经做完了。** 真正还能选爬虫的,是五个问题都指向另一边的场景——那种场景其实不多,但确实存在。
---
## 五、最后
这篇的立场我尽量放平了:爬虫不是原罪,它有它的场景;API 也不是圣杯,它有自己的成本和维护量。
但有一个区别必须说透:**API 花的钱买的是"确定性"**——确定性的数据口径、确定性的错误反馈、确定性的法律边界。爬虫省下的钱,本质是从这三个确定性里借的,利息在后面。
我们做三平台数据接口服务,这篇里的账就是我们自己算过的账——也是我们客户 switching(从爬虫迁到 API)时反复验证的账。`tco.py` 的参数改成你们团队的真实数字,跑一遍,比看任何人的结论都有用。
下一篇写「多店铺授权管理」:token 的存储与加密、并发刷新撞车、单店授权过期的隔离——那是接入体系的最后一个坑区。
---
*附:`tco.py` 里 `maintain_fte=0.25` 是我们从数十个爬虫系统的维护投入里归纳的经验区间(0.2~0.4)的中位值。如果你的爬虫由多人共管,实际投入可能更高——共管意味着责任分散,这是另一个隐性成本。*