尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

告别爬虫踩坑:中国采招网API接入与招标商机监控实战

发布时间:2026/9/26 7:37:01

资讯中心
01
ARTICLE

告别爬虫踩坑:中国采招网API接入与招标商机监控实战

告别爬虫踩坑:中国采招网API接入与招标商机监控实战
中国采招网API是我做招投标数据服务这两年一直离不开的一个接口。最开始做招标信息盘点我一门心思想用爬虫解决结果被反爬、字符编码、页面结构改版折腾得够呛。后来在一个同行那儿看到他们直接接的采招网官方接口才意识到有些数据入口与其自己修路不如直接上高速。这篇文章我就把从申请密钥、理解鉴权、完成第一次调用到搭一个能自动盯商机的小工具的完整过程摊开来讲顺带把我踩过的坑都标出来。不管是做商机监控、标讯聚合还是给企业内部系统补数据源这套思路基本都能复用。1. 中国采招网API是什么我为什么从爬虫转过来1.1 官方接口解决的三个原始痛点先说结论能用官方接口解决的问题尽量不要自己写爬虫。这句话我是在被页面改版坑了三次之后才真正认同的。第一次做标讯采集的时候我的方案很简单定时抓取采招网的列表页再用规则解析标题、地区、时间这些字段。那个版本上线后头两周跑得还算平稳但第三周就收到告警解析出来的标题全是乱的。打开页面一看原来是列表页结构调整了字段从td放进了div里正则和XPath全部失效。改完一轮过了大概一个月对方又加了动态加载直接变成异步接口渲染我那些静态请求又拿不到完整数据了最后不得不上一套无头浏览器。那段时间的维护成本说实话比写业务代码还高。第二点是反爬问题。官方页面虽然不至于像电商平台那样严防死守但高频请求后IP会被临时限制验证码也会偶尔弹出来。我见过有人用几十个住宅代理去轮换且不说成本单是代理池本身的稳定性和合规性就已经够让人头疼了。做数据服务的人应该都知道业务侧最怕的不是接口少而是数据源中途断了整个下游链路全停。第三点才是关键网页数据是非结构化的。列表页里每一条公告的标题、地区、行业、预算金额能不能拆清楚完全取决于页面当时的排版。有时候预算金额不在列表里需要点进详情页再取有时候代理机构名称写得很随意根本没法做标准化。而官方API直接返回结构化JSON字段是定死的拿过来就能用清洗成本瞬间降了一个量级。这三件事叠在一起让我很坚定地转了方向。1.2 它的接口设计和我接触过的其他平台有什么不同接触采招网API之前我也接过一些别家的信息接口对比下来最大感受是这套接口更像是一套面向B端业务的开放平台而不是临时拿数据库往外糊一层HTTP。第一接口整体走的是比较标准的RESTful风格资源用名词表示比如公告列表、公告详情、关键词订阅操作方式就是GET或POST。第一次拿到文档的时候我甚至没有花太多时间就猜出了大部分路径的命名规律这对联调来说非常友好。第二返回结构是统一封装的外层有code、message、data不管请求成功还是失败走的都是同一套格式写通用异常处理非常省事。第三它在鉴权上做得比较细不只是给你一个API Key就完事而是要求加上签名参数时间戳、随机数这些都有。刚开始觉得麻烦后来想想这种设计能挡住不少恶意重放和参数篡改对靠数据吃饭的业务来说反而是好事。还有一点我特别满意字段设计上考虑到了实际业务场景比如publish_time、deadline、budget这些字段都是现成的不用我从标题或者正文里再去扣信息。这种懂业务的接口比那种只给你一堆无意义字段名的接口省心太多。2. 核心能力拆解能拿到什么数据字段长什么样2.1 主要接口能力清单我拿到的权限里最常用的接口大概是这几个大家实际接入时以官方文档为准但能力类型基本差不多公告列表接口按发布时间倒序返回当前最新的招标公告支持分页和关键词筛选。公告全文搜索接口不只是匹配标题可以对正文、附件名称做关键词检索适合做比较细的商机筛选。公告详情接口通过公告ID取某一条的完整内容包括项目概况、投标人资格要求、联系方式等。关键词订阅接口在平台上配置关键词后可以把新匹配到的公告推送到你自己的回调地址或者定期拉取。分类筛选项接口获取行业分类、地区编码等基础数据方便在业务系统里做下拉筛选。我第一次看到能力清单的时候第一反应是这个接口能撑起一个小型标讯平台。后来确实也验证了这一点我自己用它搭了一个面向内部销售团队的商机监控系统数据层面完全够用。2.2 返回数据的字段结构解读直接上一个我实际处理过的返回示例字段名和结构做了脱敏但格式是一样的{ code: 0, message: ok, data: { list: [ { id: 123456, title: 某市智慧交通项目招标公告, notice_type: 招标公告, region: 浙江省, industry: 交通运输, publish_time: 2025-01-10 09:30:00, deadline: 2025-02-10 17:00:00, budget: 1250000.00, agency: 某招标代理有限公司, source_url: https://example.com/detail/123456, summary: 本项目主要建设内容包括交通信号系统升级、视频监控设备采购及相关配套服务。 } ], total: 100, page: 1, page_size: 10 } }这里有几个字段我要特别提醒。budget看起来是一个简单的金额但很多项目不一定公示预算实际返回可能是null如果业务侧把预算为空当成预算为0那做统计报表的时候会非常尴尬。publish_time和deadline建议统一按北京时间处理别用服务器的本地时区我之前因为时区问题导致定时任务在夏令时切换的几天里推送时间全部错乱。source_url是官方落地页的地址它不等于永久链接有些详情页只对短期内有效的公告友好时间一长可能跳转或者打不开后面我会专门说这个问题。2.3 几个容易忽略的隐藏功能说两个容易被文档埋没、但实际很有用的点。第一个是全文搜索接口的召回率和我想的不太一样。最初我以为关键词搜索只能在标题里匹配后来发现它可以检索正文和附件名甚至能搜出一些标题里完全没出现关键词的公告。这个特性在做商机挖掘的时候特别值钱。举个例子很多市政项目标题是道路提升改造但正文里写到了智能井盖如果你只盯标题关键词这类信息就漏掉了。所以我建议有条件的人优先测一下搜索接口的检索范围把关键词配置做得宽一点。第二个是增量同步的时间戳参数。部分接口支持传一个开始时间只返回这个时间之后新发布或更新的公告。这比每次用page翻到底要高效得多。我一开始傻乎乎地全量翻页每天产生上万次请求还经常触发限流后来改成按最近更新时间增量拉取请求量降到原来的十分之一数据完整性反而更稳定。这个思路做数据同步的人应该都懂——增量永远比全量优雅。3. 从零接入申请密钥、鉴权细节和第一次真实调用3.1 申请API Key之前需要想清楚的事先泼一盆冷水申请接口权限之前最好先把自己想用它干什么想清楚。我见过太多人一上来就问给我开个最高权限结果拿到之后发现根本用不上每个接口的调用量是单独计费的预算蹭蹭就出去了。正常流程是这样的先在采招网注册账号完善企业信息然后找商务或技术支持申请开放平台权限。这个过程通常需要提供营业执照、业务场景说明以及你预估的调用量。官方要通过资质审核主要是防止接口被拿去批量倒卖数据。申请时你可以多要几个接口的测试权限一般是免费的或者带少量免费配额足够你跑通链路。拿到API Key之后第一件事绝对不是去写代码而是把Key和Secret存好。我见过不少人直接把Key写在Git仓库里或者贴在Jira工单上这等于把钥匙挂在门口。建议用环境变量、专门的密钥管理服务或者至少放到一个不参与版本控制的本地配置文件中。Key一旦泄露别人可以拿你的配额去跑数据轻则账单飙升重则账号被限流封禁处理起来非常麻烦。3.2 签名和鉴权不是简单的Token很多刚接触这套接口的人会在鉴权这里卡一下。它并不是把API Key放到Header里就完事而是要求对请求参数做签名常见的流程是这样的准备公共参数包括appid就是你的API Key、timestamp当前Unix时间戳、业务参数比如page、page_size。把参数按字典序排序转成keyvalue的字符串并用拼接。用API Secret对拼接字符串做HMAC-SHA256摘要得到sign。请求的时候把sign一起传上去服务端用同样的方式计算签名对比一致才会放行。为什么要这么设计简单说就是为了防重放。如果只传一个API Key别人截获请求之后就可以无限重放你的请求加了时间戳之后服务端可以判断这个请求是不是在某个时间窗口内发出的过期作废。再加签名参数稍有改动签名就对不上。做数据接口的这类鉴权已经是常规操作了。我自己的签名函数是这么写的大家可以参考import hashlib import hmac import time import requests API_KEY your_api_key API_SECRET your_api_secret BASE_URL https://api.example.com # 以官方文档为准 def generate_sign(params: dict, secret: str) - str: # 过滤空值并按 key 排序保证签名结果稳定 items sorted((k, v) for k, v in params.items() if v not in (None, )) query_string .join(f{k}{v} for k, v in items) sign hmac.new(secret.encode(), query_string.encode(), hashlib.sha256).hexdigest() return sign def fetch_notices(page1, page_size10): params { appid: API_KEY, timestamp: str(int(time.time())), page: page, page_size: page_size, keyword: , } params[sign] generate_sign(params, API_SECRET) resp requests.get(f{BASE_URL}/openapi/notice/list, paramsparams, timeout10) return resp.json()需要注意几个细节签名时要不要把sign自身也放进去不同平台规则不一样官方文档里会写清楚timestamp的时间单位是秒别拿毫秒去算另外有些接口要求用POSTJSON签名参数放Body里规则相同但拼字符串的方式可能略有差别。拿到文档后第一件事就是用最小请求跑通签名再逐步加业务参数。3.3 用Python完成第一次可用调用签名函数写好之后第一次真实调用基本就是拼参数的事。我习惯先不写任何业务逻辑直接用curl或者一个非常简单的Python脚本把返回结果打印出来确认能拿到数据再做下一步。data fetch_notices(page1, page_size5) if data[code] 0: for item in data[data][list]: print(item[id], item[title], item[publish_time]) else: print(request failed:, data[message])第一次看到正常返回的时候说实话还是挺兴奋的毕竟从各种正则匹配网页文本切换到一个JSON直接给全字段这个体验差异是巨大的。在这里也提醒一下如果第一次调用就遇到错误不要急着怀疑是接口坏了先按顺序排查三件事——参数是不是齐全时间戳对不对签名计算是否一致。大部分401、403都是这三类问题引起的。我刚开始有一次怎么签都不对最后发现是因为我用了中文关键词拼接签名的时候没有做URL编码导致服务端算出来的签名和我的不一样。这类问题多看几眼官方示例就能避开。4. 实战搭一个招标商机监控与推送小工具4.1 最小可用方案的选择接入接口之后最容易上手的第一个实战项目就是做一个招标商机监控工具。需求很简单每天定时去查一遍最新的招标公告凡是标题或正文里命中了我们关心的关键词比如智慧城市视频监控系统集成就把这条公告推到工作群让销售或商务人员第一时间看到。方案选择上我不推荐一上来就上Spring Boot、消息队列、Docker这些重家伙。对中小团队来说一个Python脚本 SQLite数据库 crontab再配合飞书、钉钉或者企业微信的机器人Webhook完全够用。这样做的好处是代码量小部署简单出了问题直接看日志就行不需要额外维护一堆基础设施。我遇到过很多新手总想把工具做得非常复杂又是搞微服务又是搞容器编排结果光环境搭建就折腾了三天核心业务还没碰。做这种内部工具最重要的是快速响应业务需求能跑、能推、能提醒就是胜利。4.2 增量抓取与推送的核心实现整个脚本的核心逻辑其实只有两步先去接口拉最新数据再和本地已处理过的公告ID做对比发现新公告就推送。去重这一环节很关键。如果每次都是全量拉取不做本地去重那同一个公告会被推送好几遍群里全是重复消息业务方很快就不看了。我的做法是本地维护一个seen表只存公告ID每次抓取时先拿到当前库里最大的ID然后只处理大于这个ID的数据。import sqlite3 import requests import time conn sqlite3.connect(notices.db) conn.execute(CREATE TABLE IF NOT EXISTS seen(id INTEGER PRIMARY KEY)) conn.commit() def get_last_seen_id(): row conn.execute(SELECT MAX(id) FROM seen).fetchone() return row[0] if row and row[0] else 0 def save_seen_ids(ids): conn.executemany(INSERT OR IGNORE INTO seen(id) VALUES (?), [(i,) for i in ids]) conn.commit() def fetch_new_notices(keyword智慧城市, max_pages5): last_id get_last_seen_id() new_items [] for page in range(1, max_pages 1): data fetch_notices(pagepage, keywordkeyword) for item in data[data][list]: if item[id] last_id: # 列表按发布时间倒序遇到旧数据就可以停掉当前页循环 continue new_items.append(item) return new_items def push_to_webhook(items): webhook_url https://your-webhook-url for item in items: msg { msgtype: text, text: { content: f新商机{item[title]}\n地区{item[region]}\n截止{item[deadline]}\n链接{item[source_url]} } } requests.post(webhook_url, jsonmsg, timeout5)这段代码写的比较直白胜在好理解好改。实际在项目里我会再加一个基础请求重试比如遇到网络抖动或5xx错误时用指数退避的方式重试两三次而不是直接抛异常。推送消息的格式也要稍微克制一点别把整篇正文塞进去因为群消息太长了根本没人看。标题、地区、截止时间、链接这四样信息足够业务方判断要不要点开详情。4.3 定时任务部署与日志排查本地脚本写好后部署很简单。我在服务器上放了一个目录叫bidding-monitor里面有三个东西脚本文件、SQLite数据库文件、logs目录。然后用crontab加了一条定时任务*/10 * * * * cd /home/user/bidding-monitor /usr/bin/python3 monitor.py logs/monitor.log 21每10分钟跑一次这样从公告发布到推送进群延迟一般不会超过10分钟。对于商机监控这个场景这个频率是合理的。再高的频率比如每1分钟一次容易触发限流而且对业务的实际帮助不会提升太多反而增加接口消耗。日志是我排查问题的主要依据。每跑一次脚本至少要打出以下几个信息请求开始时间、返回码、本批次新增数量、推送是否成功、执行耗时。有了这些后续不管是被群里的人问为什么没推还是接口突然报错都能很快定位。我还养成了一个习惯每天凌晨看一次昨天的错误统计如果某种错误码在短期内突然增多往往是接口参数变了、权限过期了或者官方调整了限流策略。这种事早发现一天就能少挨一天业务方的骂。5. 高频报错与排查技巧实录5.1 常见错误码速查表接口用久了常见的错误码基本就那么几种。我整理了一张速查表方便大家对照排查错误码常见原因排查与处理400参数格式不对、必填项缺失、日期格式错误检查请求参数是否和文档一致注意字段类型401签名错误、API Key不对、时间戳超时重新检查签名算法确认服务器时间准确403账号没有该接口权限或套餐不含该能力联系商务开通对应接口权限429超过调用频率限制降低并发加缓存做指数退避500服务端异常先等待几秒后重试多次出现则反馈官方503服务暂时不可用或正在升级不盲目重试观察一段时间再恢复这里我想特别强调一下401的排查思路。很多人一看到401就疯狂怀疑自己的API Key写错了但其实签名错误才是最常出问题的地方。我的排障顺序是先用官方SDK或文档里的示例请求跑一遍如果能通说明Key没问题然后把示例请求逐步替换成自己的参数看到底是哪一步把签名破坏了最后再检查请求到服务端的过程中有没有因为URL编码导致参数和签名前的原始字符串不一致。5.2 限流问题429不全是坏消息限流是很多人第一次跑这个接口时最容易遇到的坎尤其是上来就开多线程并发拉全量数据的。我看到429的第一反应从骂骂咧咧变成了冷静分析因为429至少说明三件事接口的鉴权是正常通过的你的请求量真的很大服务端在保护自己的资源。应对限流我自己的经验是三步走。第一步先看自己的请求必要性。是不是所有数据都需要实时拉比如历史公告归档完全可以在凌晨低峰期跑一次不用在白天高峰期反复翻。第二步给请求加缓存。同一个分页参数、同一个关键词短期内多次调用的结果是一样的直接在本地缓存里返回就行没必要反复打接口。第三步设计退避重试策略。遇到429不要立即重试建议先等几秒再按指数递增第一次等1秒第二次等2秒第三次等4秒最多不要超过30秒。同时把错误记录下来如果连续多次429宁愿让任务暂停也别硬刚。我后来用增量拉取代替全量翻页之后整体请求量降了一个数量级基本再也没触发过429。所以说好的数据同步设计不只是省流量更是避免被打回来的最好方式。5.3 避坑清单这些坑我替你踩过了最后列一份我自己踩过、也看别人踩过的坑每条都是真金白银换来的。API Key管理不规范。有人把Key直接写在代码注释里或者提交到Git仓库导致泄露后被刷了一整晚的接口。建议所有密钥都从环境变量读取代码库只留一个.env.example。时间戳和时区混用。服务器如果用了UTC和官方的北京时间差了8个小时增量同步和截止时间判断会出大问题。强烈建议在代码入口统一使用北京时间。分页不是无限翻的。很多开放平台对page_size有上限有的最大50有的100翻页太深之后效率也很差。正确做法是用时间增量去同步而不是傻乎乎地翻几千页。返回字段的source_url不要当永久链接使用。我最早把这条链接直接存进数据库给业务系统做外链跳转结果一个月后很多链接点击去已经跳到首页。后来我改成把公告详情里的关键字段也存下来自己渲染一个内部详情页链接失效的问题才彻底解决。不要多任务并行跑同一个账号。有人为了快开了十个进程同时拉数据结果就是全部429关掉九个进程之后才恢复。单账号的并发数是有限制的想要更高并发得走官方流程申请而不是自己偷偷开线程。接口文档和实际返回不一致时以实际返回为准。有一次文档里明明写了某字段是budget_money但真实返回是budget我按文档字段写代码结果全是None查了半天才反应过来。遇到这种情况先打印一段原始返回用眼睛确认字段名再写解析逻辑。按照我自己的习惯现在会把采招网的接口日志单独拉到一个目录每天凌晨看一眼错误分布哪类错误突然变多往往意味着接口参数或套餐权限调整了。另外接口返回里的source_url别直接存到数据库当永久链接我之前吃过教训有些详情页只对当天新发布的公告有效过了几天再打开就跳走了所以业务上要留个自己的落地页快照。如果你正准备接这套API我建议第一周先只做一个只读统计脚本把返回数据的字段真实性摸清楚再上线正式业务。这比什么都重要。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。