本项目的目的是将牛客(NowCoder)导出的比赛榜单转换为另一个项目 MergeResolver (https://github.com/SXUas-acm-team/MergeResolver) 需要的榜单格式。 在 MergeResolver 中,理想的工作流是由牛客管理员导出带有用户 id 的榜单文件;但在实际情况中,比赛组织者并不总能及时获得带 id 的导出文件。所以本项目提供一组脚本,用以:
- 读取比赛组织者从牛客导出的榜单(Excel),并结合保存的排行榜 HTML 页面解析/匹配出用户 id;
- 生成包含用户 id 的榜单和用户 id 列表,便于后续在 MergeResolver 中使用;
- 下载用户头像并按竞赛数据包规范重命名/组织图片(便于滚榜时显示学校/队伍标识)。
输入说明:
- 本项目的输入 Excel 是比赛举办者在比赛结束后从牛客上导出的榜单文件(由组织者在牛客管理后台导出或通过“导出”功能生成),请将该文件放在
Input/目录下以供脚本读取。
头像与校徽处理:
- 为了在滚榜或合并榜单时显示学校/队伍标识,本项目支持下载用户头像并按 ICPC tools 的竞赛数据包规范组织图片:
- 校徽(组织标识 / logo)应存放为:
organizations/<id>/logo<宽>x<高> - 选手/队伍照片应存放为:
teams/<id>/photo
- 校徽(组织标识 / logo)应存放为:
- 因此仓库中提供了两个相关脚本:
get_user_avatar.py(抓取并保存用户头像)和rename_logos.py(批量重命名/转换图片以匹配上述命名规范)。详见下面的功能说明。
- 从本地保存的牛客排行榜 HTML 文件中解析并提取用户昵称 -> 用户ID 的映射(支持多个 HTML 文件)
- 如果仓库中不存在
config.json,脚本会自动创建一个配置模板并退出,提示用户编辑后重新运行;配置中的文件名无需包含Input//Output/前缀,脚本会自动添加。 - 读取比赛组织者导出的 Excel 文件(兼容多种读取引擎:pandas 默认、xlrd、openpyxl)并根据昵称等字段做模糊匹配以尝试填充用户ID。
- 支持对列名进行宽松匹配(例如昵称列会尝试匹配
昵称/昵称名称/nick等变体;真实姓名匹配真实姓名/真实名称/姓名;学校匹配学校/院校/单位等)。
- 支持对列名进行宽松匹配(例如昵称列会尝试匹配
- 生成一个精简的输出表,包含列:
用户ID、昵称、真实姓名、学校(以output_file为名保存为 Excel,并同时导出为 UTF-8 带 BOM 的 CSV)。 - 输出去重后的
user_ids.txt(每行一个用户ID)以供get_user_avatar.py下载头像使用;同时会生成not_found_users.txt列出未匹配到 ID 的昵称以便人工核查。
- 从用户ID清单文件读取所有用户ID
- 访问牛客用户个人主页
- 自动解析并下载用户头像
- 保存为
avatars/<用户ID>/photo.png
- 批量重命名/转换图片文件
- 按图片尺寸重命名为
logo<宽>x<高>.png - 支持递归处理、预览模式、删除原文件等选项
确保已安装 Python 3.7+,然后运行:
python -m pip install -r requirements.txt依赖包括:
pandas- Excel/CSV 数据处理openpyxl/xlrd- Excel 文件读写beautifulsoup4- HTML 解析requests- HTTP 请求Pillow- 图片处理
Crawler2.0/
├── Input/ # 输入文件目录
│ ├── rank_page_1.html
│ ├── rank_page_2.html
│ ├── rank_page_3.html
│ └── input.xls
├── Output/ # 输出文件目录
│ ├── output.csv
│ ├── user_ids.txt
│ ├── not_found_users.txt
│ └── avatars/
│ └── <用户ID>/
│ └── photo.png
├── config.json
├── get_user_id.py
├── get_user_avatar.py
├── rename_logos.py
└── README.md
目录说明:
Input/- 存放所有输入文件(HTML 文件和 Excel 文件)Output/- 存放所有输出文件(生成的 CSV、用户ID清单、头像等)
首次运行 get_user_id.py 时,如果不存在 config.json,会自动创建配置模板。
{
"local": {
"html_files": [
"rank_page_1.html",
"rank_page_2.html",
"rank_page_3.html"
],
},
"files": {
"input_file": "input.xls",
"output_file": "output.csv",
"user_id_list": "user_ids.txt",
"not_found_users": "not_found_users.txt",
"avatar_dir": "avatars"
}
}运行时行为:如果
config.json不存在,get_user_id.py会自动创建一个配置模板文件并退出(脚本会提示已创建配置文件,请编辑后重新运行)。请在首次运行前检查并修改config.json中的文件名设置。
| 配置项 | 说明 |
|---|---|
html_files |
排行榜 HTML 文件名列表(保存在 Input/ 目录中) |
input_file |
输入的 Excel 文件名(位于 Input/ 目录) |
output_file |
输出的 Excel 文件名(保存到 Output/ 目录) |
user_id_list |
用户ID清单文件名(保存到 Output/ 目录) |
not_found_users |
未找到用户清单文件名(保存到 Output/ 目录) |
avatar_dir |
头像保存目录名(相对于 Output/ 目录) |
注意:配置文件中只需填写文件名,无需包含
Input/或Output/前缀。程序会自动将输入文件从Input/目录读取,输出文件保存到Output/目录。
- 在浏览器中打开牛客竞赛排行榜页面
- 右键 → "另存为" → 保存为完整网页(HTML)
- 将 HTML 文件命名为
rank_page_1.html、rank_page_2.html等 - 放置在
Input/目录中
- 登录牛客(NowCoder)并进入对应比赛的管理页面,导出排名名单(需要比赛管理员权限)。
- 导出 Excel 文件,保存到本仓库的
Input/目录下,建议命名为input.xls或类似清晰名称。
注意:本项目目前只能处理特定导出格式的牛客榜单。要求导出的 Excel 必须包含报名时收集的完整用户信息。当前支持的列(按顺序或按列名匹配)至少应包含:
- 排名
- 团队
- 昵称
- 真实名称
- 学历
- 邮箱
- 手机号码
- 学校
- 毕业年份
- 学号
- 专业班级
- 性别
- 备注
- 通过题数
- 罚时
- 题目列(例如:A、A-相似度、B、B-相似度 ...)
也即,导出的表头应类似:
排名 团队 昵称 真实名称 学历 邮箱 手机号码 学校 毕业年份 学号 专业班级 性别 备注 通过题数 罚时 A A-相似度 B B-相似度......
这是因为脚本依赖这些字段来在导出的榜单与保存的排行榜 HTML 页面之间做严格匹配并提取用户 id。如果报名时没有收集这些信息,导出的榜单将不符合本项目目前的解析规则,导致匹配失败或丢失 id。
如果你的导出格式与上述不一致,请手动调整 Excel 表头使其匹配,或联系项目维护者以添加对新格式的支持。
将 Excel 文件放置在 Input/ 目录中。
python get_user_id.pyoutput.csv- CSV 格式(UTF-8 with BOM 编码),包含完整用户信息user_ids.txt- 用户ID清单(每行一个ID,去重)not_found_users.txt- 未找到ID的用户列表
说明与细节:
- 脚本会尝试使用多种 Excel 解析引擎读取文件(以提高兼容性),如果读取失败会抛出相应错误并提示。
-- 输出的
output.csv为脚本生成的精简表,包含列:用户ID、昵称、真实姓名、学校。 user_ids.txt为去重后的用户ID列表(按出现顺序去重),适合作为get_user_avatar.py的输入。- 未匹配到 ID 的昵称会被写入
not_found_users.txt,建议人工核对这些昵称与 HTML 页面或报名信息进行比对后补全。
确保已运行 get_user_id.py 并在 Output/ 目录中生成了 user_ids.txt。
python get_user_avatar.py- 读取
Output/user_ids.txt中的所有用户ID - 逐个访问
https://ac.nowcoder.com/acm/contest/profile/<用户ID> - 解析页面中的头像图片链接
- 下载并保存为
Output/avatars/<用户ID>/photo.png(或 .jpg 等)
- 脚本会在每个请求之间等待 0.5 秒,避免请求过于频繁
- 如果某些用户页面需要登录才能访问,可能需要手动添加 Cookie
- 下载失败的用户会在控制台显示错误信息
[1/50] 处理用户 123456789 ...
已保存头像: Output\avatars\123456789\photo.png
[2/50] 处理用户 987654321 ...
未找到头像链接
...
rename_logos.py 可以批量处理图片,按尺寸重命名为统一格式。
# 查看将要执行的操作(不实际修改文件)
python rename_logos.py <目标目录> --dry-run
# 处理指定目录中的所有图片
python rename_logos.py <目标目录>
# 递归处理子目录
python rename_logos.py <目标目录> --recursive
# 转换后删除原文件
python rename_logos.py <目标目录> --delete-original
# 显示详细处理信息
python rename_logos.py <目标目录> --verbose| 参数 | 简写 | 说明 |
|---|---|---|
target |
- | 目标目录(必需) |
--recursive |
-r |
递归处理子目录 |
--ext |
-e |
指定文件扩展名(逗号分隔,默认:png,jpg,jpeg,gif,bmp,webp) |
--dry-run |
-n |
预览模式,仅打印操作不实际修改 |
--delete-original |
-d |
转换后删除原始文件 |
--verbose |
-v |
显示详细处理信息 |
# 处理 Output/avatars 目录下的所有图片,递归子目录,删除原文件
python rename_logos.py Output/avatars -r -d -v
# 仅处理 Output 目录的 PNG 和 JPG
python rename_logos.py Output -e png,jpg
# 预览将要做的修改
python rename_logos.py Output/avatars --dry-run- 原文件:
Output/avatars/123456/photo.jpg(800×600) - 新文件:
Output/avatars/123456/logo800x600.png
如果目标文件名已存在,会自动添加序号:
logo800x600.png→logo800x600_1.png→logo800x600_2.png...
1. 保存排行榜 HTML 页面
↓
2. 准备输入 Excel 文件
↓
3. 运行 get_user_id.py
├─ 输出: output.csv
└─ 输出: user_ids.txt
↓
4. 运行 get_user_avator.py
└─ 输出: avatars/<用户ID>/photo.png
↓
5. (可选) 运行 rename_logos.py
└─ 输出: avatars/<用户ID>/logo<宽>x<高>.png