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/10 10:15:40

ModOrganizer2 游戏启动失败 解决指南

ModOrganizer2 游戏启动失败 解决指南 【免费下载链接】modorganizer Mod manager for various PC games. Discord Server: https://discord.gg/ewUVAqyrQX if you would like to be more involved 项目地址: https://gitcode.com/gh_mirrors/mo/modorganizer MO2报错是…

作者头像 李华
网站建设 2026/4/17 2:00:24

智能体的商业潜力:百度灵境矩阵如何助力开发者实现变现

智能体经济崛起:百度灵境矩阵如何重塑开发者变现路径 从概念到现金流的智能体革命 凌晨三点的写字楼里,王磊盯着屏幕上跳动的数据曲线露出了微笑——他开发的"法律合同审查助手"在接入百度灵境矩阵平台第47天,单日活跃用户突破8000…

作者头像 李华
网站建设 2026/4/17 7:20:23

3步搞定学术引用:APA格式生成工具全解析

3步搞定学术引用:APA格式生成工具全解析 【免费下载链接】APA-7th-Edition Microsoft Word XSD for generating APA 7th edition references 项目地址: https://gitcode.com/gh_mirrors/ap/APA-7th-Edition 剖析学术引用的痛点与挑战 学术写作中&#xff0c…

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

3大核心功能+5种实用场景:CSDN博客下载器完全指南

3大核心功能5种实用场景:CSDN博客下载器完全指南 【免费下载链接】CSDNBlogDownloader 项目地址: https://gitcode.com/gh_mirrors/cs/CSDNBlogDownloader 一、功能解析:三大下载模式深度解析 用户模式:完整备份个人博客空间 当你需…

作者头像 李华
网站建设 2026/4/16 9:36:59

STM32F103C8T6 HAL库实战:CubeMX配置PWM精准驱动SG90舵机

1. 舵机控制基础与硬件准备 SG90舵机是最常见的微型舵机之一,价格亲民且性能稳定,广泛用于机器人关节、遥控模型等场景。它的工作电压通常在4.8V-6V之间,扭矩约为1.6kgcm,转动角度范围是0-180度。我实测过市面上几种不同厂家的SG9…

作者头像 李华
网站建设 2026/4/15 13:50:24

UEFI启动画面自定义零基础教程:打造专属开机视觉体验

UEFI启动画面自定义零基础教程:打造专属开机视觉体验 【免费下载链接】HackBGRT Windows boot logo changer for UEFI systems 项目地址: https://gitcode.com/gh_mirrors/ha/HackBGRT 你是否曾遇到这样的困扰:每次开机都要面对千篇一律的Windows…

作者头像 李华