作者:jason
日期:2025-07-27
1. 为什么需要“命令行交题”?
刷算法题时,最常见的动作是:
- 本地写完代码
- 打开浏览器 → 登录 → 选语言 → 选文件 → 点击 Submit
- 等待结果 →回到 IDE 继续 Debug
整个链路里有大量机械点击。
如果能把第 2 步压缩成 一条命令,效率立刻翻倍,而且还能:
- 在 Vim/VS Code 里一键绑定快捷键
- CI 里做回归测试,定时把模板题跑一遍
- 比赛时批量交暴力,对拍数据
2. 脚本功能一览
脚本 submit.py 只有 100 行不到,却能:
功能实现方式登录 OJrequests.Session + Cookie自动提取 CSRF-tokenBeautifulSoup 双策略(meta/input)语言缩写自动映射cpp → cc.cc14o2 …提交后返回记录号解析 302 Location3. 运行姿势
- # 把 1000 题的 C++ 代码交上去
- python3 submit.py \
- https://oj.example.com \
- 1000 \
- alice \
- secret \
- ~/a.cpp \
- cpp
复制代码 输出示例:- 提交成功!记录号:123456
- 查看结果:https://oj.example.com/record/123456
复制代码 4. 代码精讲
4.1 参数检查
- if len(sys.argv) != 7:
- print("用法:python submit.py <BASE> <PID> <USER> <PASS> <FILE> <LANG>")
- sys.exit(1)
复制代码
- 位置参数比交互式 input() 更适合脚本化,易与编辑器/CI 集成。
- 7 个参数正好是 BASE PID USER PASS FILE LANG + 脚本本身 sys.argv[0]。
4.2 会话初始化
- s = requests.Session()
- s.headers.update({
- 'User-Agent': 'Mozilla/5.0 ...',
- 'Referer': SUBMIT_URL
- })
复制代码
- Session 自动管理 Cookie,后续请求无需手动带 sid。
- Referer 很多 OJ 会检查,防止 CSRF。
4.3 CSRF-token 双保险
- # 1) meta 标签
- meta = soup.find('meta', attrs={'name': 'csrf-token'})
- ...
- # 2) 隐藏域
- inp = soup.find('input', attrs={'name': re.compile(r'csrf|_csrf')})
- # 3) 兜底 Cookie
- csrf_submit = s.cookies.get('sid.sig', '')
复制代码
- 不同 OJ 的 CSRF 位置不一样,脚本做了 三级回退。
- 如果全部失败,打印页面源码方便 Debug。
4.4 语言映射
- if LANG == 'cpp':
- LANG = 'cc.cc14o2'
- elif LANG in ('python', 'py'):
- LANG = 'py.py3'
复制代码
- 命令行里敲 cpp、py 即可,无需记住评测机内部代号。
- 想支持更多语言,直接加 elif 即可。
4.5 提交与结果
- resp = s.post(SUBMIT_URL, data=payload, allow_redirects=False)
- if resp.status_code == 302:
- rid = resp.headers['Location'].split('/')[-1]
复制代码
- 评测机成功接收后通常 302 到 /record/{rid}。
- 解析 Location 即可拿到记录号,再拼成可点击的 URL。
4.6 完整代码
- 请在写一篇文章
- #!/usr/bin/env python3
- import sys, requests, re
- from bs4 import BeautifulSoup
- def main():
- if len(sys.argv) != 7:
- print("用法:python submit.py <BASE> <PID> <USER> <PASS> <FILE> <LANG>")
- sys.exit(1)
- BASE, PID, USER, PASS, FILE, LANG = sys.argv[1:]
- LOGIN_URL = f"{BASE.rstrip('/')}/login"
- SUBMIT_URL = f"{BASE.rstrip('/')}/p/{PID}/submit"
- # 1. 建立会话
- s = requests.Session()
- s.headers.update({
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
- 'AppleWebKit/537.36 (KHTML, like Gecko) '
- 'Chrome/120.0.0.0 Safari/537.36',
- 'Referer': SUBMIT_URL
- })
- # 2. 首次 GET 登录页 → 取 Cookie 中的 CSRF
- s.get(LOGIN_URL)
- csrf_login = s.cookies.get('sid.sig', '')
- # 3. 登录
- resp = s.post(LOGIN_URL, data={
- 'uname': USER,
- 'password': PASS,
- '_csrf': csrf_login
- }, allow_redirects=False)
- if resp.status_code != 302:
- raise RuntimeError('登录失败')
- # 4. GET 提交页 → 取新的 CSRF
- submit_html = s.get(SUBMIT_URL).text
- soup = BeautifulSoup(submit_html, 'lxml')
- csrf_submit = None
- # 1) meta 标签
- meta = soup.find('meta', attrs={'name': 'csrf-token'})
- if meta:
- csrf_submit = meta['content']
- # 2) 隐藏域
- else:
- inp = soup.find('input', attrs={'name': re.compile(r'csrf|_csrf')})
- csrf_submit = inp['value'] if inp else None
- # 3) 兜底:如果都没有,就直接用 Cookie 里的 sid.sig
- if not csrf_submit:
- csrf_submit = s.cookies.get('sid.sig', '')
- if not csrf_submit:
- print('⚠️ 无法提取 CSRF,页面预览:')
- print(submit_html)
- sys.exit(1)
- # 5. 读源码
- with open(FILE, encoding='utf-8') as f:
- code = f.read()
- # 6. POST 提交
- # 6.1 转换 LANG
- if LANG =='cpp':
- LANG = 'cc.cc14o2'
- elif LANG == 'python' or LANG == 'py':
- LANG = 'py.py3'
- payload = {'_csrf': csrf_submit, 'lang': LANG, 'code': code}
- resp = s.post(SUBMIT_URL, data=payload, allow_redirects=False)
- if resp.status_code == 302:
- rid = resp.headers['Location'].split('/')[-1]
- print(f'提交成功!记录号:{rid}')
- print(f'查看结果:{BASE}/record/{rid}')
- else:
- print('提交失败,状态码', resp.status_code)
- if __name__ == '__main__':
- main()
复制代码 5. 进阶玩法
5.1 VS Code 一键绑定
在 .vscode/tasks.json 里加:- {
- "label": "submit",
- "type": "shell",
- "command": "python3",
- "args": [
- "${workspaceFolder}/submit.py",
- "https://oj.example.com",
- "${fileBasenameNoExtension}",
- "alice",
- "secret",
- "${file}",
- "cpp"
- ],
- "group": "build"
- }
复制代码 写完后 Ctrl+Shift+B 直接交题。
5.2 CI 自动回归
GitHub Actions 每晚跑模板题:- - name: 提交模板题
- run: |
- python3 submit.py https://oj.example.com 1000 bot bot template.cpp cpp
- python3 submit.py https://oj.example.com 1001 bot bot template.py py
复制代码 5.3 多账号/多 OJ 配置
把账号密码写到 .env,脚本里用 python-dotenv 读取,避免明文泄露。
6. 常见坑 & 解决办法
现象原因解决登录 200 不 302密码错误或字段名不对浏览器抓包对比 uname/password/_csrf提交返回 403CSRF 失效确认 Referer,重新 GET 页面语言无效评测机代号变化打开下拉框查看 option value7. 一句话总结
把机械化的“点鼠标”变成可编程的“HTTP 请求”,
你就能在 任何地方、任何工具链 里完成“写完即交”。
脚本虽小,却能把刷题体验提升一个数量级。
Happy hacking,愿每一次 ./submit.py 都是一次愉快的 AC!
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |