
简介压缩包汇集了US News、软科、QS、THE四个主流全球大学排名机构在2022年发布的数据并配套8个Python脚本实现从网页或接口抓取、清洗、解析到结构化输出的完整流程。资源共15个文件包括8个py脚本、4个zip数据包和3个txt说明文件压缩后仅13.21MB脚本按四大排名机构分块组织覆盖US News大学基本信息与学科指标、QS综合排名、软科世界一流学科排名等常见分析场景。数据截至2021年11月10日时效性较强已有1182人学习下载利用脚本可免去手工复制粘贴快速完成不同榜单间的大学对比、学科差异分析、趋势变化研究为选校、科研评价或出国咨询提供数据支撑。对高校研究者、升学规划顾问以及Python学习者而言这也是一个贴近实战的练习样例能同时了解requests、BeautifulSoup、pandas等库在真实排名数据上的协作方式稍作修改即可扩展成自己的大学排名采集和分析工具。1. 这个项目到底在做什么四榜数据合并的价值与难点先说结论这不是一个“爬数据”的项目而是一个“把四家评分标准完全不同的榜单揉成一张表”的项目。US News、软科ARWU、QS、THE这四家世界大学排名每年放榜时间不一样数据字段不一样学校英文名的写法还不一样想直接横向对比靠手工复制粘贴基本会疯掉。我在做2022年这版数据的时候核心目标就两个一是把四家的原始数据采集下来二是通过规范化的Python脚本把数据清洗、合并、对齐最终输出一份能直接用的CSV或者Excel。为什么选2022年这个版本因为2022年的四家数据在字段完整度和学校覆盖面上都比较均衡而且很多学校在COVID后的排名波动明显作为数据分析样本很有参考价值。这个脚本本身也不挑年份2023、2024的数据拿到手改一下抓取规则和映射表就能复用。1.1 四家榜单的数据格式差异有多大很多人以为四家榜单就是四列排名实际上远没那么简单。软科ARWU只公布前1000名字段最少核心就三样排名、学校名称、国家/地区。官网还附带一个历史版本对比这点比较友好。数据获取难度最低直接解析静态页面就能拿到完整表格。QS公布前1000名左右额外带一个“综合得分”Overall Score。2022年那版QS对指标的权重做过调整学术声誉占比降了、雇主声誉提升导致不少学校名次波动很大。它的页面有分页加载适合直接读接口或者分页抓。THE泰晤士高等教育公布前1400名以上字段比较全除了排名还有教学、科研、引文、产业收入、国际展望这五个维度的得分。它的数据在官网是表格形式但页面结构偏重加载慢需要等元素渲染完成。US News全球榜2022年公布了1750所学校字段包括全球研究声誉、区域研究声誉、出版物、书籍、会议等一堆学术产出指标。字段虽多但大部分分析用不到。US News的页面反爬最严格抓太快会弹验证码。四家榜单的国别字段写法也各不相同“United States”在QS里有时是“USA”THE里可能是“United States of America”软科干脆叫“美国”。这些清洗起来全是细节活。1.2 项目技术栈与脚本工具选型这个项目的整个处理链路用到的库不多但每一样都不可或缺requests负责拿网页数据。配合 fake_useragent 随机切换User-Agent降低被封概率。pandas负责清洗、合并、重命名、去重和导出。这几乎是数据分析脚本的标配。argparse作为脚本的入口参数解析工具支持命令行传参。比如指定年份、指定输出格式、指定是否抓取某一家的数据。BeautifulSoup / lxml解析HTML表格。如果数据能直接拿到JSON接口就跳过解析步骤直接用json库。提示对于纯数据分析的场景不要把四家榜单的抓取逻辑强塞进一个脚本里。我实测下来按榜单拆成多个模块文件再用一个主脚本统一调用后续维护性会好很多这也是脚本设计的基本经验。2. 数据采集策略四家榜单怎么抓最省事每个人的网络环境不同我没法保证我的抓取方式对所有人都能跑通尤其是US News这种反爬强的站点但下面这套方案是我反复调试后认为普适性最高的思路能拿接口就拿接口拿不到接口再解析HTML最后实在不行就手动下载离线表格兜底。2.1 QS和THE的采集思路优先找内嵌数据QS的官网数据通常会通过一个JSON接口返回而不是直接写在HTML里。用浏览器的开发者工具切到Network面板刷新页面后过滤XHR请求能看到一个返回榜单数据的接口地址。相对HTML解析JSON的处理简单很多直接requests.get就能拿到然后用pandas.read_json或者json.loads解析。THE的官网需要等JavaScript渲染才能看到表格直接requests拿到的HTML里没有排名数据。这种情况下有两种办法用Selenium或Playwright模拟浏览器加载等表格出现后抓取。在Network面板里找它背后的API地址直接从接口拿数据。我采的是第二种因为自动化脚本里引入一个重型浏览器会大大降低采集效率。THE的API接口有时候带签名参数请求头里需要带上Referer信息才不会被拒。2.2 软科与US News的采集差异软科的榜单纯静态HTML表格结构非常规整适合直接用pandas.read_html一把梭。read_html内部依赖BeautifulSoup/lxml解析会自动把table标签转换成DataFrame省去手写选择器的工作量。实测软科2022年的榜单页面read_html一次就能拿到全部1000所学校数据处理很方便。US News要麻烦得多它的数据分布在多个分页上而且页码参数是加密过的。最早的抓取方案是逐页翻HTML结果抓了十几页就触发验证码。后来改成了读页面内嵌的JSON数据把初始请求带上完整headers一次抓完所有数据再本地拆分。针对这个情况我的建议是优先找XHR接口不要死磕HTML解析既慢又不稳定。2.3 请求节奏控制的实操参数无论抓哪家都必须控制请求频率。这是我踩了很多次坑以后总结出的一个稳妥节奏榜单请求间距单次获取量所需请求次数软科1-2秒1000条1-2次QS3-5秒250条/页4-6次THE5-8秒100条/页10-15次US News8-10秒200条/页8-10次带随机延时比固定延时更安全核心在于避免平均间隔规律过于明显。用random.uniform(5, 8)代替time.sleep(5)能有效降低被限流的风险。3. 数据清洗与规范化把四张表对齐的关键环节采集只是第一步。四家榜单合并之后最头疼的问题不是数据量少而是字段口径不一致和学校名的写法五花八门。这一节我把清洗环节拆成两个核心子问题来讲。3.1 学校名称归一化四家榜单的“同名不同字”怎么处理学校名称对齐是全网做排行榜合并的人都会遇到的通用问题也是这个脚本里最需要花人工维护的部分。举个例子“清华大学”在四家榜单里的写法基本是Tsinghua University问题不大。“麻省理工学院”在US News写作“Massachusetts Institute of Technology”在THE写作“Massachusetts Institute of Technology”但QS有时候缩写为“MIT”。南洋理工大学在部分榜单里写作“Nanyang Technological University”在某一年份的软科数据里却写作“Nanyang Technol Univ”。这种大小写、缩写、带不带“The”的差异靠模糊匹配比如fuzzywuzzy虽然能解决一部分但正确率不够稳会把两所名字相近的学校匹配错。我的做法是建一个手动的标准化映射表把四家榜单的原始名称映射到一个统一的规范名上name_mapping { Massachusetts Institute of Technology: MIT, MIT: MIT, Nanyang Technol Univ: Nanyang Technological University, Nanyang Technological University: Nanyang Technological University, Univ Oxford: University of Oxford, }清洗时统一先走一遍映射表再考虑用简单规则补漏去掉缩写点、统一大小写、去掉首尾空格、把“University of XXX”和“XXX University”做一次互换尝试。规则不能太多太多反而容易误伤。3.2 排名字段的特殊处理区间值、未上榜和类型坑榜单数据里经常出现“301-350”这种区间排名。直接转成整数会报错不处理又没法排序。我当时的处理方案是拆出区间上下限生成两个新列rank_min和rank_max保留原始值在rank_display列。做筛选时用rank_min展示时用rank_display。这样既不影响分析又能保留原始信息。另一个大坑是“未上榜”的表示。不同榜单对未上榜学校的处理方式不一样有的是NaN有的是999有的是“1000”。如果统一填充成9999均值计算会被极端值拉低如果直接删掉又会丢失其他榜单已有的排名信息。我的方案是保留NaN不填充合并的时候用outer join做到哪家上榜就保留哪家的数据NaN在Excel里展示为空在分析时需要单独处理。字段类型也有讲究年份要统一成int排名区间列要统一成str国家/地区代码如果要合并ISO标准就单独建一个映射列不能直接拿原始国别去关联地图数据。4. 脚本实现从采集到合并的完整流程这部分给出我实际使用的脚本主体思路。完整代码太长我这里挑核心结构来讲方向对了你拿回去改成自己的需求并不难。4.1 主流程与参数传递设计脚本入口用argparse接收参数实际上对应了一些热搜词提到的“python给另一个py脚本传递参数”的场景。在设计上我选择让fetch模块和merge模块互相独立主脚本只负责组装数据流模块之间不互相调用这样就能在Shell里这样运行# 只抓取软科和QS python main.py --sources arwu qs --year 2022 # 抓取全部并输出Excel python main.py --sources arwu qs the usnews --year 2022 --format excel # 只合并本地已有数据不进行网络抓取 python merge.py --year 2022 --input data/ --output output/import argparse import pandas as pd def parse_args(): parser argparse.ArgumentParser(descriptionWorld University Rankings Collector) parser.add_argument(--sources, nargs, choices[arwu, qs, the, usnews], default[arwu, qs, the, usnews]) parser.add_argument(--year, typeint, default2022) parser.add_argument(--format, choices[csv, excel], defaultcsv) parser.add_argument(--output, typestr, defaultoutput) return parser.parse_args() if __name__ __main__: args parse_args() # 按顺序抓取并保存各榜单数据 for source in args.sources: df fetch_source(source, args.year) save_intermediate(df, source, args.year)这个参数设计在Linux上跑也完全没有问题只需要把python3替换成实际的解释器命令配合crontab做定时更新非常顺手。这也从侧面印证了脚本自动化学习的价值同样的代码逻辑在不同操作系统上的差异只在外层调用方式。4.2 合并逻辑与结果导出采集完成后所有中间文件统一放在data目录命名规则是{source}_{year}.csv。合并阶段的核心逻辑如下def merge_rankings(year, sources): frames [] for source in sources: df pd.read_csv(fdata/{source}_{year}.csv) df[source] source frames.append(df) # 用规范化名校对齐 combined None for df in frames: df[uni] df[school_name].map(name_mapping) if combined is None: combined df[[uni, rank, source]] else: combined pd.merge( combined, df[[uni, rank, source]], onuni, howouter, suffixes(_prev, _new) ) return combined单看合并逻辑很简单实际使用时需要注意直接merge之后行数往往会变多因为部分学校只出现在一家榜单里。出现这种情况不要慌这是正常的因为这些学校本身就不是全球性榜单的常客。最后导出的列我建议按这个顺序排列学校名、国家/地区、US News排名、QS排名、软科排名、THE排名、当年是否在前100、在几家榜单中出现过。这样排的好处是用Excel透视表或Tableau做后续分析时字段顺序符合阅读习惯。4.3 数据处理后的验收检查清洗合并完成后我在脚本里加了一个质检环节统计四家公司各自能匹配上的学校数量如果匹配率低于85%脚本会打印Warning提醒我检查映射表。数据量不是越全越好匹配率才是评价脚本质量的核心指标。def validate(combined, expected_counts): present combined.notna().sum() print(fUS News匹配数: {present.get(rank_usnews, 0)}) print(fQS匹配数: {present.get(rank_qs, 0)}) # 低于阈值时触发手动检查5. 常见问题与排查技巧实录写这类脚本运行的时候问题绝大多数不在业务逻辑而在数据本身的脏乱差和网络环境的不确定性。我把自己真实遇到过的坑整理成一个速查表方便你对照排查。5.1 编码乱码与请求被拒故障现象用requests抓回来的网页文本print出来全是乱码。或者Excel打开导出的CSV时中文全部变成乱码。排查思路HTTP响应头没有带charset或者带了错误的charset。requests默认会用header里的编码去解码如果header没写就落到ISO-8859-1中文直接崩。解决方案手动指定解码编码软科和THE的页面用UTF-8有的历史页面是GB2312。稳妥做法是先用requests的r.apparent_encoding检测一遍再回退到手动指定。导出CSV时统一用encodingutf-8-sig后缀这个编码会在文件头加BOMExcel才能正确识别中文。故障现象连续抓了几十页数据后突然收到403 Forbidden或503 Service Unavailable。原因分析请求频率太高被服务器限流。更隐蔽的原因是没有带全请求头很多站点会校验Referer和Accept字段缺少必要字段直接拒绝服务。解决方案加大延时完整复制浏览器请求头。最重要的是不要裸奔——裸requests的User-Agent很容易被识别并屏蔽。5.2 学校名匹配不全或匹配错乱故障现象合并完成后发现“University of California, Berkeley”和“Univ Calif Berkeley”被当成两所学校或者“University of Washington”被错误匹配成“Washington University in St. Louis”。原因分析靠简单规则匹配比如包含关系或首字母缩写很容易造成误匹配。中文网站在翻译时也喜欢简称转写进一步增加了复杂度。解决方案手工映射表 一次人工校对。第一版映射表构建好之后打印所有匹配不上的学校名人工确认一遍。这个过程虽然耗时但一次维护后可以长期复用后续年份只需要增量处理新上榜学校就行。这是一个值得做的累活。5.3 排名区间值和特殊符号的处理故障现象某个学校的排名显示为“42”或者“301-350”转成数值时直接报错或者解析成NaN。原因分析榜单里“”表示并列排名不处理的话没法参与数值运算。解决方案清洗函数里约定取第一个字符串把“”去掉遇到区间就取区间下限值作为rank_min。def clean_rank(value): if isinstance(value, str): if in value: return int(value.replace(, ).split(-)[0]) if - in value: return int(value.split(-)[0]) return int(value) return value6. 后续还能扩展什么方向脚本做完之后这个项目其实还有好几个很有价值的扩展方向。我目前验证过的两个思路你如果也有数据基础可以直接上手。6.1 自动更新与定时任务把抓取逻辑完成后配合Linux的crontab定时任务每月跑一次脚本就能在本地维护一份长期更新的榜单历史数据库。历史数据积累得越多越能看出大学排名的变化趋势比如某些学校因为指标权重调整而大幅波动的情况在时间序列里会非常清晰。实现方式不复杂就是把主脚本做成无交互模式所有参数通过命令行传入输出文件按年份归档。6.2 可视化分析与差异对比合并后的宽表可以直接接上matplotlib或pyecharts做可视化甚至可以做四家榜单排名的散点对比找出那些在US News和软科中排名差异巨大的“偏科”学校。这个方向的洞察价值往往比排名本身更有意思因为任何单一榜单都有指标偏置交叉对比才能还原学校的真实画像。个人经验补充一句做这类项目代码写得多好是次要的数据的可靠性才是决定成败的关键。宁可多花半小时做人工校对也不要完全相信自动化匹配的结果。我在这个项目上最值回票价的投入就是那一张手工维护的学校名称映射表。最后再分享一个小技巧如果只是想快速体验整个流程不必一上来就抓全四家榜单先用软科加QS两家数据跑通合并逻辑再逐步接入THE和US News这样排查问题时会容易很多。本文还有配套的精品资源点击获取