
简介一份涵盖美国新闻与世界报道US News、软科、QS、泰晤士高等教育THE2022年世界大学排名数据及Python处理脚本的资源包面向需要批量获取全球高校排名信息的研究者、教育工作者和数据分析初学者也可作为Python爬虫与数据清洗的实战练习。压缩包共15个文件包含8个.py脚本、4个.zip数据包和3个.txt说明文件整体13.21MB。脚本按四个排名机构分别编写覆盖网页请求、HTML解析、字段提取和结果保存涉及requests、BeautifulSoup、pandas等常用库数据包中存放各榜单原始信息txt文件提供大学名称、链接等辅助数据便于对照查阅。已有1182人学习下载。利用这些代码无需手动逐校查询即可快速获得结构化排名四大排名体系分别侧重学术声誉、科研产出、雇主评价和教学质量借助脚本可进行横向对比与趋势分析适合院校研究、留学参考和Python综合项目。 2022年做留学数据整理时我碰上一个很实际的问题US News、软科、QS、THE这四大世界大学排名数据分散在各自官网上格式五花八门手动一个个复制到表格里不仅慢还容易抄错行。索性花了几个晚上写了套Python脚本把抓取、清洗、标准化、存储全串了起来。这篇文章就把这套脚本的设计思路、踩坑过程和关键代码完整拆出来适合要做数据分析、留学申请对比或者单纯想练手爬虫的朋友参考。1. 为什么要把四大排名收进一套脚本1.1 先搞清楚四个排名在比什么在写第一行代码之前我先把四个排名的评价体系摸了一遍。很多人在抓数据时不做这一步结果抓回来一堆数字却不知道怎么统一。US News全球最佳大学Best Global Universities覆盖学校数量最多超过2000所评价维度包括学术声誉、论文引用、国际合作等区域性拆得比较细。软科世界大学学术排名ARWU非常偏理工科和科研产出看重诺贝尔奖、菲尔兹奖、高被引学者、Nature和Science论文数这些硬指标语言和人文类院校在这里往往不太占便宜。QS世界大学排名大家听得最多40%学术声誉、10%雇主声誉、再加师生比、论文引用、国际师生占比企业HR和中介都非常认这套。THE泰晤士高等教育排名则是教学、研究、论文引用、国际展望、产业收入五个维度打散后加权其中论文引用占30%和QS的口径差别相当明显。这套评价体系差异带来的直接后果就是同一所学校在不同榜单里的位次可能差出一两百名。这不是数据错了而是评分逻辑完全不同。所以做聚合数据时必须把source字段保留下来不能简单混在一起比大小。1.2 数据标准化一套字段吃四份数据四个榜单的表头命名各不相同。US News会有“National University Rank”这种带国家属性的表述QS部分页面是分页表格THE则有大量“—”、“100”这种特殊标记。直接合并的话光字段名就够折腾。我的做法是统一成一张宽表字段就六个source数据来源、year年份、rank排名、university学校名称、country国家/地区、score总分。最后再加一列raw_title保留原始标题行方便回溯原始页面。这六列看着简单但能把四个榜单的数据全部装进去。后续不管你是做groupby统计还是按国家过滤或者画趋势图都不需要再回原始页面去对数据。2. 脚本架构选型与参数化设计2.1 技术栈为什么这么选这套工具我最终选的是 requests BeautifulSoup4 lxml Pandas动态加载页面才用 Selenium 兜底。没有选 Scrapy是因为这个任务本质是几个轻量抓取脚本不是持续运行的爬虫系统用框架反而增加学习和排错成本。requests 负责拿到HTMLBeautifulSoup 配合 lxml 解析器负责从页面提取表格。Pandas 负责最后的数据清洗和落盘。至于 Selenium我本来也不想上但个别排名网站的数据表是通过AJAX异步刷出来的直接 requests 抓不到目标表格这种情况才用浏览器模拟。其实这套技术组合可以推广到更广的场景凡是需要从网页端拿到结构化数据的任务基本都是“请求页面 → 找数据节点 → 清洗入库”这条链路。核心不是工具多高级而是你愿不愿意先把目标页面的HTML结构看清楚。2.2 主脚本如何调度子脚本并传参四个榜单的页面结构差异很大写在一个大脚本里后期维护会很难受。我拆成了四个子模块文件分别对应usnews、arwu、qs、the的解析逻辑再用run_pipeline.py统一调度。这里就用到了“一个Python脚本给另一个Python脚本传递参数”的思路。具体做法是主脚本通过subprocess调用fetch_one.py用--source和--year把来源和年份传进去子脚本用argparse解析参数。import subprocess import sys import argparse SOURCES [usnews, arwu, qs, the] def run_one(source, year): cmd [sys.executable, fetch_one.py, --source, source, --year, str(year)] subprocess.run(cmd, checkTrue) if __name__ __main__: ap argparse.ArgumentParser() ap.add_argument(--sources, nargs, defaultSOURCES) ap.add_argument(--year, typeint, default2022) args ap.parse_args() for s in args.sources: run_one(s, args.year)子脚本fetch_one.py里大概是这样的结构参数决定走哪个数据源的解析函数import argparse def parse_usnews(year): pass def parse_arwu(year): pass def parse_qs(year): pass def parse_the(year): pass if __name__ __main__: ap argparse.ArgumentParser() ap.add_argument(--source, requiredTrue) ap.add_argument(--year, typeint, default2022) args ap.parse_args() parser_map { usnews: parse_usnews, arwu: parse_arwu, qs: parse_qs, the: parse_the, } parser_map[args.source](args.year)这样设计的最大好处是每个源出错时错误堆栈只会出现在它自己的子脚本里不会互相牵连。而且主脚本可以只跑其中一个源比如python run_pipeline.py --sources qs the不用每次都四个全跑一遍。3. 核心代码实现与实操步骤3.1 页面抓取与表格定位抓取页面最忌讳一上来就对着网址写选择器。我的习惯是先把页面保存到本地在编辑器里搜关键词比如“Rank”“University”“score”确认目标数据在哪个表格标签里。构建通用请求函数时有几个细节值得注意。首先用requests.Session()而不是每次重新请求这样能复用连接和Cookie。其次要设置timeout防止某个页面卡住导致整个流程停下来。第三是编码问题部分老页面返回的响应头里没有 charset直接resp.text会乱码所以要先探测编码。import requests from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept-Language: en-US,en;q0.9, } def get_table(url, keywordUniversity): session requests.Session() resp session.get(url, headersheaders, timeout15) resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, lxml) tables soup.find_all(table) for t in tables: if keyword in t.get_text(): return t return None这里用keyword in t.get_text()来判断目标表格比写死class或者id要稳。因为某些网站升级模板后CSS类名经常变但表格里的列头文字不会轻易改。3.2 排名数据的脏数据清洗拿到表格以后清洗环节才是真正费时间的部分。我这套脚本里遇到过几种典型的脏数据形态第一是并列排名。US News和THE里经常出现“120”“187”这种写法要去掉等号再转整数。第二是缺失值部分学校的得分栏为空或者只有“-”要统一变成空值不能直接塞进DataFrame。第三是学校名称里混入了上下标标签比如有的校名带sup标记需要把标签剥掉。用Pandas清洗代码大概是这个思路import pandas as pd rows [] for tr in table.find_all(tr): cells [td.get_text(stripTrue) for td in tr.find_all([td, th])] if len(cells) 3: rows.append(cells) df pd.DataFrame(rows) df.columns [rank, university, country, score] df[rank] df[rank].astype(str).str.replace(, ) df[rank] pd.to_numeric(df[rank], errorscoerce).astype(Int64) df[score] pd.to_numeric(df[score], errorscoerce) df[source] source df[year] year df[raw_title] df[university] df.to_csv(fdata/{source}_{year}.csv, indexFalse, encodingutf-8-sig)pd.to_numeric(..., errorscoerce)配合Int64类型很重要。它能把无法转换的字符串变成NaN同时保留整数排名不会出现“120.0”这种带小数点的尴尬情况。CSV用utf-8-sig编码保存是为了让Excel打开时不出现中文乱码这是Windows环境下常见的坑。3.3 Linux下运行与定时更新整套脚本最终跑在Linux服务器上因为要定时更新数据。这里有两个我反复踩过的坑先说第一个不要在系统全局Python环境里装依赖一定要用虚拟环境。cd ~/ranking python3 -m venv venv source venv/bin/activate pip install requests beautifulsoup4 lxml pandas第二个坑是crontab里的环境变量。和终端里运行不同crontab执行任务时默认的PATH很精简很可能找不到python3或者找到的不是同一个环境。所以定时任务里必须写绝对路径0 9 * * 1 cd /home/user/ranking /home/user/ranking/venv/bin/python run_pipeline.py --sources qs the logs/run.log 21这里每周一早上9点跑一次只更新QS和THE这两个变动较快的榜单。日志重定向到logs/run.log出问题时可以回头翻记录。4. 常见问题排查与避坑实录4.1 反爬拦截与请求频率控制抓2022年数据时US News页面请求稍微频繁一点就容易触发安全验证返回状态码直接变成403或者跳转到验证页。这时候首先要做的不是上代理而是检查请求头是否完整。有些页面会校验Referer字段总之把浏览器的标准请求头带齐能挡掉大部分误杀。另一个更重要的习惯是控制请求间隔。我写了个最简单的随机延时import time import random time.sleep(random.uniform(1, 3))提示抓取公开数据时尤其要注意“礼貌抓取”。每秒几十个请求对个人数据整理任务来说完全没有必要反而容易把自己的IP封掉。数据量不大时慢一点反而更稳。如果页面确实走AJAX动态渲染静态请求抓不到表格就考虑 Selenium。但要注意的是Selenium需要装驱动且要匹配浏览器的版本不是装上就万事大吉。能用静态解析搞定就尽量别上浏览器效率差很多。4.2 表格解析异常和中文乱码问题四大排名页面里最常遇到的解析问题是定位到了错误的表格。一个页面里除了主排名表往往还有某个国家的子榜单、赞助商Logo列表等表格。如果代码傻傻地取find_all(table)[0]拿到的很可能不是想要的数据。我的排查思路分两步先打印len(tables)确认页面里到底有几张表再遍历每个表格打印表头那一行人工确认哪个才是目标表。定位目标表时用表头关键字匹配比如包含“Rank”和“University”列的就是主表。中文乱码问题出现在两层一是网页源代码本身编码乱了二是CSV落盘后Excel打开乱码。前者用resp.apparent_encoding重新探测编码绝大多数情况能解决。后者则是写CSV时没有指定encodingutf-8-sig这个问题在项目里第一次跑数据时就出现过一眼扫过去全屏乱码还以为是抓回来的网页本身有问题后来排查到是Excel默认用ANSI打开UTF-8文件导致的。4.3 常见报错速查表报错信息原因解决办法ModuleNotFoundError: No module named requests依赖没安装或用错Python环境先确认在venv内再执行 pip install -r requirements.txtAttributeError: NoneType object has no attribute text选择器没匹配到目标节点把页面保存下来检查HTML结构确认类名或表头关键字是否被改动UnicodeDecodeError文件读写编码不一致统一用 utf-8 / utf-8-sig不要依赖系统默认编码WebDriverException: Message: chromedriver executable needs to be available没装驱动或版本不匹配下载和Chrome版本一致的driver放到PATH路径下ValueError: cannot convert float NaN to integer把含空值的列直接转int先用 pd.to_numeric(errorscoerce) 清洗再转 Int64前三个问题我在这个项目里都实测碰到过。特别是第一个很多人明明pip install requests成功脚本却还是报 ModuleNotFoundError十有八九是系统里有多个Python版本pip装到了一个环境运行脚本时又用了另一个环境的解释器。这种情况用which python3和which pip对比一下路径就能定位。最后一个问题也很有代表性把含缺失值的列直接转整数类型会直接抛异常之所以用Int64而不是普通的int就是Pandas里专门用来容纳缺失值的整数类型。最后分享一个我个人的实操体会这类排名数据整理脚本真正难的从来不是Python语法而是对数据源规则的理解。我一开始没看US News的列结构就急着写解析器结果把排名和得分列搞反了清洗后才发现数据对不上只能重新抓一遍。建议所有第一次做这个项目的人先手动下载任意一个榜单的原始页面观察十分钟再动手写代码。另外这四套脚本做好之后后续换年份基本只需要改--year参数架构的扩展成本非常低。如果你想拿大学排名做分析但不想折腾抓取也可以直接用官网提供的Excel或CSV下载入口脚本只承担清洗和标准化的活也能省下不少维护精力。本文还有配套的精品资源点击获取