简介:这是一份面向 Excel 与 WPS 办公自动化、VBA 网络请求开发者的网络函数库整合包,重点解决表格软件原生无法直接访问网络资源、难以完成 HTTP 请求与数据回传的问题。压缩包共 67 个文件、约 55.44MB,以 31 个 dll 动态库为核心,配合 xlsx/xlsm 示例表、js 浏览器扩展脚本、exe 安装更新程序及 xml、json 等配置与说明文件,覆盖网络库调用、公式示例、快递查询和翻译等场景。已有 2574 人学习下载,适合需要把实时接口数据接入表格的中级用户参考。读者可从中获得可运行的库文件、示例工作簿、扩展脚本与安装卸载说明,用于理解 VBA 调用 MSXML、Winsock 等网络组件的落地方式,并据此搭建自己的表格联网取数流程。
1. 从单元格里发一次 HTTP 请求:这个网络函数库到底解决什么问题
每月初,做数据核对的同事都要把后台导出的几十个接口地址逐个粘进浏览器,再把返回的 JSON 手工抠字段贴回 Excel 表格,一个下午就耗在这上面。所谓 Excel、WPS 用的网络函数库,本质是把一次 HTTP 请求封装成单元格函数:在格子里写=HTTPGET("…"),返回值直接落进表里,再配一个JsonPick把字段拆到列上,不需要装插件、不需要开浏览器,靠的是两个软件都认的 VBA 宏引擎。
它适合手上有一批固定 URL 要定期取数、又不愿意为这点需求专门上 BI 或写 Python 脚本的人——财务对账、运营拉日报、测试做接口回归、做数据比对的最常见。代价也说清楚:串行请求、没有真正的并发,单次几千行会跑上一阵子,所以后面的章节重点全在超时、限速和落表方式上。
2. 三条请求路线的选型:WinHTTP、MSXML2 与 WPS JS 宏怎么挑
同样是发一个 GET,选哪个引擎决定了这张表是“能用”还是“一跑就卡死”。我见过最多的事故不是代码写错,而是引擎挑错:用了没有超时控制的组件,对端某个接口挂住,整个 Excel 进程跟着失去响应,只能从任务管理器里结束进程,之前跑的几百行白干。这一章先把三条路线的脾气讲清楚,再落到部署前的环境盘点和函数命名。
2.1 三种请求引擎的差异与取舍
WinHttp.WinHttpRequest.5.1是系统自带的 COM 组件,最大优势是四段超时都能单独设:解析、连接、发送、接收,任意一段到点就抛错返回,函数能正常退出。它同步调用,写自定义函数最省事,缺点是有些老环境里被安全软件拦过一次之后就再也创建不出来,会报“ActiveX 部件不能创建对象”。
MSXML2.XMLHTTP兼容性更老更广,很多十年前的示例代码都用它,但它本身没有超时参数,只有ServerXMLHTTP提供了setTimeouts。用普通XMLHTTP发同步请求,对端不回包,Excel 就一直等——这就是典型的黑匣子场景,用户只看到转圈,不知道发生了什么。所以只有在WinHTTP创建失败需要兑底时,我才用它,并且一定在业务层套重试。
WPS 这边,专业版带 VBA 环境,Excel 那套代码基本能直接粘过去跑;个人版走的是 JS 宏,语法接近前端fetch,写起来更顺手,但 API 和 VBA 完全不互通。团队里两种软件都有人用的话,做法是把“发请求”抽成一个Engine接口,业务层只调HttpGet,底层换实现,别在业务代码里到处CreateObject。
最小可用的写法如下,Late Binding 不引用任何 COM 库,拷进标准模块就能跑:
' 最小可用版本:不引用 COM 库,直接创建对象,避免引用丢失后整表报错 Public Function HttpGetBasic(ByVal url As String) As String Dim http As Object On Error GoTo Fail ' 换成 "MSXML2.XMLHTTP" 也能跑,但它没有超时,对端不回包就会一直卡住 Set http = CreateObject("WinHttp.WinHttpRequest.5.1") ' 解析 / 连接 / 发送 / 接收,单位毫秒,四段都要设,任何一段为 0 都是无限等待 http.SetTimeouts 5000, 5000, 10000, 20000 http.Open "GET", url, False ' 第三个参数 False 才是同步,函数能直接拿到结果 http.Send HttpGetBasic = http.ResponseText Exit Function Fail: HttpGetBasic = "#ERR " & Err.Number & " " & Err.Description End Function四个超时不要照抄,按接口实际情况调:连接 5 秒够用了,接收给到 20 秒是因为有些报表接口要先跑聚合再返回,10 秒以内经常被误判成失败。返回值统一用#开头表达错误,是为了让调用方能一眼区分“真数据”和“异常”,这个约定后面批量落表时会一直用。
2.2 部署前的环境盘点
代码粘进去跑不起来,八成不是代码问题,是环境没准备好。我会在动手写业务函数之前先过一遍这五项:
第一,文件必须另存为.xlsm,.xlsx存不了宏,很多人改完代码顺手一保存,第二天打开发现函数全变#NAME?。
第二,宏安全设置和受信任位置。放在受信任位置里的工作簿不会每次打开都被拦,团队分发时尤其重要,否则每个人打开都要点一次“启用内容”。
第三,确认宿主软件支持宏。WPS 个人版默认走 JS 宏,VBA 模块可能压根打不开,先确认再决定写哪套。
第四,位数问题。老代码里常见Declare Function调系统 API 做延时,32 位和 64 位要写不同签名,一换机器就编译报错。我后面统一用Timer循环做延时,绕开这个坑。
第五,网络出口。公司网络对外访问通常有策略限制,先用浏览器把目标 URL 打开一次,确认能通,再去怀疑代码。
一段十几行的自检代码,能省掉半小时的瞎猜:
Sub 环境自检() Debug.Print "宿主版本: " & Application.Version ' 52 = xlsm(可存宏),51 = xlsx(存不了宏,代码会丢) Debug.Print "工作簿格式: " & ThisWorkbook.FileFormat Debug.Print "当前表: " & ActiveSheet.Name Debug.Print "用户名: " & Application.UserName ' 试着创建一次引擎,创建不出来说明被安全策略拦了 On Error Resume Next Dim h As Object: Set h = CreateObject("WinHttp.WinHttpRequest.5.1") Debug.Print "WinHTTP 可用: " & IIf(h Is Nothing, "否", "是") On Error GoTo 0 End SubFileFormat返回 51 就说明你现在在 xlsx 里写代码,赶紧另存;WinHTTP 可用打印“否”的话,先换MSXML2.XMLHTTP顶一阵,再找 IT 确认策略。
2.3 函数清单与命名约定
函数库不大,六七个函数就够覆盖日常取数。命名上我坚持两点:全部加业务前缀(比如Net_),避免和内置函数或其他插件撞名;错误一律返回#开头的字符串,不抛异常到单元格。
| 函数名 | 作用 | 返回 | 典型场景 |
|---|---|---|---|
Net_HttpGet | 发 GET 请求 | 响应文本或#ERR | 拉列表、查详情 |
Net_HttpPost | 发 POST 请求 | 响应文本或#ERR | 提交表单、带 body 的查询 |
Net_JsonPick | 按路径取 JSON 字段 | 标量字符串 | 从返回里抠名称、金额、状态 |
Net_UrlEncode | UTF-8 百分号转义 | 编码后字符串 | 中文关键词拼进 URL |
Net_BytesToText | 按字符集解码字节流 | 文本 | 响应头没写 charset 时救乱码 |
Net_GetCached | 带缓存的 GET | 同上 | 同一 URL 在一张表里被引用多次 |
命名定完就不要中途改,Excel 的公式里写死了函数名,改名等于把人家做好的表全废掉。真要改,就在旧函数里转调新函数,保留一层壳,过两个月再清理。
3. 把请求封成单元格函数:HttpGet、JsonPick 与批量落表的三段式
真正落地的时候,问题会从“能不能发出去”变成“发出去之后怎么把数据干净地放进格子”。三段式是我用得最顺的结构:第一段负责把请求发出去并把异常收敛成一个字符串,第二段把 JSON 拆成可寻址的字段,第三段用 Sub 批量写值而不是让几千个公式同时重算。三段分开写,出问题时能立刻定位是哪一段。
3.1 请求核心:HttpGet 的完整版
比最小版本多了请求头注入、状态码判断和错误收敛。状态码非 2xx 时不能直接返回响应体,因为很多接口报错时返回的是一段 HTML,塞进单元格里会把整列撑乱。
' 完整版 GET:支持附加请求头,非 2xx 统一收敛成 #HTTP 开头 Public Function Net_HttpGet(ByVal url As String, _ Optional ByVal timeoutMs As Long = 15000, _ Optional ByVal extraHeaders As String = "") As String Dim http As Object, pair As Variant, kv As Variant On Error GoTo Fail Set http = CreateObject("WinHttp.WinHttpRequest.5.1") http.SetTimeouts timeoutMs, timeoutMs, timeoutMs, timeoutMs http.Open "GET", url, False http.SetRequestHeader "User-Agent", "Mozilla/5.0 (ExcelFuncLib)" http.SetRequestHeader "Accept", "application/json, text/plain, */*" ' extraHeaders 用 "Key:Value|Key:Value" 传,避免再定义一堆可选参数 If Len(extraHeaders) > 0 Then For Each pair In Split(extraHeaders, "|") kv = Split(pair, ":", 2) If UBound(kv) = 1 Then http.SetRequestHeader Trim$(kv(0)), Trim$(kv(1)) Next pair End If http.Send If http.Status >= 200 And http.Status < 300 Then Net_HttpGet = http.ResponseText Else Net_HttpGet = "#HTTP" & http.Status & " " & Left$(http.ResponseText, 120) End If Exit Function Fail: Net_HttpGet = "#ERR " & Err.Number & " " & Err.Description End FunctiontimeoutMs一个值同时喂给四段超时,是图省事;如果某个接口连接快但计算慢,就拆成四个参数分别给。extraHeaders用竖线分隔,是为了在公式里能一行写完,比如Net_HttpGet(A2, 15000, "Authorization:Bearer xxx")。错误前缀分#ERR(本机异常,网络不通、组件创建失败)和#HTTP(对端返回非 2xx),排错时看前缀就知道该查自己还是查对方。
3.2 用九十行解析器把 JSON 拆到列
不引外部库的原因很简单:别人机器上没装就报“找不到工程或库”,而这类工作簿往往要在十几个同事的电脑上打开。自己写一个小解析器,只支持对象、数组、字符串、数字、布尔和 null,够用,也不会因为 Office 升级挂掉。
' 按 "data.list.0.name" 这种路径取值,取不到返回空串 Public Function Net_JsonPick(ByVal s As String, ByVal path As String) As String Dim keys() As String, i As Long, pos As Long keys = Split(path, ".") pos = InStr(s, "{") ' 从第一个对象开始 If pos = 0 Then Exit Function For i = LBound(keys) To UBound(keys) If IsNumeric(keys(i)) Then pos = SkipToIndex(s, pos, CLng(keys(i))) ' 数组下标,辅助函数略 Else pos = FindKey(s, pos, keys(i)) End If If pos = 0 Then Exit Function ' 路径断了,直接返回空,不要抛错 Next i Net_JsonPick = ReadScalar(s, pos) End Function ' 从 pos 开始找名为 key 的成员,返回它的值起始位置 Private Function FindKey(ByVal s As String, ByVal pos As Long, ByVal key As String) As Long Dim i As Long, kEnd As Long, k As String, ch As String i = pos Do While i <= Len(s) ch = Mid$(s, i, 1) If ch = """" Then kEnd = NextQuote(s, i + 1) k = Mid$(s, i + 1, kEnd - i - 1) i = kEnd + 1 Do While i <= Len(s) And Mid$(s, i, 1) <> ":": i = i + 1: Loop i = i + 1 If k = key Then FindKey = i: Exit Function ' 命中,返回值的起点 i = SkipValue(s, i) ' 不是目标就整体跳过它的值 ElseIf ch = "}" Then Exit Function ' 到对象结尾还没找到 Else i = i + 1 End If Loop End Function ' 跳过一整个 JSON 值,字符串要处理 \" 转义,对象和数组要配对括号 Private Function SkipValue(ByVal s As String, ByVal pos As Long) As Long Dim ch As String, depth As Long ch = Mid$(s, pos, 1) If ch = """" Then SkipValue = NextQuote(s, pos + 1) + 1: Exit Function If ch = "{" Or ch = "[" Then depth = 1 pos = pos + 1 Do While pos <= Len(s) ch = Mid$(s, pos, 1) If ch = """" Then pos = NextQuote(s, pos + 1) + 1 Else If ch = "{" Or ch = "[" Then depth = depth + 1 If ch = "}" Or ch = "]" Then depth = depth - 1 If depth = 0 Then SkipValue = pos + 1: Exit Function End If pos = pos + 1 End If Loop End If Do While pos <= Len(s) ' 数字、true/false/null,吃到分隔符 If InStr(",}]" & vbCr & vbLf, Mid$(s, pos, 1)) > 0 Then Exit Do pos = pos + 1 Loop SkipValue = pos End Function ' 找到与前面那个引号配对的结束引号,遇 \ 直接跳过两个字符 Private Function NextQuote(ByVal s As String, ByVal pos As Long) As Long Do While pos <= Len(s) Select Case Mid$(s, pos, 1) Case "\": pos = pos + 2 Case """": NextQuote = pos: Exit Function Case Else: pos = pos + 1 End Select Loop NextQuote = Len(s) + 1 End Function ' 读标量:字符串去引号,数字原样返回(保留原始文本,避免精度丢失) Private Function ReadScalar(ByVal s As String, ByVal pos As Long) As String Dim e As Long, raw As String If Mid$(s, pos, 1) = """" Then e = NextQuote(s, pos + 1) raw = Mid$(s, pos + 1, e - pos - 1) ReadScalar = Replace(Replace(raw, "\""", """"), "\n", vbLf) Else e = pos Do While e <= Len(s) And InStr(",}] " & vbCr & vbLf, Mid$(s, e, 1)) = 0 e = e + 1 Loop ReadScalar = Trim$(Mid$(s, pos, e - pos)) End If End Function路径用点分隔,数组元素直接写下标,data.list.0.name和data.total都能取。SkipToIndex和FindKey思路一致,只是在数组里数逗号,代码不长,可以按同样套路补。几个要说明的边界:第一,键名本身是纯数字时IsNumeric会误判,这种接口就得改用别的路径写法;第二,字符串里嵌转义 JSON 的情况不支持,取出来会是一段原始文本,需要再调一次Net_JsonPick;第三,数字按原始文本返回,就是为了不把 19 位订单号转成浮点数。
3.3 批量填充:一次跑完整列不刷屏
几百个单元格同时写=Net_HttpGet(A2)是最常见的错误用法:每次重算都真的发一次请求,一个单元格改动带动整列重跑,接口直接被自己打死。正确做法是用 Sub 串行跑一遍,把结果写成值。
' 串行跑完整列:A 列 URL,B 列时间,C 列结果,D 列状态 Public Sub BatchFill() Dim ws As Worksheet, lastRow As Long, i As Long, resp As String Const STEP_MS As Long = 200 Set ws = ThisWorkbook.Worksheets("任务") lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row Application.ScreenUpdating = False ' 关掉重绘,几百行下来差别很明显 Application.Calculation = xlCalculationManual For i = 2 To lastRow If Len(ws.Cells(i, "A").Value) = 0 Then Exit For If Len(ws.Cells(i, "D").Value) > 0 Then GoTo NextRow ' D 有值说明跑过,断点续跑 resp = Net_HttpGet(ws.Cells(i, "A").Value, 15000) ws.Cells(i, "B").Value = Now If Left$(resp, 1) = "#" Then ws.Cells(i, "C").Value = resp ws.Cells(i, "D").Value = "FAIL" Else ws.Cells(i, "C").Value = Net_JsonPick(resp, "data.name") ws.Cells(i, "D").Value = "OK" End If SleepMs STEP_MS ' 限速,别把对端当压力测试靶子 NextRow: DoEvents Next i Application.Calculation = xlCalculationAutomatic Application.ScreenUpdating = True End Sub ' 不用系统 API 的延时,32/64 位都能跑 Private Sub SleepMs(ByVal ms As Long) Dim t As Single: t = Timer Do While (Timer - t) * 1000 < ms DoEvents Loop End SubD列兼作断点标记,中途崩了重跑会自动跳过已完成的行,这个习惯救过我很多次。STEP_MS按接口承受能力调,公开接口给到 300 到 500 毫秒更稳妥。关闭自动重算很关键,否则每写一个单元格都可能触发整表重算,本来是几百次请求,硬生生变成几千次。
4. 参数怎么设:超时、鉴权头与编码的九个开关
同一份函数库,参数设得对不对,结果差很远:设松了整表跑一晚上,设紧了正常接口全被判失败。这一章把三类最常调的参数拆开讲——时间类的超时和重试、身份类的请求头、文本类的编码,每类都给可抄的写法和建议区间。
4.1 超时与重试:退避比次数重要
| 参数 | 含义 | 建议值 | 设错的后果 |
|---|---|---|---|
| 连接超时 | TCP 握手等待 | 3000~5000 ms | 太小,网络稍抖就整批失败 |
| 发送超时 | 请求体写完的等待 | 5000~10000 ms | POST 大 body 时容易被截断 |
| 接收超时 | 等对端返回 | 15000~30000 ms | 太小,聚合类接口全部超时 |
| 重试次数 | 单个 URL 最多试几次 | 2~3 次 | 太多,限流时雪上加霜 |
| 退避间隔 | 两次重试之间等多久 | 300 ms 起,逐次翻倍 | 固定间隔等于持续施压 |
重试要分错误类型:#HTTP429(被限流)必须退避,原地立刻重试只会让封禁时间更长;#ERR里的连接失败可以短间隔重试;#HTTP4xx里的 400、401 重试多少次都没用,是参数或凭证的问题,应该直接标记失败让人看。
' 带退避的重试包装:429 单独处理,其它错误短间隔重试 Public Function Net_HttpGetRetry(ByVal url As String, Optional ByVal tries As Long = 3) As String Dim i As Long, r As String For i = 1 To tries r = Net_HttpGet(url, 15000) If Left$(r, 1) <> "#" Then Exit For ' 拿到正常数据就走 If InStr(r, "#HTTP429") = 1 Then SleepMs 1000 * i ' 限流:1s、2s、3s 递增 ElseIf InStr(r, "#HTTP4") = 1 Then Exit For ' 4xx 是请求本身有问题,别重试 Else SleepMs 300 * i End If Next i Net_HttpGetRetry = r End Functiontries默认 3,是因为大部分瞬时抖动在第二次就能恢复,再往上加收益很小,反而拖长整批时间。判断 4xx 用InStr(r, "#HTTP4") = 1,前提是前面拼错误串时状态码紧跟在#HTTP后面,格式不能改。
4.2 请求头与密钥:别把 token 写进公式
把Authorization直接写进单元格公式,等于把凭证发给每一个能看到这张表的人,而且改一次密码就要全表替换。我的做法是放在隐藏工作表的命名区域里,公式只引用函数,函数内部去取名值。
' 从命名区域取 token,命名区域建在隐藏表上,公式里看不到明文 Private Function AuthHeader() As String Dim t As String On Error Resume Next t = ThisWorkbook.Names("API_TOKEN").RefersToRange.Value On Error GoTo 0 If Len(t) = 0 Then AuthHeader = "" Else AuthHeader = "Bearer " & t End Function ' 业务侧调用:请求头从命名区域拼,不在公式里出现 Public Function Net_GetWithAuth(ByVal url As String) As String Dim h As String: h = AuthHeader() If Len(h) = 0 Then Net_GetWithAuth = "#ERR 缺少 API_TOKEN" Exit Function End If Net_GetWithAuth = Net_HttpGet(url, 15000, "Authorization:" & h) End Function命名区域名API_TOKEN要在“公式 → 名称管理器”里建,引用位置指向隐藏表的一个单元格。工作簿分发出去之前记得把值清空,让对方自己填,这一条属于血泪经验——带着真实凭证的模板发出去,收回来就收不回了。
Content-Type在 POST 里必须显式声明,application/json和application/x-www-form-urlencoded两种写法差别很大,前者 body 是 JSON 字符串,后者要自己拼k=v&k2=v2并且值要转义。
4.3 编码三连:URL 转义、GBK 响应与长数字
中文参数直接拼进 URL,大概率返回 400 或者查不到数据,因为空格、中文、&这些字符在 query 里必须做百分号转义,而且是 UTF-8 的字节。VBA 里没有现成函数,用ADODB.Stream转一次字节最省事:
' UTF-8 百分号转义,只保留字母数字和 - _ . 三类安全字符 Public Function Net_UrlEncode(ByVal s As String) As String Dim st As Object, b() As Byte, i As Long, out As String Set st = CreateObject("ADODB.Stream") st.Type = 2: st.Charset = "utf-8": st.Open st.WriteText s st.Position = 0: st.Type = 1 ' 切回二进制再读,拿到 UTF-8 字节 b = st.Read st.Close For i = LBound(b) To UBound(b) If (b(i) >= 48 And b(i) <= 57) Or (b(i) >= 65 And b(i) <= 90) _ Or (b(i) >= 97 And b(i) <= 122) Or b(i) = 45 Or b(i) = 95 Or b(i) = 46 Then out = out & Chr$(b(i)) Else out = out & "%" & Right$("0" & Hex$(b(i)), 2) End If Next i Net_UrlEncode = out End Function反过来,响应乱码多半是字符集没对上。ResponseText按响应头里的 charset 解码,头里没写或者写的是gb2312时,中文就成问号。这时候不要用ResponseText,改用ResponseBody自己解:
' 按指定字符集解码字节流,响应头没写 charset 时手动指定 Public Function Net_BytesToText(ByVal body As Variant, Optional ByVal charset As String = "utf-8") As String Dim st As Object Set st = CreateObject("ADODB.Stream") st.Type = 1: st.Open st.Write body st.Position = 0 st.Type = 2: st.Charset = charset ' 常见值:utf-8、gb2312、gbk Net_BytesToText = st.ReadText st.Close End Function先试utf-8,出来还是乱码就换gb2312,两种都不对说明返回的根本不是文本(可能是压缩包或图片),别再折腾编码。
长数字是另一类隐形坑:19 位订单号写进单元格会被 Excel 按数值处理,尾部直接变 0,日期格式的字符串还会被自动转成日期序列号。写值之前先把目标列设成文本:
' 写值前先把列设成文本,防止长数字被截断、日期被自动转换 With ws.Columns("C") .NumberFormat = "@" .Value = .Value ' 已有内容重写一遍,把之前被转掉的格式纠正回来 End With5. 排查清单:函数库在真实表格里翻车的 5 个现场
代码在别人机器上跑和在你自己机器上跑,是两件事。下面这五条都是我在实际分发中反复遇到的,按“现象 → 原因 → 解决”写,遇到对应症状可以直接对号入座。
5.1 公式层:报错、不刷新、整列卡住
现象一:整列显示#VALUE!,但双击某个单元格回车重算又正常了。原因是自定义函数在一次批量重算里集中发起请求,超时或撞上限流,返回的错误串被 Excel 当成非法值。解决办法是别用公式铺满整列,改成 3.3 的 Sub 批量写值;确实要保留公式的,把请求量控制在几十个以内,并给足接收超时。
现象二:所有单元格都是#NAME?,模块里明明有函数。原因有三个常见分支:宏没启用、函数写在了ThisWorkbook或工作表模块里(那里面的 Public 函数单元格看不见)、函数名和内置函数或已加载的插件撞名。解决是把它挪进标准模块、确认宏已启用、加业务前缀重命名。改完名称后原来的公式会失效,记得同步替换。
现象三:改了参数,结果不跟着变。自定义函数默认不是易失性的,参数没变就不重算。解决是在函数开头加Application.Volatile,或者在参数里加一个自增序号列,让参数真正发生变化。加Volatile要谨慎,整表任何改动都会触发全部请求,量大时更卡。
5.2 数据层:乱码、截断与格式漂移
现象四:返回的中文全是问号或方块。原因是字符集判断错了,或者响应头里压根没写 charset。解决办法是用ResponseBody配Net_BytesToText,先试 utf-8 再试 gb2312。还有一个容易忽略的分支:请求里带了中文参数但没转义,对端收到的是乱码,返回的自然也是乱码,这时候要查的是Net_UrlEncode有没有用上。
现象五:返回的大段文本只进了一部分,或者长数字尾部变 0。前者是单元格字符数上限(单个单元格约 3 万字符),超长响应要先截断再落表,别指望整段塞进去;后者是数值格式在作怪,写值前把列设成文本。日期型字符串被自动转成2024/1/1这种显示,也是同一类问题,同样靠NumberFormat = "@"解决。
5.3 限流与体积:429、超时雪崩和文件越跑越大
现象六:跑了几十行之后开始连续返回#HTTP429,而且越重试越严重。原因是没有限速,对端按来源计数,短时间高频请求直接触发保护。解决是加固定间隔(200~500 毫秒)、按 4.1 的退避策略重试、把批量任务拆成小批分时段跑。这里没有捷径,只能把节奏降下来。
现象七:一批跑完,文件从几百 KB 涨到几十 MB,打开越来越慢。原因是每写一个单元格都触发了整表重算,加上公式链残留、整列引用,体积滚雪球。解决是批量前关掉自动重算和屏幕刷新、把中间结果写成值而不是保留公式、跑完做一次“清除不可见字符 + 另存为新文件”。我一般的做法是每天跑完另存一个带日期的副本,原始模板保持干净。
6. 再进一步:给函数库加一层缓存与上线自检
单次请求跑通只是及格线,能反复用在多张表里才算落地。最划算的一步优化是加缓存:同一张表里同一个 URL 经常被引用好几次(比如多个字段都从同一个详情接口取),不缓存就是白白多发几倍请求。
' 会话级缓存:同一批任务内相同 URL 只发一次 Private cache As Object Public Function Net_GetCached(ByVal url As String) As String If cache Is Nothing Then Set cache = CreateObject("Scripting.Dictionary") If cache.Exists(url) Then Net_GetCached = cache(url) Exit Function End If Dim r As String r = Net_HttpGetRetry(url, 3) cache.Add url, r Net_GetCached = r End Function ' 每批任务开始时清空,避免拿到上一次跑的旧数据 Public Sub ResetCache() Set cache = Nothing End Sub缓存放模块级变量,生命周期跟工作簿走,所以ResetCache必须在每批任务开始前调一次,否则第二天跑的还是昨天的数据,这种错误排查起来非常费劲。缓存键直接用完整 URL(含 query),不同参数自然区分开;如果接口返回会随时间变化,就别用缓存版本的函数,老老实实每次请求。
上线前我会对着一张表把六项过一遍,缺一项都不敢发给同事:
| 检查项 | 通过标准 | 不通过的典型表现 |
|---|---|---|
| 文件格式 | 已存为启用宏的格式 | 重开后函数全变#NAME? |
| 函数位置 | 全部在标准模块且为 Public | 单元格里找不到函数 |
| 凭证存放 | 在隐藏表命名区域,公式无明文 | 表格外发即泄露 |
| 超时设置 | 四段全部显式赋值 | 偶发整表失去响应 |
| 限速与重试 | 有间隔、有退避、4xx 不重试 | 跑一半开始连续 429 |
| 断点续跑 | 有状态列,重跑能跳过已完成行 | 中途崩了只能从头来 |
从那以后我每接一个取数表,都强制走一遍这个顺序:先跑环境自检确认引擎能创建,再拿三条 URL 手动试跑看返回结构,然后小批量跑二十行验证限速,最后才铺全量。跳过前两步省下的十分钟,往往要在后面用两小时排查补回来。希望帮到你。
本文还有配套的精品资源,点击获取