Lab 12Part 5 健壮性与真实世界AI Level 1
真实文本数据处理
这次实验给你一份故意做脏的数据——有编码问题、有格式不一致的行、有缺失字段。你的任务是把它处理干净,并且不崩溃。
截止:小作业 4 · 12 月 8 日 周二 23:59
本页目录
本次目标
- 用
with和显式编码安全地读写文件 - 处理格式不规整的真实数据,跳过坏行而不崩溃
- 用正则从半结构化文本里提取字段
- 让随机实验可复现
准备数据
先运行这段代码生成本次实验用的文件:
import random
from pathlib import Path
random.seed(1602) # 固定种子,保证所有人拿到同一份数据
Path("/tmp/lab12").mkdir(exist_ok=True)
names = ["alice", "bob", "carol", "dave", "eve"]
levels = ["INFO", "WARN", "ERROR"]
lines = []
for i in range(200):
ts = f"2026-11-{random.randint(1, 28):02d} {random.randint(0,23):02d}:{random.randint(0,59):02d}:{random.randint(0,59):02d}"
lvl = random.choices(levels, weights=[6, 3, 1])[0]
user = random.choice(names)
ms = random.randint(1, 3000)
lines.append(f"{ts} {lvl} user={user} action=login duration={ms}ms")
# 故意插入 12 行坏数据
for _ in range(12):
bad = random.choice([
"", # 空行
"### 这是一行注释", # 注释
"2026-11-05 时间戳后面什么都没有", # 字段缺失
"2026-11-06 12:00:00 DEBUG user= action=login duration=abc", # 用户名空、时长非数字
])
lines.insert(random.randint(0, len(lines)), bad)
Path("/tmp/lab12/app.log").write_text("\n".join(lines), encoding="utf-8")
print("已生成 /tmp/lab12/app.log,共", len(lines), "行")
题目
12-1 编码实验
写一段代码回答下面三个问题,把代码和输出都放进提交说明:
"计算导论"用 UTF-8 编码是几个字节?用 GBK 是几个字节?为什么不一样?- 把 UTF-8 编码的
"你好"用latin-1解码,会得到什么?把结果再用latin-1编码回去,能还原成原来的字节吗? - 把 UTF-8 编码的
"你好"用ascii解码会怎样?和第 2 问的失败方式有什么不同?
12-2 安全地读文件
实现:
def read_lines(path: str) -> list[str]:
"""读入文件的所有行,去掉行尾换行符。
文件不存在时抛出 FileNotFoundError,并附上有用的信息。
"""
要求:用 with、显式 encoding="utf-8"、不要用 readlines() 之后再处理(直接逐行遍历)。
在提交说明里回答:如果这个文件有 10GB,你的实现会有什么问题?
12-3 解析日志
实现:
def parse_line(line: str) -> dict[str, str] | None:
"""解析一行日志,返回 {date, time, level, user, action, duration} 的字典。
无法解析时返回 None。
"""
日志的正常格式是:
2026-11-05 14:23:45 INFO user=alice action=login duration=1234ms
用正则加命名分组实现。duration 去掉 ms 后缀,作为字符串返回即可。
然后:
def load_log(path: str) -> tuple[list[dict[str, str]], list[tuple[int, str]]]:
"""返回 (成功解析的记录列表, [(行号, 原始内容)] 的坏行列表)。"""
在提交说明里报告:总共多少行?成功解析多少行?坏行有哪些?
12-4 统计
基于 12-3 的结果,实现三个统计函数:
def count_by_level(records: list[dict[str, str]]) -> dict[str, int]: ...
def busiest_user(records: list[dict[str, str]]) -> str: ...
def average_duration(records: list[dict[str, str]]) -> float: ...
average_duration 要注意:duration 是字符串,转换可能失败。失败的记录该算进平均还是跳过? 你决定,但要在提交说明里说明。
12-5 输出成 CSV
把 12-3 解析出的记录写成一个 CSV 文件 /tmp/lab12/parsed.csv,含表头。
要求用 csv 模块(不是手写 join(",")),并且加 newline=""。
然后再用 csv.DictReader 读回来,验证记录数一致。把验证代码和输出贴进提交说明。
12-6 正则提取
给定下面这段文本,写正则提取三类信息:
text = """
联系方式:张三 zhangsan@sjtu.edu.cn 13812345678
李四的邮箱是 lisi.wang@example.com,电话 15900001111
王五:wangwu@qq.com / 13700002222
无效的:not-an-email@ 和 123456
"""
- 所有邮箱地址
- 所有 11 位手机号(1 开头)
- 所有邮箱的域名部分(
@后面的)
在提交说明里写出你的三个正则,并说明你怎么确认它没有误匹配”无效的”那一行。
12-7 可复现的模拟
写一个函数模拟”生日悖论”:
def birthday_collision(n_people: int, trials: int, seed: int | None = None) -> float:
"""模拟 trials 次,每次随机给 n_people 个人分配生日(1-365),
返回出现至少两人同生日的比例。
"""
用它算出 n_people 从 20 到 30 时的碰撞概率,找出概率首次超过 50% 的人数。
要求:同一个 seed 必须给出完全相同的结果。 在提交说明里贴两次运行的输出证明这一点。
提交前自查
- 12-1 三个问题都有代码和输出
- 12-2 用了
with和显式编码,回答了 10GB 的问题 - 12-3 用正则命名分组,报告了坏行
- 12-3 程序在整份脏数据上没有崩溃
- 12-4 说明了失败记录的处理策略
- 12-5 用了
csv模块和newline="" - 12-6 三个正则都有,并说明了怎么排除误匹配
- 12-7 贴了两次运行结果证明可复现
- AI 使用声明写了
- 个人项目 B 已提交