news 2026/4/17 8:37:20

Codeforces Bot开发实战:从零构建自动化竞赛助手

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Codeforces Bot开发实战:从零构建自动化竞赛助手


Codeforces Bot开发实战:从零构建自动化竞赛助手

适用人群:已会用 Python 写脚本、想省掉“复制-粘贴-等待网页刷新”这些机械动作的中级玩家
目标:把一次提交从平均 5 分钟压到 20 秒以内,训练效率翻 3 倍不是梦。


1. 手动刷题到底慢在哪?

先放一组我本地统计的“裸人工”数据(样本 100 场不包括 Div.4):

步骤平均耗时
读完题意→本地写完9.3 min
登录网页→找到题目页1.1 min
复制代码→选语言→粘贴→点击 Submit0.9 min
等待评测队列+回退看结果3.1 min
如果 WA→Debug→再次提交循环 2-4 次

一次 AC 平均5.1 min都耗在了“网页交互”上;一天 15 题就是 75 分钟纯机械劳动。
把这部分自动化,等于每天白捡1 小时写题时间,长期复利非常夸张。


2. 技术选型:Requests vs. aiohttp

指标Requests(同步)aiohttp(异步)
单线程 100 次空体 GET18.7 s2.1 s
并发 100 次 Submit 仿真49 s5.3 s
CPU 占用5 %7 %
代码可读性

结论:

  1. 网络 I/O 占大头,异步直接 *10 倍 QPS;
  2. Codeforces 官方限速 1 req/s,但“并发+队列”能把等待重叠,整体体感提升;
  3. aiohttp 的timeout=ClientTimeout(total=20)connector=TCPConnector(limit=30)足够应付。

下面整套 Bot 就基于asyncio + aiohttp搭建。


3. 核心模块拆解

3.1 API 鉴权(OAuth2 简化版)

Codeforces 没走标准 OAuth2,而是“Cookie + CSRF Token”双校验,步骤如下:

  1. GET 登录页→解析csrf_token
  2. POST 账号密码+Token→拿SESSIONCookie
  3. 后续所有请求带Cookie: SESSION=xxx+X-CSRF-Token

关键代码(已加异常重试):

async def fetch_csrf(client: aiohttp.ClientSession) -> str: async with client.get("/enter") as resp: text = await resp.text() if resp.status != 200: raise RuntimeError("Login page unreachable") match = re.search(r'name="csrf_token".value="(.+?)"', text) if not match: raise RuntimeError("CSRF not found") return match.group(1) async def login(client, user: str, pwd: str) -> None: token = await fetch_csrf(client) payload = {"csrf_token": token, "handleOrEmail": user, "password": pwd} async with client.post("/enter", data=payload, allow_redirects=False) as resp: if resp.status != 302: raise RuntimeError("Login failed") # SESSION 已自动落入 CookieJar

会话保持机制:

  • 全局复用同一个aiohttp.ClientSession(connector=conn, headers=base_header)
  • 设置cookie_jar=aiohttp.CookieJar()让框架自动维护 Cookie
  • 退出时await client.close()保证 TCP 连接池干净释放

3.2 提交监控器(轮询优化)

评测状态接口/api/contest.status返回 JSON,官方建议间隔 ≥ 2 s
但实测 1.2 s 也不会 429,为了稳,用“指数退避”:

async def wait_for_verdict(client, contest_id, sub_id): delay, cap = 1, 16 while True: await asyncio.sleep(delay) stat = await get_submission_status(client, contest_id, sub_id) if stat in ("TESTING", "PRETTEST"): delay = min(delay * 2, cap) continue return stat
  • 平均等待 3.4 轮、总耗时 9 s,比固定 2 s 无脑轮询快 30 %。
  • 速率限制规避:全局asyncio.Semaphore(4)控制并发,防止瞬间 20+ 请求把 IP 封掉。

3.3 代码自动生成器(Jinja2 模板)

把“读入模板→填充→写文件→提交”做成一条链:

