静轾 发表于 前天 16:07

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

作者: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/1234564. 代码精讲

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。
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
    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 py5.3 多账号/多 OJ 配置

把账号密码写到 .env,脚本里用 python-dotenv 读取,避免明文泄露。
6. 常见坑 & 解决办法

现象原因解决登录 200 不 302密码错误或字段名不对浏览器抓包对比 uname/password/_csrf提交返回 403CSRF 失效确认 Referer,重新 GET 页面语言无效评测机代号变化打开下拉框查看 option value7. 一句话总结

把机械化的“点鼠标”变成可编程的“HTTP 请求”,
你就能在 任何地方、任何工具链 里完成“写完即交”。
脚本虽小,却能把刷题体验提升一个数量级。
Happy hacking,愿每一次 ./submit.py 都是一次愉快的 AC!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 一行命令自动提交:用 Python 把本地代码秒交评测机