前阵子有个做航空市场研究的朋友问我想拉一份“全国民用运输机场吞吐量排名2006-2024”用来做区域经济分析和机场格局变迁的可视化。我一开始觉得这活儿不难官网有公报复制粘贴就行了结果真上手才发现这份看似简单的数据坑比想象中多得多。从2006年到2024年近20年跨度数据源分散、格式不统一、口径变化频繁光是“机场名称”这一列就能让人崩溃。这篇文章就把我完整跑通的数据获取方案整理出来。我会拆解怎么选数据源、怎么把PDF里排版混乱的表格抽出来、怎么清洗成统一结构的长表以及每一类坑对应的排查方法。不管你是做数据分析、写研究报告还是纯粹想用机场吞吐量做练手的可视化项目这套流程都能直接拿过去用。1. 先想清楚要什么需求拆解与整体路线1.1 一份看似简单的排名表背后有三层需求很多人看到这个标题第一反应是“排名表嘛不就是机场名字加一个数字”。但如果你真拿它去做分析会发现至少有三层需求是被隐藏的。第一层需求是把“某年某机场的旅客吞吐量”这组原始记录拿出来这解决的是“有没有数据”的问题。第二层需求是让不同年份的数据能够横向对比这里就涉及机场名称变更、单位换算、指标口径统一解决的是“数据能不能用”的问题。第三层需求是形成一张跨年度的长表每条记录是“机场-年份-旅客量-货邮量-起降架次”这样你才能做排名变迁分析、增速计算、区域结构对比解决的是“数据能挖出什么价值”的问题。我一开始犯的错就是一上来就找“排名”结果拿到的都是某一年的单独榜单做不了时序分析。后来我调整思路不要“排名表”而是要“原始明细表”排名自己算。这样思路一换整个数据获取方案就顺了。1.2 数据获取策略的取舍面对这个需求有人会想能不能直接调一个现成的接口把数据一条条拉下来我实话实说市面上没有哪个公开接口能一次性给你2006到2024年全国所有运输机场的完整吞吐量序列。就算有第三方平台做了整理数据质量和版权都是问题。最靠谱的办法还是回到官方发布的年度统计公报逐年抓取、逐年清洗。我的最终策略是“官方数据为主第三方数据为辅”。官方发布的年度民航机场生产统计公报是主干数据源里面包含全国运输机场的旅客吞吐量、货邮吞吐量和起降架次基本覆盖我需要的所有字段。第三方聚合平台和行业媒体做的榜单只用来做交叉校验比如判断我清洗后的某一列数值是否数量级正确。在字段设计上我建议不要只存“吞吐量”一个指标。既然要建历史库就把三件套都抓下来旅客吞吐量、货邮吞吐量、起降架次。理由很简单后面你做分析时旅客量反映的是客运市场的活跃度货邮量反映的是物流枢纽地位起降架次反映的是机场运行负荷三者的趋势经常不同步只留一个会损失大量信息。2. 数据源盘点哪条路更靠谱各自的脾气2.1 官方年度统计公报最权威但格式年年变官方年度统计公报是这类数据的源头。每年发布一份会列出全国民用运输机场的生产统计情况包括旅客吞吐量排名、货邮吞吐量排名、起降架次以及同比增速。我用下来最大的感受是权威性没得说但格式真的年年变。2006到2010年前后的公报很多是网页表格或者扫描版的PDF字段相对简单。到了2011年之后公报普遍以PDF形式发布表格结构也稳定一些。但你以为稳定就能无脑解析太天真了。有些年份的表格是单栏排布有些年份是双栏排布表头也经常换写法比如“旅客吞吐量”有时候是“旅客吞吐量人次”有时候是“旅客吞吐量万人”单位一变数字含义就完全不同不仔细看就是灾难。另外公报里的“吞吐量排名”通常是先给出前几十名有时候会附上全部机场的完整列表但不同年份覆盖范围不一样。早期的公报可能只公布了部分机场的数据后面年份才逐渐补齐。这意味着你拿到的逐年数据覆盖机场数量是不一致的做跨年对比时要注意底数差异。2.2 行业统计年鉴与区域性报告补缺口用的备用粮20年跨度里光靠公报一定会遇到一些年份的数据空缺或者细节缺失。比如某些年份公报里只有旅客吞吐量没有货邮吞吐量和起降架次又比如某一年你死活找不到完整的PDF版公报只找到新闻稿摘录的Top 20榜单。这种情况下行业统计年鉴和区域性民航管理部门的年度工作报告就派上用场了。统计年鉴的数据通常按年度汇总内容更细包括分机场的明细但缺点是出版滞后公开电子版比较少。区域性报告则适合补某几个机场的单独数据比如某个新建机场通航第一年的吞吐量可能在区域管理部门的报告里有详细记录。我的习惯是把这些补充数据源当作“拼图碎片”用哪一年缺了就去翻对应年份的补充材料但不会把它们当主数据源。因为不同来源的统计口径可能不一致混用多了后期校验会非常痛苦。2.3 第三方聚合平台与开放数据方便但只能当参考网上有不少第三方平台整理了历年的机场吞吐量榜单甚至还有可视化图表。这些平台的优势是方便打开就能看到整理好的表格劣势是来源不明、口径不一。比如同一个机场我在A平台看到的是“02834567”这样的数值在B平台看到的是“283.4万”没准两者都对只是单位不同。又比如机场名称有的平台用“某机场”完整名称有的平台用城市名加机场名或三字码你在做名称对齐时会被折腾得够呛。所以我对第三方数据的态度很明确只做校验不做基底。具体做法是用它来核对“我清洗后的数据里某机场某年的数值是否在合理区间里”而不是直接从上面复制数据进主表。这样既能借助第三方发现自己清洗过程中的错误又不会被它的混乱带偏。顺便提一句如果需要在大屏幕上展示或写报告引用尽量以官方公报口径为准避免版权和数据来源方面的麻烦。3. 实操全过程从PDF到结构化表格3.1 环境准备Python三件套选型阶段我对比过好几套方案。网页表格类数据直接抓HTML就行但官方公报大量以PDF形式发布所以PDF解析是主力。我用的是Python核心库就三个pdfplumber负责表格抽取pandas负责结构化处理re负责文本正则清理。安装很简单pip install pdfplumber pandasre是Python自带模块不用额外安装。有些年份是扫描版PDF那就是纯图片pdfplumber也抽不出文本这种情况就只能“截成图片再用OCR识别”。我会在后面的问题排查部分详细讲这里先记住一套主线流程。为什么选pdfplumber而不是别的库因为它对“版式规整的表格”识别效果非常好能够按页面里的线条坐标定位单元格把表格还原成行列结构。相比之下直接用pdfminer抽取文本虽然也能拿到字符但表格的对应关系就丢了你还得自己靠坐标推算哪列是哪列。pdfplumber的table抽取功能刚好能省掉这步。3.2 第一步抓取公报文件清单拿到一个年份的公报第一步不是解析而是先把官网页面上的下载链接梳理出来。公报一般挂在官方统计数据栏目下文件命名通常带有年份和“公报”字样。这一步我强烈建议不要用浏览器手动点20次下载写一个小的请求脚本来做更稳。但注意两点一是要设置合理的请求间隔不要瞬间并发几十个请求避免给目标服务器造成压力二是要做好失败重试和断点续传。我习惯的做法是先把下载链接收集到一个列表里逐个下载到本地data/pdf/目录文件名统一改成“2006年公报.pdf”这样的格式为后面批量解析做准备。import requests import time pdf_urls { 2006: https://example-placeholder/2006.pdf, 2007: https://example-placeholder/2007.pdf, # ... 按实际链接替换 } for year, url in pdf_urls.items(): resp requests.get(url, timeout30, headers{User-Agent: Mozilla/5.0}) if resp.status_code 200: with open(fdata/pdf/{year}年公报.pdf, wb) as f: f.write(resp.content) else: print(f{year} 下载失败状态码{resp.status_code}) time.sleep(2)下载这块踩过的一个坑是个别年份的链接指向的不是PDF而是一个网页预览页。这种情况下你直接把响应保存为PDF后面解析时会报“文件损坏”。所以下载完成后最好统一检查一下文件头PDF文件以“%PDF”开头不是的话就说明下载错了。3.3 第二步PDF表格抽取PDF下载完毕后就可以用pdfplumber批量抽取表格。官方公报的表格结构通常是排名、机场名称、旅客吞吐量、旅客吞吐量同比增速、货邮吞吐量、货邮吞吐量同比增速、起降架次、起降架次同比增速。这些字段不一定每年完全一致但大部分年份都在这个框架内。基本抽取代码如下import pdfplumber import pandas as pd def extract_table_from_pdf(pdf_path): tables [] with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: page_tables page.extract_tables() for table in page_tables: tables.extend(table) return pd.DataFrame(tables)这里有个细节extract_tables()返回的是列表的列表每一行是“单元格字符串列表”。如果表格正好跨页不同页面的表格结构可能相同但直接横向拼接会出现错位因为最后一行可能被表格分割线切断。所以抽取后我通常先看每一页的shape再做缝合处理。pdfplumber的一个好处是可以手动指定表格的列边界解决某些表格线不完整导致识别失败的问题。遇到竖线不闭合的表格可以用table page.extract_table({ vertical_strategy: lines, horizontal_strategy: text })line策略按线条切列text策略按文本行切行。这种混合策略能解决一部分“线不全但文字排布整齐”的表格。但要承认没有一个库能百分百搞定所有PDF所以核心思路是识别出来只是第一步后面清洗才是重头戏。3.4 第三步数据清洗与字段标准化PDF抽取出来的数据大概率是脏的。常见问题包括表头混入数据、单元格带空格、数字里有千分位逗号、单位用“万”而不是“人次”、机场名称里有奇怪的换行和空白。这一阶段的核心工作是把脏数据变成干净的、可比对的结构化数据。以机场名称为例我需要做三件事第一去掉名称里的全角和半角空格、多余的换行符。PDF里“某枢 纽机场”这种情况很常见实际上应该是“某枢纽机场”。第二统一表达方式。同一个机场在不同年份的公报里可能一会儿叫“某枢纽机场”一会儿叫“某市某机场”也可能是“某机场”带后缀或者不带后缀。最稳妥的办法是维护一个“标准名称映射表”把同一机场不同叫法映射到唯一的标准名。第三处理“多机场合并”的情况。有些年份公报会把同一个城市两个机场的数据合并成一行比如显示为“某城市/某支线机场”。这种记录在跨年对比时要特别标记否则你会以为是一个机场实际上是几个机场数据的加总。数字清洗这边也很有讲究。我写了一个小函数专门处理数字列def clean_number(x): if pd.isna(x): return None s str(x).replace(,, ).replace( , ).replace(, ) if s.endswith(万): return float(s[:-1]) * 10000 try: return float(s) except ValueError: return None这个函数的作用是把“28,345,678”转成28345678把“283.4万”转成2834000。货邮吞吐量同理关键是要先看准表头单位如果表头写的是“万吨”数值是“1.25”那实际吨数就是12500吨这个换算系数不能搞错。另外公报里通常还带“同比增速”这一列比如“5.2%”。清洗时我建议把增速单独拆出来保留因为你可以用它来校验“本期吞吐量 / 上期吞吐量 - 1”是否和增速对得上。对不上的地方往往意味着数据有缺失或者口径变了一查一个准。3.5 第四步跨年拼接与排名重置当所有年份都清洗完毕后就把它们拼成一张长表。结构很简单年份机场标准名称旅客吞吐量人次货邮吞吐量吨起降架次架次数据来源哪个年份的公报备注是否有口径变更、缺失、合并等情况拼接完成后你会发现最难处理的不是解析而是对齐。比如某个机场2006年没有数据是因为那一年它还没通航还是公报里漏了某个机场某一年的旅客量断崖式下跌是因为航班转场还是因为统计口径从“包含旅客”变成了“仅定期航班旅客”这些问题必须在备注列里写清楚方便后续分析时决定是保留、剔除还是单独标注。排名的话不需要依赖公报给你排好的名次直接用pandas算就行df[当年排名] df.groupby(年份)[旅客吞吐量].rank( ascendingFalse, methodmin )注意rank里的methodmin意思是相同数值的名次相同并且下一名会跳号。这和官方排名的处理方式一致不至于出现并列却都排第2名的情况。整个过程结束把结果导出为CSVdf.to_csv(全国民用运输机场吞吐量_2006_2024.csv, indexFalse, encodingutf-8-sig)utf-8-sig编码是为了让Excel直接打开时中文不乱码这种细节平时不注意真给别人发文件时就露馅了。4. 常见问题与排查技巧实录4.1 PDF表格“读出来是乱的”怎么办这是被问得最多的一个问题。pdfplumber有时候抽出来的表格行和列完全对不上数字跑到机场名称那一列里去了。我排查下来原因集中在两种一是表格的双栏排布二是合并单元格。双栏排布特别坑。公报里某些年份的表格不是一行一行地横贯整个页面而是左边一栏是一个表格右边一栏是另一个表格。pdfplumber会把整页当成一个表格抽两栏的数据就混在一起了。解决思路有两个一是用page.crop()先把页面裁切成左右两个区域再分别抽取表格二是先观察表格结构手动给extract_table指定列边界。合并单元格的情况同样常见。比如某一行里“机场名称”跨了两行或者“排名”这一列是一个大单元格底下盖住了好几行。处理这种我一般会透视看抽取出的行结构找到被合并的行然后手动补全。这类问题没法完全自动化只能靠“批量抽取出结果 人工抽检若干年份 写规则修正常见错位模式”这种组合拳来推进。如果遇到的是扫描版PDF那就不是表格抽取问题是OCR问题了。这种PDF本质上是图片pdfplumber提取不到文字。常规做法是用pymupdf把每一页导出成图片再用OCR引擎识别文字最后对识别出的文本块做排版还原。OCR这步会引入新错误比如“8”识别成“3”所以用OCR处理的年份需要额外做一轮数值校验。4.2 某些年份数据缺失怎么补20年里你最可能遇到的“缺”有几种情况第一种某一年公报里压根没公布完整的机场明细只有Top 10或Top 20。这种情况下不建议对未公布的机场做估算。宁可让该年份的机场覆盖数量少于其他年份也不能用插值去“脑补”吞吐量否则会污染后续所有同比分析。第二种某机场在某一年有运营但数据为0或没列入公报。常见原因是该机场当年处于改扩建停航状态或者从军民航合用机场转为纯民用机场的过程中统计方式有变。处理方式是在备注列里明确标记而不是把0当真实值填进去。第三种公报丢失但你可以从行业统计年鉴或者该机场所在的省级交通主管部门年度报告里找到同样口径的数据。这种补充来源要如实记录在“数据来源”列里因为不同统计主体之间的细微口径差异可能造成年份间的微小断裂。补充缺失数据的优先级顺序是官方公报优先统计年鉴次之区域性报告再次之第三方数据最后。任何来源都补不上的年份宁可留空给分析阶段足够的提示。4.3 机场名称和口径变化怎么追踪跨20年的数据名称不变才是少数。最常见的变化是“更名”比如从“某机场”改成“某国际机场”或者城市改了名机场跟着改。其次是“搬迁”老机场关闭新机场启用两者吞吐量不能衔接。还有“合并统计”和“拆分统计”比如两个机场整合运营统一申报数据。追踪这些变化我自己摸索出的一个土办法是维护一张“机场名称变更映射表”专门记录“曾用名-现用名-变更年份-变更类型”。清洗时每遇到一个不在标准表里的新名称就人工判断一次把它映射进去。这个过程虽然是半人工的但非常值得因为后续做长表分析时你唯一能用来关联不同年份的键就是标准名称。口径变化也要时刻留意。官方统计公报的“旅客吞吐量”通常包含旅客进出港人数既包括定期航班也包括客运包机。但不同年份是否把地区航线旅客计入“国内旅客”和“国际旅客”处理方式可能不同。你在做区域结构分析时如果发现“国际旅客量突然暴涨或暴跌”先不要急着下结论去查一下那一年的统计口径注释大概率能找到原因。4.4 数据质量校验三连数据处理完不代表结束我还保留了一个强制环节质量校验。每次清洗完一批年份数据我都会做三个查检动作。第一查总量校验。把当年所有机场的旅客吞吐量加总和公报上下文里提到的“全国总量”或“全年总计”对比。算出来的差异如果在1%以内基本没问题如果偏差很大大概率是某个机场的数值少了一个“0”或者单位换算错了。第二查排名合理性。把清洗后的数据按旅客吞吐量降序排一下查看前几名和后几名。一般来说头部机场的吞吐量量级和数量关系是符合常识的如果出现“某支线机场的吞吐量竟然超过某枢纽机场”那就要回到原始PDF确认。第三查年份间突变。对一个机场做逐年曲线正常情况下大多数年份应该是平滑变化或者趋势稳定。如果某一年突然出现“十倍暴涨”一般是那一行数据串行了如果是“断崖式下跌”大概率是统计口径变更或者机场停航需要回到备注列确认。这三个检查做完这份数据才敢说“能用了”。5. 一点个人体会这套数据我前后折腾了两轮才完全跑通。第一轮我急着要结果直接搜了一堆第三方榜单下载后拼在一起结果做到一半就发现年份之间完全对不上最后只能推倒重来。第二轮老老实实回到官方公报用“逐年下载、逐年清洗、逐年拼接”的方式做虽然前期慢但后面的分析阶段顺得出奇顺畅。如果让我给后来者一个建议我想说数据获取这类工作真正的技术难度不在“下载”而在“对齐”。PDF解析、正则清洗、pandas操作都是可以速成的工具技能而维护好一份名称映射表、处理好口径变更、保留好每个数据点的出处才是决定这份数据能复用多久的关键。别嫌半人工的校验环节麻烦你手工补一条备注可能就帮未来的自己省了三个小时的排查时间。最后再分享一个小技巧。如果你打算把这份数据做成长久维护的资产建议除CSV外再导出一份带完整备注的Excel版本每个字段都加“数据说明”工作表写明每个年份的数据来源、单位、口径变化。这样哪怕过了一年再回头用你也不需要重新翻公报去猜当年的字段含义。数据获取的终点不是“拿到表格”而是“让表格经得起时间检验”。