ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

批量采集API接口前为什么要做预检?从preflight到get_lesson的工程实践

批量采集API接口前为什么要做预检?从preflight到get_lesson的工程实践 我手里这套小工具一直没正经写过实测记录今天把misakanet_get_lesson和misakanet_preflight这两个函数放在一起完整跑了一遍补上这篇记录。起因不复杂我需要批量把课程平台里自己有权限的课程内容拉到本地做离线整理和笔记拆解。一开始工具只有get_lesson一个函数结果上线第一周就连续翻车——有的请求莫名其妙返回 403有的读到一半连接超时还有的文章读出来明显少了后半段排查了半天才发现是接口默认只回摘要。被折腾几次之后我补写了preflight在每次真正的读取动作之前先做一轮预检探测网络、TLS、Token、接口状态不行就直接停下来避免无效请求浪费时间。跑了小半个月我对这两个函数的真实边界有了点具体体会预检能挡掉一部分明显问题但并不能保证后面读取一定成功读全文能拿到大部分正文但在某些边界条件下也会静默截断。这篇文章就把它们各自的做法、实测数据、踩到的坑完整写出来供以后维护参考也给需要做类似“请求前预检 正文拉取”设计的朋友一个对照样本。1. 为什么我会把一次请求拆成 preflight 和 get_lesson 两段最开始设计时我确实只写了一个get_lesson(token, article_id)逻辑很简单拼请求头、GET 接口、解析返回里的 content 字段、落盘。单篇文章跑起来没有任何问题但一旦进入批量场景麻烦就接踵而至。最痛的一次是连续抓 20 篇文章跑到第 7 篇时突然报超时重试两次还是超时我就知道被限流了。但这时候问题来了你根本分不清到底是被限流、还是网络抖动、还是 Token 失效、还是接口本身临时 5xx。每一次盲目的重试都在加剧被限流的风险形成恶性循环。所以我决定加一层preflight。它的定位是“进入正式流程之前用最小的代价确认这条路能不能走通”。设计上我定了三个原则探路请求必须轻量能用 HEAD 绝不用 GET能少传参数就少传参数整个预检过程最好控制在 500ms 以内。检查项必须分层从底层到上层逐层做DNS 不过就直接退出没必要再去做 TLS 握手。预检通过不等于万事大吉它只负责挡住“明显走不通”的情况更深的运行时问题交给get_lesson内部的容错去兜底。也因为这个设计后来preflight和get_lesson的调用关系就很清晰了批量任务启动时先逐个对文章做预检预检有一项失败就跳过该篇并记录原因全部通过后再用固定并发数去跑get_lesson。预检的频次比读全文低得多所以哪怕多花一点时间也完全值得。2. preflight 预检覆盖的检查项与实测返回值preflight我实现的检查项一共六层按顺序执行任何一层失败就立即返回失败结果和阶段标识。2.1 六层检查的具体实现第一层是 DNS 解析。直接用socket.gethostbyname(host)确认域名能解析出地址。这一步看着多余但真遇到过内网 DNS 抖动导致批量任务全部失败的情况所以保留下来。第二层是 TCP 连接检测。用原生 socket 去连目标主机的 443 端口超时 3 秒。这一层能筛掉大部分网络不通的问题。第三层是 TLS 握手。Python 里直接用ssl.create_default_context()建立SSLSocket同时校验证书有效期和域名匹配。证书过期这种事在测试环境特别常见线上反而少。第四层是 HTTP 探路。构造一个和get_lesson几乎一样的请求头但请求方法换成 HEAD然后看状态码。这一步能发现 401、403、404 这类鉴权和资源问题。第五层是 Token 有效性校验。如果用的是 JWT我可以直接解码看exp字段但有的平台用不透明 Token我就改调一个轻量的鉴权探针接口或者解析响应头确认 Token 还有效。第六层是限流余量检查。读取响应头里的X-RateLimit-Remaining或RateLimit-Remaining字段。如果剩余次数太少就主动降低后续并发而不是等触发限流再被动退避。2.2 实测数据与典型的失败返回用 20 篇文章做了两轮完整预检每轮重复 3 次记录耗时和结果检查层平均耗时成功次数失败原因分布DNS 解析12ms40内网 DNS 超时 2 次TCP 连接45ms40全部通过TLS 握手110ms391 次证书过期HTTP HEAD130ms382 次 403请求头缺字段Token 校验8ms40全部有效限流余量3ms373 次余量过低主动暂停整体预检平均耗时约 400ms比一次完整 GET 请求少了差不多一半用来做批量任务前哨非常值。但也是这次实测让我确认了 preflight 的两个天然边界HEAD 请求和真实 GET 的路径可能不一致。有的反代对 HEAD 请求放行很宽松但 GET 会触发更严格的校验所以出现“预检过了、正式读却 403”的情况。限流余量是瞬时值。你读到余量 100不代表几秒后它还是 100尤其在高并发下余量变化很快。3. get_lesson 读全文的完整链路与实测边界get_lesson的职责很纯粹给定 Token 和文章 ID返回完整正文。但“完整正文”这四个字实际做起来远比听上去复杂。3.1 从 HTTP 响应到干净正文的四步处理第一步是构造请求头。User-Agent必须带Accept设为text/html,application/jsonAccept-Encoding设成gzip, deflate, br。这里有个坑请求头里的这些字段必须和preflight里用的完全一致。我吃过一次亏preflight 用极简头过了get_lesson 带了完整头反而被服务器识别为“非浏览器环境”而拒了。后来干脆统一从同一个配置函数生成请求头才彻底解决。第二步是处理重定向。301/302 要跟随但要设max_redirects 5防止死循环重定向把请求耗死。此外还要处理Content-Type一下子从application/json跳到text/html的诡异情况我见过有服务端在重定向后返回了登录页 HTML。第三步是解压。响应体如果带Content-Encoding: gzip或br直接解压成原始字节流。这里容易踩的坑是有的平台服务端会在设置了Accept-Encoding后对错误响应也做压缩你解压时如果不做异常兜底会直接抛gzip.BadGzipFile。所以我统一用try/except包裹解压过程解压失败就按原始字节处理。第四步是正文提取。接口返回 JSON 的情况最简单直接取data[content]字段再清洗掉首尾空白。但如果平台返回的是 HTML 片段就得走提取逻辑先用charset探测编码优先响应头里的charset参数其次查 HTML 里的meta charset否则回退 UTF-8再通过readability-lxml这类库抽取正文节点。3.2 长文、图文混排、代码块的实测表现我用 20 篇真实课程文章做了读全文测试大致分四类文章类型平均耗时正文完整度出现的问题纯文本短文2000 字内0.6s高无图文混排8000 字 图片1.4s高懒加载图片丢失只拿到图片占位符含代码块和表格1.8s中代码块被误判为脚本节点整段被 readability 过滤超长文章20000 字以上3.2s低接口默认只返回前 5000 字需要加参数翻页拉取这里最有价值的教训是接口的“全文”和我们理解的“全文”可能完全不是一回事。某个平台在 API 里加了verbosefalse和verbosetrue两个模式前者只返回摘要和正文前 5000 字后者才返回完整内容。我一开始没仔细读文档导致前几篇文章全部只抓到一半还以为是被限流截断了排查了很久。另一个重要边界是readability 提取正文的误伤问题。文章里嵌了大段代码结果readability-lxml因为代码块里的样式、脚本特征误判成script节点直接过滤掉。后来我在提取前先把pre、code节点加标记保护起来提取完再还原才解决。图片懒加载也是个大坑。正文里的img标签src字段往往是占位图真实地址在>
返回列表