Python自动化刷题实战:从页面解析到正则表达式精讲
在CTF竞赛和编程练习中,经常会遇到需要快速计算并提交答案的题目。这类题目往往要求选手在极短时间内完成复杂的数学运算,手动操作不仅效率低下,还容易出错。本文将带你用Python实现一个自动化解题脚本,以经典题目"秋名山车神"为例,深入讲解requests库的使用、正则表达式匹配技巧以及会话保持等关键技术点。
1. 理解题目逻辑与自动化需求
"秋名山车神"这类题目通常具有以下特征:
- 每次刷新页面都会生成新的数学表达式
- 需要在极短时间内提交正确答案(通常3-5秒)
- 提交方式多为POST请求,参数名为value
- 正确答案会返回flag或进入下一关卡
手动解题的痛点显而易见:
- 需要反复刷新页面获取新题目
- 必须快速心算或使用计算器
- 容易因操作延迟而超时
- 重复劳动,无法积累可复用的技术
自动化脚本的价值在于:
- 毫秒级响应,确保在时限内提交
- 100%计算准确率
- 一次编写,永久复用
- 可扩展应用到类似题目场景
2. 环境准备与基础工具
2.1 必要Python库安装
确保已安装以下Python库,若未安装可通过pip命令安装:
pip install requests核心库功能说明:
- requests:处理HTTP请求,支持会话保持
- re:Python内置正则表达式模块,无需额外安装
2.2 开发环境配置建议
推荐使用以下工具组合:
- Python 3.7+(支持f-string等现代语法)
- VS Code + Python插件(智能提示和调试)
- Postman(辅助调试HTTP请求)
- Chrome开发者工具(分析页面结构)
3. 核心脚本实现与逐行解析
3.1 基础版脚本实现
import requests import re url = 'http://114.67.175.224:10053/' s = requests.Session() source = s.get(url) expression = re.search(r'(\d+[+\-*])+(\d+)', source.text).group() result = eval(expression) post = {'value': result} print(s.post(url, data=post).text)关键代码解析:
requests.Session():创建会话对象,自动保持cookiess.get(url):发送GET请求获取题目页面re.search():使用正则表达式提取数学表达式eval():执行字符串形式的数学运算s.post():提交计算结果并打印响应
3.2 增强版脚本实现
import requests import re request = requests.Session() url = "http://114.67.175.224:10053/" response = request.get(url) n = re.findall("<div>(.*?)=", response.text)[0] num = eval(n) data = {"value": num} response1 = request.post(url, data=data) f = re.findall('flag{(.*?)}', response1.text) flag = "flag{" + f[0] +"}" print(flag)改进点分析:
- 更精确的正则匹配
<div>(.*?)=定位题目区域 - 直接提取并拼接flag格式
- 变量命名更具可读性
- 完整展示从获取题目到提取flag的全流程
4. 正则表达式深度解析
4.1 基础匹配模式
原始脚本使用的正则表达式(\d+[+\-*])+(\d+)分解:
\d+:匹配1个或多个数字[+\-*]:匹配+、-或*(注意转义)():分组捕获+:前面的模式重复1次或多次
4.2 高级匹配技巧
增强版脚本使用的<div>(.*?)=解析:
<div>:匹配文字div标签.*?:非贪婪匹配任意字符=:以等号结尾():只保留括号内内容
常见正则表达式模式对比:
| 模式 | 类型 | 匹配行为 | 示例 |
|---|---|---|---|
.* | 贪婪 | 匹配最长可能字符串 | "aabb" → "aabb" |
.*? | 非贪婪 | 匹配最短可能字符串 | "aabb" → "a", "a", "b", "b" |
\d+ | 量词 | 1个或多个数字 | "123" → "123" |
[abc] | 字符集 | 匹配任意指定字符 | "apple" → "a", "p", "p", "l", "e" |
(abc) | 分组 | 捕获匹配内容 | "abc" → "abc" |
4.3 正则表达式调试技巧
- 使用在线工具验证(如regex101.com)
- 分步测试复杂表达式
- 注意特殊字符转义
- 优先考虑非贪婪模式
- 合理使用分组捕获关键内容
# 正则测试示例 test_str = "3+5*2-8/4=" pattern = r'(\d+[+\-*/])+(\d+)' match = re.search(pattern, test_str) if match: print(f"完整匹配: {match.group(0)}") print(f"第一部分: {match.group(1)}") print(f"第二部分: {match.group(2)}")5. 脚本优化与错误处理
5.1 异常处理增强
基础脚本缺乏错误处理,可能导致以下问题:
- 网络请求失败
- 正则匹配不到表达式
- 数学表达式执行错误
- 服务器返回异常响应
增强版异常处理:
try: response = request.get(url, timeout=5) response.raise_for_status() n = re.findall("<div>(.*?)=", response.text) if not n: raise ValueError("未找到数学表达式") num = eval(n[0].strip()) data = {"value": num} response1 = request.post(url, data=data, timeout=5) response1.raise_for_status() f = re.findall('flag{(.*?)}', response1.text) if f: print(f"成功获取flag: flag{f[0]}") else: print("未找到flag,响应内容:", response1.text) except requests.exceptions.RequestException as e: print(f"网络请求错误: {e}") except Exception as e: print(f"处理错误: {e}")5.2 性能优化建议
- 设置合理的超时时间(如timeout=5)
- 复用Session对象减少连接开销
- 避免不必要的正则匹配
- 使用连接池提高效率
- 考虑异步请求处理(如aiohttp)
5.3 可配置化改进
将硬编码参数提取为配置变量:
CONFIG = { "base_url": "http://114.67.175.224:10053/", "timeout": 5, "retry_times": 3, "expression_pattern": r"<div>(.*?)=", "flag_pattern": r"flag{(.*?)}", "post_field": "value" }6. 扩展应用与类似题目实战
6.1 适用题目特征识别
以下特征的题目都可采用类似方法:
- 动态生成数学表达式
- 需要快速计算并提交
- 使用POST方法提交答案
- 答案验证在服务端完成
- 有明确的时间限制
6.2 变种题目应对策略
- 多步计算:维护会话状态,顺序处理多个页面
- 验证码干扰:集成OCR识别(如pytesseract)
- JS动态生成:使用Selenium等浏览器自动化工具
- IP限制:配合代理池使用
- 答案加密:分析前端JS加密逻辑
6.3 实战演练:修改脚本应对新题目
假设新题目有以下变化:
- 表达式放在
<span class="expr">标签内 - 需要提交两个字段:answer和token
- token从页面meta标签获取
适配脚本示例:
def solve_new_challenge(): try: s = requests.Session() # 获取初始页面 resp = s.get("http://new.challenge.url/") # 提取表达式和token expr = re.search(r'<span class="expr">(.*?)</span>', resp.text).group(1) token = re.search(r'<meta name="token" content="(.*?)">', resp.text).group(1) # 计算答案 answer = eval(expr) # 构造提交数据 data = { "answer": answer, "token": token } # 提交答案 result = s.post("http://new.challenge.url/submit", data=data) print(result.text) except Exception as e: print(f"Error: {e}")7. 安全注意事项与最佳实践
7.1 eval的安全隐患
直接使用eval执行未知来源的字符串存在严重安全风险:
- 可能执行恶意代码
- 消耗系统资源
- 破坏数据完整性
更安全的替代方案:
import ast import operator def safe_eval(expr): # 允许的操作符 allowed_operators = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv } # 解析表达式 node = ast.parse(expr, mode='eval') # 验证节点类型 if not isinstance(node, ast.Expression): raise ValueError("必须是表达式") def _eval(node): if isinstance(node, ast.Num): # 数字 return node.n elif isinstance(node, ast.BinOp): # 二元操作 left = _eval(node.left) right = _eval(node.right) op = allowed_operators.get(type(node.op)) if op is None: raise ValueError(f"不允许的操作: {type(node.op)}") return op(left, right) else: raise ValueError(f"不允许的节点类型: {type(node)}") return _eval(node.body)7.2 网络请求最佳实践
- 始终验证HTTPS证书
- 设置合理的超时时间
- 限制重试次数
- 不信任任何用户输入
- 敏感信息不硬编码在脚本中
7.3 正则表达式性能优化
- 预编译常用正则表达式
- 避免过度复杂的匹配模式
- 使用非贪婪模式减少回溯
- 合理使用字符集替代分支
- 优先使用具体匹配而非通配符
# 预编译正则示例 EXPR_PATTERN = re.compile(r'<div>(.*?)=') FLAG_PATTERN = re.compile(r'flag{(.*?)}') # 使用预编译模式 expr_match = EXPR_PATTERN.search(response.text)