
目录一、四个口径四个数二、先看恒等式产生量 弃置量 回收量三、分母放谁18.1 个百分点和一个 108.3%四、人均弃置率反推人口口径的另一个入口五、URL 里嵌着年份明年这份链接就没了六、可直接抄的做法参考链接今天是中秋正日节后照例有一轮「月饼盒、包装、利是封到底回收了多少」的讨论。这类问题看着简单落到数据上全是坑。我把中国香港环境保护署的废物统计五份 CSV 全抓了下来零鉴权任何人都能复现只做一件事算回收率。三个数字先摆出来2024 年的回收率34.4% 和 52.5% 都算得出差18.1 个百分点区别在于分母放「产生量」还是「弃置量」用错分母的那条线2010 年算出过108.3%——回收率超过 100%这个数本身就是报错五份文件里「废物量」有四个口径总弃置5,723,100、都市固体废物弃置3,846,800、产生5,864,700、回收2,017,900公吨/年总弃置比都市固体废物弃置高出 48.8%。一、四个口径四个数五份文件的表头几乎一样year, waste_cat_en, 数量文件名却各不相同量的是四件事口径2024 年数值说明总弃置量5,723,100都市固体废物 建筑废物 特殊废物都市固体废物弃置量3,846,800只占上一行的 67%都市固体废物产生量5,864,700还没减掉回收掉的部分都市固体废物回收量2,017,900踩过一次就知道「香港一年产生多少废物」这句话四个数都能答而且都对。总弃置量那 5,723,100 吨拆开是都市固体废物 3,846,800、建筑废物 1,718,200、特殊废物 158,200——工地拆下来的混凝土占掉三成跟生活垃圾完全不是一回事。拿总数去算人均生活垃圾会比真实值高 48.8%。二、先看恒等式产生量 弃置量 回收量在动手算比率之前我习惯先找一条能自证的等式。这套数据里有一条# 产生量 与 弃置量回收量 的残差逐年核对importcsv,io,pathlib RAWpathlib.Path(原始返回/epd)defrows(name):text(RAW/f{name}_en_r0.csv).read_bytes().decode(utf-8-sig)return[rforrincsv.reader(io.StringIO(text))ifrandr[0].strip()]gen{r[0]:int(r[2])forrinrows(generation)[1:]}rec{r[0]:int(r[2])forrinrows(recovery)[1:]}msw{r[0]:int(r[2])forrinrows(bycat)[1:]ifr[1]Municipal solid waste}resid{y:gen[y]-msw[y]-rec[y]foryinsorted(gen)}zerosum(1forvinresid.values()ifv0)print(len(resid),zero,{y:vfory,vinresid.items()ifv!0})跑出来是16 14 {2013: -100, 2016: 100}16 个年份里14 年残差正好为 0只有 2013 和 2016 年差了 ±100 公吨——100 吨是这份数据的舍入粒度所有数字都取整到百吨所以这不是数据错是四舍五入留下的指纹。这条等式的价值在于它说明产生量、弃置量、回收量三个数来自同一套统计口径可以互相校验。拿到一批新数据时先找这种恒等式跑一遍比读完文档再下手靠谱得多——文档不会告诉你哪两个数该对得上。三、分母放谁18.1 个百分点和一个 108.3%回收率的分子没争议回收量 2,017,900分母有两个候选# 同一个分子两个分母foryinsorted(gen):over_genround(rec[y]/gen[y]*100,1)over_dispround(rec[y]/msw[y]*100,1)flag ← 超过 100%分母错了ifover_disp100elseprint(y,over_gen,over_disp,round(over_disp-over_gen,1),flag)# 2024 34.4 52.5 18.1# 2010 52.0 108.3 56.3 ← 超过 100%分母错了回收量 ÷ 产生量 34.4%废物流进处理链条之前就被分走了多少回收量 ÷ 弃置量 52.5%回收的和扔掉的之比。两个数都在说「回收率」差18.1 个百分点。第二个分母有个致命的破绽2010 年它算出 108.3%。回收率不可能超过 100%所以只要你算出来的比率越界就一定是分母选错了或者分子分母不同源。这是个不用读文档就能发现的信号——我建议把assert 0 rate 100直接写进脚本。为什么早期年份会越界看数据就明白2010 年的产生量是 6,930,100 吨回收量 3,603,400 吨而弃置量只有 3,326,700 吨——那几年回收的量大到接近弃置量说明「回收」的统计口径含出口转运与后面年份并不完全一致。跨年份比比率之前先确认口径有没有换过。四、人均弃置率反推人口口径的另一个入口第五份文件给的是人均弃置率公斤/人/日2024 年都市固体废物是1.4、家居废物是0.86。把总量和人均率放在一起可以反推出它用的是多少人口rate{}forrinrows(rate)[1:]:rate.setdefault(r[0],{})[r[1]]float(r[2])foryinsorted(msw):popmsw[y]*1000/365/rate[y][Municipal solid waste]/1e6print(y,round(pop,3))# 2009 6.948 ... 2020 7.527 ... 2024 7.528反推出来的序列从 2009 年的 694.8 万一路走到 2024 年的 752.8 万2021 年有一个回落742.3 万——与这几年的人口走势吻合。所以这份数据用的是年中人口口径而不是年末人口。这个方法反过来也很好用当你手上有两个聚合指标又不确定它们是否同源时反推出隐含的第三个量看它是否与已知事实对得上。要留意精度——人均率只保留两位小数1.4末位抖动 ±0.005 就是±0.4%换算到人口是±3 万做年度对比时这点误差无所谓做月度对比就完全不够用。五、URL 里嵌着年份明年这份链接就没了最后一个坑不在数据里在下载地址上。这五份文件的 URL 长这样.../solid-waste-generation-quantity-en-2024.csv年份直接写在文件名里。我把 2018 到 2026 都试了一遍9 个年份里只有 5 个还活着2020–2024 返回 2002018/2019 已被清理2025/2026 还没生成。更隐蔽的是同一个模式下的旧年份文件不是同一个数据2020 版只有 2009–2020 共 12 行465 字节2024 版有 16 行611 字节——文件每年长高一点旧文件是当年的快照不是全集的子集。所以硬编码 URL 有两个后果明年文件名从 2024 变 2025你的脚本直接 404就算某个旧链接还活着拿到的也不是最新全集而且你不会收到任何警告——它照样返回 200。正确做法是从数据集描述页取资源地址data.gov.hk 的package_show能拿到当前资源的真实 URL或者按年份构造时先探测再回退别把年份写死。六、可直接抄的做法先找恒等式再算比率。找到「A B C」这类能自证的关系跑一遍残差舍入粒度内的差异可以放过超出就别往下算。比率必须写边界断言。0 rate 100一行代码能挡住 108.3% 这种错。分母写进变量名。别叫rate叫recovery_over_generation半年后回来看代码你不会搞混。人均类指标反推隐含量验证口径同时记下它的有效位数别用它做超出精度的对比。下载地址不要硬编码带年份的文件名从资源清单取或者探测回退。今天 21 次请求五份主数据各两轮 三语对照 九个年份探测全部真实抓取原始文件已落盘脚本可以整篇复跑。参考链接https://www.epd.gov.hk/epd/sites/default/files/epd/english/environmentinhk/waste/data/files/solid-waste-generation-quantity-en-2024.csvhttps://data.gov.hk/en-data/api/3/action/package_show?idhk-epd-statteam-solid-waste-recovery-quantityhttps://www.epd.gov.hk/epd/english/environmentinhk/waste/waste_maincontent.html原创声明本文所有数据于 2026 年 9 月 25 日实测抓取自中国香港环境保护署公开统计文件与 data.gov.hk抓取与校验脚本随文附上欢迎复现。如果这篇帮你挡住了一次「回收率算成 108%」的事故点个收藏下一篇继续拆比率类指标的坑。