Python 抓 0.91 GB 香港法例:Agent 的进度该写进磁盘,不是写进上下文

做香港职场自动化,绕不开法例------《雇佣条例》《最低工资条例》《职业安全及健康条例》这些决定了 HR 流程里每一条硬性规则。香港把全部现行法例做成了开放数据,整包 0.91 GB

这就撞上一个很实际的问题:一个 0.91 GB 的下载任务,Agent 不可能靠上下文记进度。 上下文里放得下「我要下 4 个文件」,放不下「第 2 个文件已经下了 347,219,003 字节」。一旦中断,靠上下文回忆进度的 Agent 只能从头再来。

这篇文章把这件事做了一遍:从清单解析、三层验收、断点续传到 sha256 校验,全部实测。所有数字可复现。

文章目录

    • [1. 官方的清单,本身就是验收标准](#1. 官方的清单,本身就是验收标准)
    • [2. 三次把关,越早发现问题越便宜](#2. 三次把关,越早发现问题越便宜)
    • [3. 断点续传:先确认对端答应](#3. 断点续传:先确认对端答应)
    • [4. 完整跑一遍:49.4 MB,14.27 秒](#4. 完整跑一遍:49.4 MB,14.27 秒)
    • [5. 校验:分块读,别把 0.91 GB 读进内存](#5. 校验:分块读,别把 0.91 GB 读进内存)
    • [6. 进度账本:为什么不能放在上下文里](#6. 进度账本:为什么不能放在上下文里)
    • [7. 踩坑清单与结论](#7. 踩坑清单与结论)
    • [8. 参考链接](#8. 参考链接)

1. 官方的清单,本身就是验收标准

下载之前先拿清单。这份 XML 只有 2.8 MB,但它把整个任务的账目写清楚了:

python 复制代码
import re, pathlib

def parse_plan(path):
    """清单里每个 <DataResource> 都自带 fileSize 和 sha256 ------ 直接当验收标准用。"""
    t = pathlib.Path(path).read_text(encoding="utf-8")
    head = dict(re.findall(r'(\w+)="([^"]*)"', t.split("<DataSet>")[0]))
    items = []
    for blk in re.findall(r"<DataResource\b(.*?)</DataResource>", t, flags=re.S):
        a = dict(re.findall(r'(\w+)="([^"]*)"', blk))
        a["label"] = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", "", blk)).strip()
        items.append(a)
    return head, items

跑出来是 4 个条目:

分卷 声明大小 类型
Cap. 1 -- 300 225.0 MB 法律
Cap. 301 -- 600 534.5 MB 法律
Cap. 601 -- 末 123.3 MB 法律
附例(Instruments) 49.4 MB 附例
合计 977,571,818 B = 0.91 GB

清单里还有两个容易被忽略但很有用的字段:UpdatedDateTime(本次是 2026-09-14T21:25:47)和每条目的 fileDate它们让你在下第一个字节之前就知道这批数据是不是你要的那一版。

这三个检查点值得收藏成一张检查表 ------ 任何大文件任务,动手前过一遍。

顺手做了一个交叉验证:把清单声明的 fileSize 和对端实际返回的 Content-Length 逐条比。4 个条目全部一致,一字不差------这说明这份清单是可信的,可以直接当基准。

2. 三次把关,越早发现问题越便宜

长任务最怕的不是失败,是跑到最后一秒才发现不对。这个下载任务里其实有三个天然的检查点:

① 清单的 fileSize(动手之前)------0.91 GB 这个数让你能提前算:按实测的 3.9 MB/s,全量大概 4 分钟;磁盘够不够;要不要分期。

Content-Length(建立连接时)------只需要一个 HEAD 请求。如果它和清单声明对不上,说明远端换过文件了,此时放弃的成本是「一个请求」。

sha256(下载完成之后)------唯一能证明「下下来的字节就是官方那份」的一步。

很多人只做第 ① 步(看一眼大小差不多就开始下),然后在最后发现校验不过,回头重下。把关点越靠后,返工代价越大。

3. 断点续传:先确认对端答应

-C - 这个参数不是魔法,它只是让 curl 在请求头里加一句 Range: bytes=N-能不能续,取决于对端回不回 206 Partial Content

所以动手前先探一次:

python 复制代码
def range_probe(url, nbytes=1024):
    """断点续传的前提:对端要回 206 + Content-Range,否则 Range 会被忽略。"""
    r = subprocess.run(["curl", "-s", "-o", "/dev/null", "-D", "-",
                        "-r", f"0-{nbytes - 1}", "--max-time", "40", url],
                       capture_output=True, text=True)
    st = [l.split()[:2] for l in r.stdout.splitlines() if l.startswith("HTTP/")]
    cr = re.search(r"(?im)^content-range:\s*(.+)$", r.stdout)
    return {"status": st[-1][1] if st else "000",
            "content_range": cr.group(1).strip() if cr else None}

回到的是 206content-range: bytes 0-1023/129326534。对端答应了。

然后做真实的「中断---续传」:

复制代码
第 1 次:curl -sL --max-time 4 -o seg.zip "$URL"     → 4,728,174 字节(4.5 MB)
第 2 次:curl -sL --max-time 4 -C - -o seg.zip "$URL" → 8,932,884 字节(8.5 MB)

第二次的增量是 4,204,710 字节------它没有重新开始,而是从 4.5 MB 处接上的。

这里有个细节值得单独说:-C - 是让客户端 告诉对端「我从哪接」。如果本地文件被截断过、或者上次下载写坏了字节,-C - 会老老实实从一个错误的位置接上,并且不报错 。所以续传必须配一个事后校验------就是第 5 节的 sha256

4. 完整跑一遍:49.4 MB,14.27 秒

为了真验一次,我下载了最小的那一卷(附例,49.4 MB):

复制代码
目标 hkel_c_instruments_en.zip
清单声明 51,811,189 B | 本地已有 0 B (0.0%)
下载后 51,811,189 B | 完成 True | 状态已写入 download_state.json
real 0m14.274s

14.27 秒,49.4 MB,约 3.9 MB/s。 按这个速度,0.91 GB 全量大约 4 分钟------这是可以放进一次任务里的量级,不需要分段跨天。

速度数字的意义在于:它让「要不要下载全量」变成一个可以用算术回答的问题,而不是靠感觉。

5. 校验:分块读,别把 0.91 GB 读进内存

官方已经给了 sha256,所以校验逻辑不需要自己定义「什么算下载成功」:

python 复制代码
def sha256_of(path, chunk=1 << 20):
    """按 1 MB 分块读 ------ 0.91 GB 的文件不要一次性读进内存。"""
    h = hashlib.sha256()
    with pathlib.Path(path).open("rb") as f:
        for blk in iter(lambda: f.read(chunk), b""):
            h.update(blk)
    return h.hexdigest().upper()

实测结果:

复制代码
本地 51,811,189 B / 声明 51,811,189 B
sha256 本地 51A4D2F38E2D025DF4AE2E96... | 声明 51A4D2F38E2D025DF4AE2E96...
校验 通过

这段校验代码可以直接收藏:换任何带官方校验和的官方数据源(很多政府/开源镜像都提供),改一行路径就能用。分块大小取 1 MB 是个平衡点------再小会增加系统调用次数,再大对内存不友好。

6. 进度账本:为什么不能放在上下文里

现在回到 Agent 的问题。假设让一个 Agent 去下这 0.91 GB:

放进上下文会怎样? 「已下载 4 个文件中的第 2 个」这种状态,上下文里存的是语义摘要 。中断后重启,Agent 只能看到「第 2 个还没下完」------但不知道是 5% 还是 95%。它唯一安全的选择是重新开始,这在 535 MB 那一卷上意味着白等两分钟。

放进磁盘会怎样? 状态变成可精确读取的数字:

python 复制代码
def save_state(path, key, declared, local):
    """进度落盘:只存「能用来做判断」的字段。"""
    state = json.loads(pathlib.Path(path).read_text(encoding="utf-8")) \
        if pathlib.Path(path).exists() else {}
    state[key] = {"declared": declared, "local": local,
                  "complete": local == declared}
    pathlib.Path(path).write_text(
        json.dumps(state, ensure_ascii=False, indent=1), encoding="utf-8")
    return state

重启后第一件事不是问模型「我们进行到哪了」,而是 读磁盘 → 比对声明的 fileSize → 决定续传还是重下这一步是纯算术,不需要判断力,也就不会出错。

这个「状态落盘」的写法可以收藏 ------ 换成批量调 API、批量转码这类长任务,账本字段换掉就能直接用。

分工线因此很清楚:字节搬运和状态记账交给磁盘和 curl,Agent 只负责决策------比如「这一卷校验不过,要不要重下一次」、「磁盘只剩 2 GB 了,剩下两卷先下哪个」。

7. 踩坑清单与结论

现象 处理
只信大小不看校验 下完才发现内容不对 官方给了 sha256 就用它
-C - 接在错误位置 续传成功但文件是坏的,不报错 续传后必须校验
一次性读大文件算哈希 0.91 GB 进内存 按 1 MB 分块流式读
进度只存上下文 中断后不知道下了多少,只能重来 状态落盘,重启先读盘
不探 Range 就续传 对端不支持时静默从头下 先探 206 再决定策略
假设清单永远不变 远端换了文件,本地拼出混版 比对 fileSize + updatedDateTime

三条:

  1. 下载任务的正确姿势是「先算账,再动手」。 清单把总量、单卷大小、校验和都给了,把这些读进来看一遍,比下载完之后再发现问题便宜得多。
  2. 断点续传解决的是「不要重复劳动」,不解决「数据对不对」。 这两件事要分开做,前者靠 Range,后者靠 sha256
  3. Agent 的长任务里,能落盘的都别放上下文。 判断力是稀缺资源,进度、字节数、校验结果这些纯数据,交给磁盘比交给模型可靠。

本文只做公开数据的接入与传输实测,不涉及任何法律意见或 HR 合规判断。 全部数字可按文中代码复现。

8. 参考链接

  1. https://resource.data.one.gov.hk/doj/data/hkel_list_c_all_en.xml
  2. https://resource.data.one.gov.hk/doj/data/hkel_c_leg_cap_1_cap_300_en.zip
  3. https://resource.data.one.gov.hk/doj/data/hkel_c_instruments_en.zip

原创声明:本文为原创内容,数据来自香港律政司 e-Legislation 开放数据,代码可直接运行验证,转载请注明出处。

关注不迷路:后续会继续用 Python 拆解香港的公开数据源,把「接口看着规整、跑起来全是坑」的部分一个个写清楚。

相关推荐
ClinicTech1 小时前
实验室洗瓶机的清洗系统架构与自动化控制解析
java·python
洋洋不叫杨杨1 小时前
Codex 实战:用 AI 写运维脚本
大数据·python
高级程序源1 小时前
django大学生创新创业项目管理系统94923-计算机课程设计、毕业设计
javascript·vue.js·spring boot·后端·python·django·课程设计
wno7042 小时前
Spring Security短信验证码登录
java·python·spring
泡泡鱼(敲代码中)2 小时前
Python 容器类型学习笔记:列表、元组、字典、集合
开发语言·笔记·python·pycharm
别动我齐刘海2 小时前
ROS2 Jazzy + C++ 实战路线——基础学习2
c++·人工智能·vscode·python·学习·机器学习·机器人
Experience-摆渡2 小时前
开源RAG知识库WeKnora深度调研:让知识自己长成体系
爬虫·docker·开源·rag
AR-26710-2 小时前
Linux Day7——建组/用户、umask、Python脚本
linux·python
打工仔折腾 AI3 小时前
普通摄像头接入AI识别:绿联NAS部署Frigate监控实战
人工智能·后端·python·性能优化·ai agent 实战