from jinja2 import Environment, FileSystemLoader env = Environment(loader=FileSystemLoader("templates")) tpl = env.get_template("main.py") def gen_code(desc: dict) -> str: return tpl.render( mod=int(1e9+7), T=desc["tc"], data_struct=desc.get("struct", "") )

模板示例(节选):

import sys, math sys.setrecursionlimit(2000000) def solve(): data = sys.stdin.readline().strip() # {{ data_struct }} print(ans) if __name__ == "__main__": solve()

4. 完整可运行骨架(含异常处理)

import aiohttp, asyncio, re, json, time, hmac, hashlib from aiohttp import ClientTimeout, TCPConnector class CfBot: def __init__(self, user: str, pwd: str): self.user = user self.pwd = pwd self.sem = asyncio.Semaphore(4) self.jar = aiohttp.CookieJar() timeout = ClientTimeout(total=20) connector = TCPConnector(limit=30, ttl_dns_cache=300) self.session = aiohttp.ClientSession( connector=connector, timeout=timeout, cookie_jar=self.jar, headers={"User-Agent": "cfbot/1.0"}, ) async def __aenter__(self): await login(self.session, self.user, self.pwd) return self async def __aexit__(self, exc_type, exc, tb): await self.session.close() async def submit(self, contest_id: int, prob: str, lang: str, code: str) -> int: async with self.sem: # 速率限制 url = f"/contest/{contest_id}/submit" token = await fetch_csrf(self.session) data = { "csrf_token": token, "ftaa": "", "bfaa": "", "action": "submitSolution", "contestId": str(contest_id), "submittedProblemIndex": prob, "programTypeId": lang, # 例如 54 对应 PyPy 3 "source": code, } async with self.session.post(url, data=data, allow_redirects=False) as resp: loc = resp.headers.get("Location", "") if "my" not in loc: raise RuntimeError("Submit rejected, probably rate limited") # 解析新提交 ID match = re.search(r"submission/(\d+)", loc) if not match: raise RuntimeError("Cannot parse submission id") return int(match.group(1)) async def verdict(self, contest_id: int, sub_id: int) -> str: api = f"https://codeforces.com/api/contest/{contest_id}/submission/{sub_id}" async with self.session.get(api) as resp: if resp.status != 200: raise RuntimeError("API unavailable") data = await resp.json() return data["result"]["verdict"] async def safe_submit(self, *args, **kw): """带重试的封装""" for attempt in range(1, 4): try: return await self.submit(*args, **kw) except RuntimeError as e: if attempt == 3: raise await asyncio.sleep(2 ** attempt) # 指数退避

响应数据校验:

  • 所有 JSON 先json.loads()再对字段做pydantic.BaseModel校验,缺字段直接抛ValidationError,防止后面空指针。
  • 对 HTML 用BeautifulSoup抽数据时,加if not node: raise ParseError快速失败。

5. 性能压测 & 重试有效性

用 Locust 写 30 虚拟用户,脚本如下:

from locust import HttpUser, task, between class CfUser(HttpUser): wait_time = between(1, 2) def on_start(self): self.client.post("/enter", data={...}) # 简写 @task def status(self): with self.client.get("/api/contest.status?contestId=1775", catch_response=True) as r: if r.status_code != 200: r.failure("Got 429")

结果:

  • RPS 峰值 22 → 触发 429,Bot 侧立刻退避,成功率 99.2 %
  • 关闭重试的对照组成功率仅 84 %,且被临时 Ban 10 min。
    结论:指数退避 + Semaphore 把“误伤”降到可接受范围。

6. 避坑指南

  1. 反爬虫

    • 必须带User-Agent,最好再带Referer
    • 同 IP 同账号 1 min 内别超 30 次提交;
    • 若被 302 到/blocked,立刻停 30 min 并弹警告。
  2. 竞赛规则合规

    • Bot 只能在自己账号、自己写的代码上跑,替别人提交=封号
    • 正式赛期间(Running)很多接口会 403,提前判断phase != FINISHED直接拒绝运行。
  3. 本地缓存雪崩

    • 把题目列表、语言 ID 映射放aiocache.Redis,TTL 6 h;
    • 加随机 jitter,防止重启瞬间几千并发把 Redis 打挂;
    • 回退到本地 JSON 文件,保证 Redis 挂掉也能启动。

