Python自动化刷题实战:从页面解析到正则表达式精讲

在CTF竞赛和编程练习中,经常会遇到需要快速计算并提交答案的题目。这类题目往往要求选手在极短时间内完成复杂的数学运算,手动操作不仅效率低下,还容易出错。本文将带你用Python实现一个自动化解题脚本,以经典题目"秋名山车神"为例,深入讲解requests库的使用、正则表达式匹配技巧以及会话保持等关键技术点。

1. 理解题目逻辑与自动化需求

"秋名山车神"这类题目通常具有以下特征:

  • 每次刷新页面都会生成新的数学表达式
  • 需要在极短时间内提交正确答案(通常3-5秒)
  • 提交方式多为POST请求,参数名为value
  • 正确答案会返回flag或进入下一关卡

手动解题的痛点显而易见:

  1. 需要反复刷新页面获取新题目
  2. 必须快速心算或使用计算器
  3. 容易因操作延迟而超时
  4. 重复劳动,无法积累可复用的技术

自动化脚本的价值在于:

  • 毫秒级响应,确保在时限内提交
  • 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)

关键代码解析:

  1. requests.Session() :创建会话对象,自动保持cookies
  2. s.get(url) :发送GET请求获取题目页面
  3. re.search() :使用正则表达式提取数学表达式
  4. eval() :执行字符串形式的数学运算
  5. 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)

改进点分析:

  1. 更精确的正则匹配 <div>(.*?)= 定位题目区域
  2. 直接提取并拼接flag格式
  3. 变量命名更具可读性
  4. 完整展示从获取题目到提取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 正则表达式调试技巧

  1. 使用在线工具验证(如regex101.com)
  2. 分步测试复杂表达式
  3. 注意特殊字符转义
  4. 优先考虑非贪婪模式
  5. 合理使用分组捕获关键内容
# 正则测试示例
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 性能优化建议

  1. 设置合理的超时时间(如timeout=5)
  2. 复用Session对象减少连接开销
  3. 避免不必要的正则匹配
  4. 使用连接池提高效率
  5. 考虑异步请求处理(如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 适用题目特征识别

以下特征的题目都可采用类似方法:

  1. 动态生成数学表达式
  2. 需要快速计算并提交
  3. 使用POST方法提交答案
  4. 答案验证在服务端完成
  5. 有明确的时间限制

6.2 变种题目应对策略

  1. 多步计算 :维护会话状态,顺序处理多个页面
  2. 验证码干扰 :集成OCR识别(如pytesseract)
  3. JS动态生成 :使用Selenium等浏览器自动化工具
  4. IP限制 :配合代理池使用
  5. 答案加密 :分析前端JS加密逻辑

6.3 实战演练:修改脚本应对新题目

假设新题目有以下变化:

  1. 表达式放在 <span class="expr"> 标签内
  2. 需要提交两个字段:answer和token
  3. 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执行未知来源的字符串存在严重安全风险:

  1. 可能执行恶意代码
  2. 消耗系统资源
  3. 破坏数据完整性

更安全的替代方案:

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 网络请求最佳实践

  1. 始终验证HTTPS证书
  2. 设置合理的超时时间
  3. 限制重试次数
  4. 不信任任何用户输入
  5. 敏感信息不硬编码在脚本中

7.3 正则表达式性能优化

  1. 预编译常用正则表达式
  2. 避免过度复杂的匹配模式
  3. 使用非贪婪模式减少回溯
  4. 合理使用字符集替代分支
  5. 优先使用具体匹配而非通配符
# 预编译正则示例
EXPR_PATTERN = re.compile(r'<div>(.*?)=')
FLAG_PATTERN = re.compile(r'flag{(.*?)}')

# 使用预编译模式
expr_match = EXPR_PATTERN.search(response.text)

更多推荐