1. 项目概述:当刷题遇上AI,效率革命如何发生?
作为一名在编程学习和自动化领域摸爬滚打了十多年的老手,我见过太多朋友在“刷题”这道坎上耗费了海量时间。无论是为了准备面试、巩固算法基础,还是参加在线编程竞赛,手动一题一题地敲代码、提交、看结果,这个过程不仅枯燥,效率也极其低下。更头疼的是,遇到卡壳的题目,往往要花大量时间搜索解析,答案质量还参差不齐。最近,随着AI大模型的爆发式发展,我意识到一个绝佳的机会来了:为什么不把Python的自动化能力和AI大模型的智能解析能力结合起来,打造一个属于自己的“刷题神器”呢?
这个“Python自动化刷题神器”的核心构想,就是利用Python脚本自动化完成从题库获取、题目解析、代码生成到提交验证的全流程,并引入AI大模型作为“超级外脑”,在遇到难题时提供高质量的思路点拨甚至代码示例。它解决的不仅仅是“刷”的动作,更是“学”的效率问题。适合所有正在通过刷题提升编程能力的学习者,尤其是那些时间有限、渴望高效突破瓶颈的中高级开发者。接下来,我将彻底拆解这个神器的构建思路、技术细节和实操陷阱,手把手带你复现这个能极大提升学习幸福感的生产力工具。
2. 核心架构设计:模块化与智能化的融合
构建这样一个系统,不能一上来就埋头写代码。好的架构是成功的一半。经过多次迭代,我总结出一个稳定、可扩展的四层架构模型。
2.1 总体工作流设计
整个神器的工作流可以抽象为一个智能循环: 获取 -> 分析 -> 求解 -> 验证 -> 归档 。但这只是表象,内核是“人机协同”。我们的目标不是创造一个完全替代人类思考的“做题AI”,那是当前大模型还无法完美胜任的,尤其是在需要严密逻辑和算法优化的竞赛题上。相反,我们的定位是“增强智能助理”。系统自动化处理所有重复性、机械性的工作,如登录网站、抓取题目、格式化输入用例、提交代码并捕获结果。而当遇到难点时,由AI大模型介入,提供解题思路、代码片段或复杂度分析,人类开发者在此基础上进行判断、优化和最终决策。
这种设计有两大优势:一是效率提升显著,将你的时间集中在真正的思考和学习上;二是学习效果更好,AI提供的多种思路可以拓宽你的视野,对比你自己的解法,能产生更深刻的认知。
2.2 技术栈选型与考量
技术选型直接决定了项目的可行性和易用性。下面是我的选择清单及背后的理由:
-
核心自动化与网络交互:Python + Selenium/Playwright + Requests
- Python :毋庸置疑的首选。生态丰富,从网络爬虫、自动化测试到AI接口调用,都有成熟的库,且语法简洁,开发效率高。
-
Selenium/Playwright
:用于模拟浏览器操作。有些刷题平台(如LeetCode)的题目页面和提交界面动态加载严重,简单的
Requests库无法直接获取数据或提交。Playwright是后起之秀,由微软开发,相比Selenium,它的API更现代,速度更快,对现代Web技术的支持更好,且能自动等待元素加载,减少了大量编写等待时间的代码。 我强烈推荐新手从Playwright开始 ,它能让你避开很多异步加载的坑。 -
Requests
:对于API接口规整的平台,直接用
Requests发送HTTP请求是最高效的方式。通常,我们可以通过浏览器的开发者工具(F12)抓取提交代码的API接口,用Requests模拟,这比操作浏览器快得多。
-
AI大脑接入:OpenAI API 或 本地大模型
- OpenAI GPT系列API :这是最省事、效果通常也最好的方案。你需要一个API Key。它的优势是模型能力强(特别是GPT-4),响应速度快,无需关心部署和算力。成本按调用次数和Token数计算,对于个人刷题使用,费用极低。
- 本地大模型 :如果担心网络问题或数据隐私,可以考虑在本地部署开源模型,如Qwen、Llama等。这需要你有一张性能不错的显卡(如RTX 3060 12G以上)。优点是数据完全本地处理,无网络延迟,但需要一定的部署和调试能力,且模型效果可能略逊于顶尖商用API。 对于大多数用户,我建议初期直接使用OpenAI API,快速验证想法,后期有需求再考虑本地化。
-
任务调度与流程编排:原生循环 vs. N8N
- 对于简单的线性流程(刷完A题刷B题),用Python脚本本身的循环和函数调用就足够了。
- 如果你设想的流程非常复杂,例如:定时刷题、根据做题结果动态选择下一题难度、将错题自动整理到Notion数据库等,那么可以考虑引入 N8N 这类可视化自动化工具。它可以通过图形化界面连接各种服务(包括你的Python脚本、AI API、数据库等),适合构建复杂的自动化工作流。但对于核心的“解题”环节,Python脚本仍然是不可替代的。
-
辅助工具库 :
-
BeautifulSoup4/lxml:HTML解析,用于从抓取的页面中提取题目描述、示例等文本信息。 -
Pandas:用于管理你的刷题记录,比如将历史题目、你的代码、AI提供的思路、通过情况等整理成结构化的表格,方便复盘分析。 -
python-dotenv:管理你的敏感配置,如API Key、账号密码,避免硬编码在脚本中。
-
注意:伦理与平台规则红线 :在开始之前,必须严肃声明。自动化工具的使用必须严格遵守目标网站的服务条款(Terms of Service)。我们的目的是 辅助个人学习 ,绝不能用于恶意刷分、攻击服务器或干扰平台正常运营。务必控制请求频率,添加合理的延时(如
time.sleep(random.uniform(2, 5))),模拟人类操作间隔。滥用自动化脚本可能导致账号被封禁,甚至承担法律责任。请务必怀有敬畏之心,将工具用于正途。
3. 核心模块实现与代码拆解
有了架构蓝图,我们来逐一实现核心模块。我会以LeetCode为例进行演示,因为其具有代表性,但思路可平移到其他平台。
3.1 自动化登录与题目抓取模块
首先,我们需要能自动登录并拿到题目。对于LeetCode,我们可以采用混合策略:用Playwright处理登录(因为有图形验证码风险),用Requests获取题目数据(因为其有公开的GraphQL API)。
步骤1:使用Playwright处理登录
from playwright.sync_api import sync_playwright
import time
def leetcode_login(username, password):
with sync_playwright() as p:
# 建议使用Chromium,兼容性最好
browser = p.chromium.launch(headless=False) # 调试时设为False,看到浏览器操作
context = browser.new_context()
page = context.new_page()
page.goto("https://leetcode.com/accounts/login/")
# 等待并填写用户名密码
page.fill('input[name="login"]', username)
page.fill('input[name="password"]', password)
# 这里可能会遇到验证码,如果是简单点选验证码,可以尝试自动处理,复杂时可能需要手动干预一次。
# 更稳妥的做法是首次登录手动进行,然后保存浏览器上下文(cookies)供后续使用。
page.click('button[data-cy="sign-in-btn"]')
# 等待登录成功,可以检查某个登录后才会出现的元素,如用户头像
page.wait_for_selector('img[alt="avatar"]', timeout=10000)
# **关键步骤:保存登录状态(cookies)**
storage = context.storage_state(path="leetcode_state.json")
print("登录成功,状态已保存。")
browser.close()
return "leetcode_state.json"
# 首次运行后,后续就可以直接加载状态,无需重复登录
def get_logged_in_context(playwright, state_path):
browser = playwright.chromium.launch(headless=True) # 正式运行可设为True
context = browser.new_context(storage_state=state_path)
return browser, context
实操心得
:很多平台的登录环节是最不稳定的,因为可能有复杂的验证码。一个取巧的办法是
手动登录一次,然后用工具导出cookies
,在脚本中直接加载cookies来维持登录状态。对于Playwright,就是上面的
storage_state
功能。这能绕过99%的登录难题。
步骤2:通过GraphQL API获取题目详情
LeetCode前端通过GraphQL API获取数据,我们直接模拟这个请求更高效。通过浏览器开发者工具的“网络(Network)”标签,找到获取题目数据的请求(通常包含
graphql
关键字和
operationName
为
questionData
),复制其请求体。
import requests
import json
def fetch_question_data(title_slug, cookies):
"""根据题目的标题slug(如‘two-sum’)获取题目详情"""
url = "https://leetcode.com/graphql"
headers = {
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0 ...',
'Referer': f'https://leetcode.com/problems/{title_slug}/',
}
# 从浏览器复制的GraphQL查询
graphql_query = {
"operationName": "questionData",
"variables": {"titleSlug": title_slug},
"query": """query questionData($titleSlug: String!) {
question(titleSlug: $titleSlug) {
questionId
title
content
difficulty
codeSnippets {
lang
code
}
sampleTestCase
}
}"""
}
response = requests.post(url, json=graphql_query, headers=headers, cookies=cookies)
if response.status_code == 200:
return response.json()['data']['question']
else:
print(f"获取题目失败: {response.status_code}")
return None
这样,我们就拿到了结构化的题目信息,包括纯文本的
content
(题目描述)、
codeSnippets
(各种语言的初始代码模板)和
sampleTestCase
(示例测试用例)。
3.2 AI智能辅助解题模块
这是系统的“智慧”核心。我们的目标不是让AI直接输出最终答案(那样失去了学习意义),而是让它充当教练。
设计AI提示词(Prompt) :提示词的质量直接决定AI回复的效用。一个优秀的提示词应该包含:
- 角色定义 :让AI进入角色,例如“你是一个经验丰富的算法竞赛教练”。
- 清晰指令 :明确告诉AI要做什么,不要做什么。
- 上下文信息 :提供完整的题目描述、输入输出格式、约束条件。
- 输出格式要求 :结构化输出,方便程序解析。
import openai # 或调用本地模型的库
def get_ai_hint(question_data, api_key):
"""
根据题目数据,向AI大模型请求解题思路和复杂度分析,而非完整代码。
"""
client = openai.OpenAI(api_key=api_key)
# 构建精心设计的Prompt
prompt = f"""
你是一位顶尖的算法教练。请针对以下编程题目,提供解题指导,**不要直接给出完整的代码**。
请按以下结构回复:
1. **核心思路**:用一两句话概括解决问题的关键算法或数据结构。
2. **步骤拆解**:分步骤说明如何实现这个思路。
3. **时间复杂度与空间复杂度分析**:给出大O表示法。
4. **可能的陷阱与边界情况**:指出代码中需要注意的特殊情况。
5. **代码框架提示(可选)**:给出关键部分的伪代码或函数签名。
题目信息:
- 标题:{question_data['title']}
- 难度:{question_data['difficulty']}
- 描述:
{question_data['content']}
- 示例测试用例:{question_data['sampleTestCase']}
"""
try:
response = client.chat.completions.create(
model="gpt-4", # 或 "gpt-3.5-turbo",后者成本更低
messages=[
{"role": "system", "content": "你是一个乐于助人且善于教学的算法教练。"},
{"role": "user", "content": prompt}
],
temperature=0.7, # 创造性稍高,以便给出多种思路
max_tokens=800
)
return response.choices[0].message.content
except Exception as e:
return f"请求AI提示时出错: {e}"
注意事项
:调用AI API是异步操作,且可能失败。务必添加异常处理(
try...except
)和重试机制。另外,控制
temperature
参数:值越低(如0.2),输出越确定和保守;值越高(如0.8),输出越有创造性。对于解题思路,可以设得稍高一些以获得不同视角。
3.3 代码生成、测试与提交模块
拿到AI的提示后,你需要自己编写代码。但系统可以帮你自动化测试和提交。
步骤1:本地测试 利用题目提供的示例测试用例,在提交前进行快速验证。我们需要从题目描述中解析出函数签名,并构建一个本地测试环境。
import subprocess
import sys
def run_local_test(code, function_name, test_input, expected_output):
"""
动态生成一个测试脚本,运行并比较结果。
适用于简单题型。对于复杂的数据结构输入,需要更复杂的解析。
"""
# 假设test_input是字符串形式的列表,如 "[2,7,11,15]\n9"
test_script = f"""
{code}
if __name__ == "__main__":
# 这里需要根据具体的输入格式进行解析,这是一个简单示例
import sys
input_data = sys.stdin.read().strip().split('\\n')
nums = eval(input_data[0])
target = int(input_data[1])
result = {function_name}(nums, target)
print(str(result))
"""
try:
process = subprocess.run(
[sys.executable, '-c', test_script],
input=test_input,
text=True,
capture_output=True,
timeout=5 # 设置超时,防止死循环
)
actual_output = process.stdout.strip()
if process.returncode == 0:
return actual_output == str(expected_output), actual_output
else:
return False, f"运行时错误: {process.stderr}"
except subprocess.TimeoutExpired:
return False, "运行超时"
步骤2:自动化提交 对于LeetCode,同样可以找到提交代码的GraphQL接口。
def submit_code(question_id, lang_slug, code, cookies):
"""提交代码到LeetCode"""
url = "https://leetcode.com/graphql"
headers = {'Content-Type': 'application/json', ...}
mutation = {
"operationName": "submit",
"variables": {
"questionId": question_id,
"lang": lang_slug, # e.g., "python3"
"typedCode": code
},
"query": """mutation submit($questionId: String!, $lang: String!, $typedCode: String!) {
submit(questionId: $questionId, lang: $lang, typedCode: $typedCode) {
status
}
}"""
}
response = requests.post(url, json=mutation, headers=headers, cookies=cookies)
# 提交后,需要轮询另一个接口来获取判题结果(runId)
# 这里省略了轮询逻辑,原理是间隔几秒查询一次结果,直到状态不是“PENDING”
踩坑实录
:直接提交的代码必须完全符合平台要求,包括正确的函数名、类名。务必使用从
codeSnippets
里获取的初始模板,在其基础上修改。自己从头写很容易因为细微格式问题导致提交失败。
3.4 数据持久化与学习看板模块
刷题不是一锤子买卖,积累和复盘至关重要。我们可以用Pandas将每次的“战斗记录”保存下来。
import pandas as pd
from datetime import datetime
class ProblemRecorder:
def __init__(self, csv_path='problem_log.csv'):
self.csv_path = csv_path
try:
self.df = pd.read_csv(csv_path)
except FileNotFoundError:
self.df = pd.DataFrame(columns=[
'date', 'problem_id', 'title', 'difficulty',
'my_code', 'ai_hint', 'time_spent', 'status', 'notes'
])
def add_record(self, problem_info, my_code, ai_hint, status='ATTEMPTED'):
new_row = {
'date': datetime.now().strftime('%Y-%m-%d %H:%M'),
'problem_id': problem_info['questionId'],
'title': problem_info['title'],
'difficulty': problem_info['difficulty'],
'my_code': my_code,
'ai_hint': ai_hint,
'time_spent': None, # 可以手动填写或通过计时功能添加
'status': status, # 'PASSED', 'FAILED', 'ATTEMPTED'
'notes': ''
}
self.df = pd.concat([self.df, pd.DataFrame([new_row])], ignore_index=True)
self.df.to_csv(self.csv_path, index=False)
print(f"记录已保存: {problem_info['title']}")
def get_review_list(self, difficulty=None, status='FAILED'):
"""获取需要复习的题目列表"""
mask = self.df['status'] == status
if difficulty:
mask = mask & (self.df['difficulty'] == difficulty)
return self.df.loc[mask, ['title', 'date', 'ai_hint']]
这个简单的记录器,能让你定期回顾错题和难题,结合当时AI给的提示进行针对性复习,学习效果倍增。
4. 系统集成与主控流程
将上述模块串联起来,就形成了主程序。程序逻辑如下:
- 加载配置(账号、API Key、cookies)。
- 从计划列表(可以是一个txt文件,里面存着题目slug)中读取下一道要刷的题。
-
调用
fetch_question_data获取题目详情。 -
在控制台打印题目描述,并调用
get_ai_hint显示AI提供的思路( 建议先自己思考,再看提示 )。 - 开发者根据提示,在喜欢的IDE中手动编写代码。
- 将写好的代码粘贴回程序,或程序读取指定文件。
-
调用
run_local_test进行快速本地验证。 -
如果本地测试通过,调用
submit_code进行在线提交,并轮询获取最终结果。 -
调用
ProblemRecorder.add_record保存本次记录。
# 主函数示例
def main():
config = load_config() # 从.env文件加载配置
recorder = ProblemRecorder()
# 假设有一个待刷题目列表
problem_slugs = ["two-sum", "add-two-numbers", ...]
for slug in problem_slugs:
print(f"\n{'='*50}")
print(f"开始处理题目: {slug}")
# 1. 获取题目
question_data = fetch_question_data(slug, config['cookies'])
if not question_data:
continue
print(f"题目: {question_data['title']} [{question_data['difficulty']}]")
# 这里可以美化输出题目描述,去除HTML标签
# print(clean_html(question_data['content']))
# 2. 获取AI提示(可配置是否启用)
if config['enable_ai']:
print("\n[AI解题提示]")
hint = get_ai_hint(question_data, config['openai_key'])
print(hint)
input("\n阅读完AI提示,请按回车键开始编写代码...")
else:
input("\n请自行思考并编写代码,完成后按回车键继续...")
# 3. 开发者编写代码后,将其放入`my_solution.py`或直接输入
with open('my_solution.py', 'r', encoding='utf-8') as f:
my_code = f.read()
# 4. 本地测试(使用示例用例)
sample_test = question_data['sampleTestCase']
# 这里需要根据具体题目解析出期望输出,示例省略了解析过程
expected_out = parse_expected_output(question_data['content'])
test_passed, actual_out = run_local_test(my_code, "twoSum", sample_test, expected_out)
if test_passed:
print("本地示例测试通过!")
# 5. 在线提交
submit_success = submit_code(question_data['questionId'], 'python3', my_code, config['cookies'])
status = 'PASSED' if submit_success else 'FAILED'
else:
print(f"本地测试失败。输出:{actual_out}")
status = 'FAILED'
# 6. 记录
recorder.add_record(question_data, my_code, hint if config['enable_ai'] else '', status)
# 礼貌性延时,避免请求过快
time.sleep(5)
5. 避坑指南与进阶优化
在实际搭建和运行过程中,你会遇到各种各样的问题。以下是我踩过坑后总结出的核心要点。
5.1 常见问题与排查清单
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 登录失败或状态失效 | Cookies过期;网站反爬策略升级;验证码。 |
1. 重新运行登录函数,更新
storage_state
文件。
2. 在
launch
参数中添加
headless=False
观察手动操作过程。
3. 增加登录后的等待时间,确保页面完全加载。 |
| 抓取题目数据返回空或403 | 请求头(Headers)不完整或被识别为爬虫。 |
1. 使用浏览器开发者工具,完整复制GraphQL请求的Headers,特别是
User-Agent
,
Referer
。
2. 考虑使用
session
对象保持会话,并定期更新cookies。
|
| AI返回无关内容或格式错误 | Prompt指令不够清晰;上下文信息不足。 |
1. 细化Prompt,明确要求“不要输出代码”,并指定输出结构。
2. 在Prompt中提供更详细的输入输出示例。 3. 尝试更换模型(如从gpt-3.5-turbo升级到gpt-4)。 |
| 本地测试通过,在线提交失败 | 函数签名、类名与平台要求不符;全局变量问题。 |
1.
严格使用
从
codeSnippets
获取的初始代码模板。
2. 检查是否在代码中定义了额外的全局变量或打印了调试信息。 |
| 提交后无法获取判题结果 |
轮询接口的
runId
获取错误或状态判断逻辑有误。
|
1. 再次抓取提交后查询结果的网络请求,确认正确的API和参数。
2. 增加轮询间隔和重试次数,处理网络波动。 |
| 脚本运行速度过快被封IP | 请求频率过高,触发了服务器的速率限制。 |
务必在每次网络请求后添加随机延时
:
time.sleep(random.uniform(3, 8))
。模拟人类操作节奏。
|
5.2 进阶优化方向
当基础版本跑通后,你可以考虑以下方向让神器变得更强大:
-
智能选题策略
:不要随机或顺序刷题。可以根据你的历史记录(
ProblemRecorder),分析你在不同难度、不同标签(如“动态规划”、“二叉树”)上的通过率,自动推荐下一道最适合你当前水平的题目,实现个性化学习路径。 -
代码质量分析
:在提交前,不仅做功能测试,还可以集成代码风格检查(如
pylint)、复杂度分析工具,让AI对你的代码进行评审,提出可读性、性能上的改进建议。 - 错题本与智能复习 :基于艾宾浩斯遗忘曲线,让系统定期(如1天后、1周后)自动从错题本中抽取题目让你重新练习,强化记忆。
- 多平台支持 :将平台相关的代码(如LeetCode的API请求)抽象成接口,轻松扩展支持牛客网、Codeforces等其它刷题平台。
-
可视化仪表盘
:使用
matplotlib或plotly将你的刷题数据生成图表,展示每日刷题量、通过率趋势、难度分布等,学习成就感满满。
构建这样一个工具的过程本身,就是一次绝佳的Python全栈项目实践。它涵盖了网络爬虫、自动化、API调用、数据分析和系统设计。更重要的是,它最终服务于你自身能力的提升。工具永远只是辅助,真正的成长来自于你面对AI给出的提示后,自己进行的思考、编码和调试。希望这个详细的指南能帮你打造出属于自己的高效学习引擎。
343

被折叠的 条评论
为什么被折叠?