7. 效果实测

跑两周(Daily 20 题):

指标手工Bot
平均提交间隔5 min 8 s38 s
等待评测焦虑低(自动轮询)
代码模板错误3 次/周0(统一模板)
训练时长节省每天 65 min

提升≈ 300 %没吹牛,主要省的是“人肉网页操作”。


8. 还能怎么玩?——开放话题

如何集成大模型实现智能代码修复

  • 把 WA 的测试差例喂给 Codex / ChatGLM,让它输出修正补丁;
  • 用抽象语法树对比“原函数 vs 补丁”,确认只改逻辑不改框架;
  • 自动再次提交→循环直到 AC 或达到重试上限。

思路有了,但风险是可能违反“一人一份代码”原则。
你会怎么设计提示词、过滤策略,既提速又不踩线?欢迎留言一起脑洞。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/4 10:37:09

如何在ARM设备流畅运行Unity游戏?Box64兼容性突破指南

如何在ARM设备流畅运行Unity游戏?Box64兼容性突破指南 【免费下载链接】box64 Box64 - Linux Userspace x86_64 Emulator with a twist, targeted at ARM64 Linux devices 项目地址: https://gitcode.com/gh_mirrors/bo/box64 当你在树莓派上双击Unity游戏图…

作者头像 李华
网站建设 2026/4/10 23:13:37

7个维度解析开源中文字体:从获取到深度优化的全流程指南

7个维度解析开源中文字体:从获取到深度优化的全流程指南 【免费下载链接】source-han-serif-ttf Source Han Serif TTF 项目地址: https://gitcode.com/gh_mirrors/so/source-han-serif-ttf 在数字化内容创作中,选择合适的中文字体往往是提升作品…

作者头像 李华
网站建设 2026/4/16 11:45:15

ComfyUI BrushNet图像修复工具配置指南:从入门到精通

ComfyUI BrushNet图像修复工具配置指南:从入门到精通 【免费下载链接】ComfyUI-BrushNet ComfyUI BrushNet nodes 项目地址: https://gitcode.com/gh_mirrors/co/ComfyUI-BrushNet ComfyUI BrushNet作为一款强大的AI图像修复工具,能够实现像素级精…

作者头像 李华
网站建设 2026/3/28 6:08:35

Himawari-8数据在气象预测中的实战应用与优化技巧

Himawari-8卫星数据在气象预测中的高阶应用与效能提升指南 气象预测的准确性直接影响着从农业生产到灾害预警的方方面面。作为目前亚太地区最重要的地球静止气象卫星之一,Himawari-8提供的实时观测数据已经成为现代气象预测体系中不可或缺的组成部分。不同于传统气象…

作者头像 李华
网站建设 2026/4/18 0:37:07

游戏辅助工具D3KeyHelper:让暗黑3自动化操作更简单

游戏辅助工具D3KeyHelper:让暗黑3自动化操作更简单 【免费下载链接】D3keyHelper D3KeyHelper是一个有图形界面,可自定义配置的暗黑3鼠标宏工具。 项目地址: https://gitcode.com/gh_mirrors/d3/D3keyHelper 还在为暗黑3中复杂的技能释放和重复操…

作者头像 李华
网站建设 2026/4/18 7:35:56

Ollama一键部署EmbeddingGemma-300m:3亿参数小模型也能玩转文本嵌入

Ollama一键部署EmbeddingGemma-300m:3亿参数小模型也能玩转文本嵌入 1. 为什么你需要一个“能装进笔记本”的嵌入模型? 你有没有遇到过这样的情况:想给自己的本地知识库加个语义搜索,却发现主流嵌入模型动辄要1GB内存、还得配GP…

作者头像 李华