一行命令自动提交:用 Python 把本地代码秒交评测机

作者:jason
日期:2025-07-27


1. 为什么需要“命令行交题”?

刷算法题时,最常见的动作是:

  1. 本地写完代码
  2. 打开浏览器 → 登录 → 选语言 → 选文件 → 点击 Submit
  3. 等待结果 →回到 IDE 继续 Debug

整个链路里有大量机械点击。
如果能把第 2 步压缩成 一条命令,效率立刻翻倍,而且还能:

  • 在 Vim/VS Code 里一键绑定快捷键
  • CI 里做回归测试,定时把模板题跑一遍
  • 比赛时批量交暴力,对拍数据

2. 脚本功能一览

脚本 submit.py 只有 100 行不到,却能:

功能 实现方式
登录 OJ requests.Session + Cookie
自动提取 CSRF-token BeautifulSoup 双策略(meta/input)
语言缩写自动映射 cppcc.cc14o2
提交后返回记录号 解析 302 Location

3. 运行姿势

# 把 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'
  • 命令行里敲 cpppy 即可,无需记住评测机内部代号。
  • 想支持更多语言,直接加 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
提交返回 403 CSRF 失效 确认 Referer,重新 GET 页面
语言无效 评测机代号变化 打开下拉框查看 option value

7. 一句话总结

把机械化的“点鼠标”变成可编程的“HTTP 请求”,
你就能在 任何地方、任何工具链 里完成“写完即交”。
脚本虽小,却能把刷题体验提升一个数量级。

Happy hacking,愿每一次 ./submit.py 都是一次愉快的 AC!

posted @ 2025-07-27 17:29  爱玩游戏的jason  阅读(61)  评论(0)    收藏  举报