Python 解析 IT 运维与信息安全月报:指标抽取与阈值告警

发布时间:2026/9/17 13:43:49
Python 解析 IT 运维与信息安全月报:指标抽取与阈值告警
简介这份《IT运维及信息安全月报》PDF面向IT运维与信息安全岗位人员、运维主管及安全合规管理者提供按月汇总运维动态与安全态势的汇报模板。内容覆盖云平台资源利用、系统健康度、网络线路与应用系统排查、主机感染及病毒处置、入侵防御系统与Web应用防火墙日志、威胁与漏洞等级统计等板块并给出病毒危害等级定义便于团队对照填写和向上汇报。资源包仅含1个pdf文档大小约428KB属于轻量级文档资料下载后可直接查阅或作为月报框架复用。目前已有1034人学习下载。读者可借鉴其中的指标口径、图表组织方式和问题处置建议快速搭建从资源分配、风险监控到攻击阻断分析的完整月报结构减少重复制表与汇报整理成本。1. 一份 PDF 月报里藏着多少可被机器读取的运维指标很多人拿到《IT运维及信息安全月报.pdf》的第一反应是「这不就是个给领导看的文档吗」但真正把它拆开看里面几乎每一句话都是可结构化的指标虚拟主机 97 台、CPU 分配比例 94.71%、内存 1.62TB 分配 1.54TB、WAF 特征规则命中 15458 次、恶意攻击占 44.25%、失陷主机 0 台、病毒感染 0 例、威胁等级 1 级。这些数字放在 PDF 里是静态的抽出来做成时间序列它立刻变成一套轻量级的运维与信息安全健康度看板。这篇内容面向两类人一是需要按月出这份报表的运维工程师和信息安全工程师二是想把 PDF 里的数据自动抽取、比对、预警的技术人员。往下会先讲清这类月报的指标口径和分级标准再落到用 Python 把 PDF 解析成结构化数据最后给出趋势比对和阈值触发的具体做法中间所有代码都能直接跑。2. 月报指标体系与病毒危害分级的口径拆解要把 PDF 变成数据第一步不是写代码而是先明确每个数字的定义边界。口径不统一抽取出来的数字越多越乱。2.1 云平台资源三项核心指标的计算方式月报里云平台部分给了三组数虚拟主机数量、CPU 核数分配比、内存分配比另外单列了存储使用率。注意它们的算法并不一样指标分子分母本月数值含义虚拟主机占用率已开通 97 台授权上限 100 台97%授权容量触顶需扩容 licenseCPU 分配比例已分配 591 核总核数 624 核94.71%分配率非实际负载内存分配比例已分配 1.54TB总数 1.62TB94.74%分配率非实际占用存储使用率已用 9.32TB总数 40TB23.30%使用率非分配容易被误读的是「分配比例」和「使用率」。CPU 分配 94.71% 不代表 CPU 已经跑满 94%它只说明这些核已经划给了虚拟机实际负载要看监控系统的利用率曲线。而存储这里写的是剩余 30.68TB、使用率 23.30%用的是「已用/总量」的算法。同一个 PDF 里两种口径并存抽数据时必须分别处理否则做趋势图时会出现明显的量纲混乱。我一般会在解析配置里给每个指标打一个metric_type标签值取allocation或utilization后续画图和告警分开走。提示月报里「建议增加 50 个云堡垒机 license」这类结论是人工写的不要尝试自动抽取它是判断逻辑的输出结果不是原始指标。2.2 病毒危害 1 到 5 级的判定逻辑月报末尾的病毒危害等级定义表是整份文档里最值得工程化的一段。它用颜色和等级两套编码1 级蓝色弱仅本机单一平台传播对系统无影响或影响微弱2 级绿色弱局域网或子网段传播或多平台混合造成程序异常和部分网络资源消耗3 级黄色中具备有限互联网传播能力造成系统软件崩溃或大量耗用网络资源4 级橙色强具备主动传播攻击能力或两种弱互联网传播方式的蠕虫造成数据丢失或网络堵塞5 级红色高三种以上中传播能力或两种强传播能力的蠕虫造成大面积数据丢失或阻断通信。这份月报连续给出「严重等级均为 1 级」「无重大威胁」意味着所有检出项都落在这张表的最低档。从工程角度这套分级可以直接映射成告警级别1 到 2 级走日报汇总3 级触发工单4 到 5 级直接电话通知。很多团队的月报只是把等级抄一遍没有把它变成阈值这是最可惜的地方。等级定义本身是稳定的可以做成一张字典表让 PDF 解析出来的原始等级字符串去查表得到数值和告警策略。2.3 信息安全侧的几类日志口径月报里的安全数据分四块每块来源不同解析难度也不同IPS 日志会话监控、瞬时接口实时流量、7 日威胁排行、7 日应用排行主要威胁是恶意扫描且全部阻断缓冲区溢出策略为阻断WAF 日志启用特征防护规则 15458 次恶意攻击 44.25%防爬虫 24.28%其他 31.47%全部阻断主机病毒感染案例 0 例虚拟机感染 0 台未发现恶意软件漏洞与威胁严重等级均为 1 级无重大漏洞。这里有个值得注意的比例问题。WAF 三项加起来正好 100%说明它是按攻击类型对命中总量做的归一化。而 15458 次是「启用特征防护规则」的次数不等于攻击次数也不等于告警次数。做同比时可以拿它做基数但不要把它当成攻击强度指标——一个扫描器慢速扫一遍就可能刷出很大量。真正体现强度的是恶意攻击的占比变化。把 44.25% 这个数按月拉出来如果连续三个月上升即使总次数不变也说明攻击结构在恶化。3. 用 Python 解析 PDF 并抽取月报关键字段口径清楚了接下来把它变成能跑的东西。月报是带表格和图片的版式文档解析策略要分两条线文字用文本层抽表格用表格识别抽图片只能靠图注和上下文对齐。3.1 环境准备与 PDF 文本层探测先判断这份 PDF 有没有文本层。很多月报是从 Word 导出再打印成 PDF 的文字是真实的可以直接抽如果是扫描件就得走 OCR。# 建虚拟环境装解析依赖 python -m venv venv source venv/bin/activate pip install pdfplumber pymupdf pandas openpyxl选型上我一般用 pdfplumber 处理表格和文字它的坐标信息比 PyPDF2 完整得多pymupdf 负责快速读取整页文本和渲染页面成图速度更快。用下面这段先做探测import fitz # pymupdf def probe_text_layer(path: str) - dict: doc fitz.open(path) report {pages: doc.page_count, has_text: False, sample: } for page in doc: text page.get_text(text).strip() if len(text) 20: # 单页文字超过20字符视为有文本层 report[has_text] True report[sample] text[:200] break doc.close() return report print(probe_text_layer(IT运维及信息安全月报.pdf))get_text(text)返回按阅读顺序排列的纯文本page_count是总页数。如果has_text为 False说明是扫描件得换 OCR 方案本文后续步骤全部基于有文本层的情况。sample打印前 200 字符用来肉眼确认抽取顺序是否正确——有些 PDF 的文本层是乱序的按坐标重排才行。3.2 用正则从文本层抓取指标数值月报的文字有一定规律比如「已开通 97 台虚拟主机」「CPU 总核数 624 核」「已分配虚拟主机核数 591 核」。用带分组名的正则可以稳定抽出。import re PATTERNS { # 虚拟主机数量匹配“已开通 97 台虚拟主机” vm_count: r已开通\s*(\d)\s*台虚拟主机, # CPU 总核数 cpu_total: rCPU\s*总核数\s*(\d)\s*核, # 已分配核数 cpu_alloc: r已分配虚拟主机核数\s*(\d)\s*核, # 内存总数单位 TB mem_total: r内存总数\s*([\d.])\s*TB, # 已分配内存 mem_alloc: r已分配内存空间\s*([\d.])\s*TB, # 存储使用率 storage_util: r使用率\s*([\d.])%, } def extract_metrics(text: str) - dict: result {} for key, pattern in PATTERNS.items(): m re.search(pattern, text) result[key] float(m.group(1)) if m else None # 抽不到记 None不抛异常 return result full_text \n.join(page.get_text(text) for page in fitz.open(IT运维及信息安全月报.pdf)) print(extract_metrics(full_text))每个正则有两点要盯一是\s*允许数字和中文之间出现空格换行因为 PDF 抽取常在字符间插空白二是抽不到时返回None而不是抛异常月报每期措辞可能微调硬失败会让整个流程断掉。跑完拿到的字典可以直接写进数据库或者拼成一行 CSV。如果某个字段长期为None说明措辞变了再去调对应正则而不是重写整个解析器。3.3 用 pdfplumber 抽表格对齐安全数据WAF 的威胁类型分布和危害等级定义是表格结构正则抽不稳改用 pdfplumber 的表格接口。import pdfplumber def extract_tables(path: str) - list: rows [] with pdfplumber.open(path) as pdf: for page in pdf.pages: for table in page.extract_tables(): rows.append(table) # 每个 table 是二维列表 return rows def find_waf_table(tables: list) - dict: out {} for table in tables: flat .join(str(c) for row in table for c in row if c) if 特征防护 in flat or 防爬虫 in flat: for row in table: cells [str(c).strip() if c else for c in row] # 行形如 [恶意攻击, 44.25%] if len(cells) 2 and cells[-1].endswith(%): out[cells[0]] float(cells[-1].rstrip(%)) return out waf find_waf_table(extract_tables(IT运维及信息安全月报.pdf)) print(waf) # {恶意攻击: 44.25, 防爬虫: 24.28, 其他: 31.47}extract_tables()依赖页面里可见的表格线或对齐规则没有线框的表格可能识别为空。这时可以用page.extract_table({vertical_strategy: text, horizontal_strategy: text})按文字位置推断边界。find_waf_table用「特征防护」「防爬虫」这类关键词定位目标表避免拿错表rstrip(%)去掉百分号再转浮点方便后面算同比。3.4 校验三项 WAF 占比之和应为 100解析完必须自检否则错的数据比没有数据更危险。一个简单规则WAF 三类占比之和应接近 100。def check_waf_ratio(waf: dict, tol: float 0.5) - bool: total sum(waf.values()) ok abs(total - 100) tol print(fWAF占比合计 {total:.2f}%校验{通过 if ok else 失败}) return ok check_waf_ratio(waf)tol是容差考虑到四舍五入0.5 个百分点足够。校验不通过通常有两种原因表格列被错切某一行少了一列或者有第四个类别没被关键词匹配到。查的时候直接把extract_tables的原始二维列表打出来对比比盯着结果猜快得多。这一步做完从 PDF 到结构化数据的链路就闭环了。4. 把月度指标做成趋势比对与阈值告警单期数据只能看现状月报的价值在于连续几期摆在一起。这个项目本身是「第 6 期」说明前面已有 5 期正好适合做时间序列。4.1 多期数据落库与结构设计把每期解析结果存成一行字段与指标对齐日期用报表区间结束月份。字段名类型示例说明periodDATE2020-08-31报表月最后一天vm_countINT97虚拟主机数cpu_alloc_ratioDECIMAL(5,2)94.71CPU 分配比mem_alloc_ratioDECIMAL(5,2)94.74内存分配比storage_utilDECIMAL(5,2)23.30存储使用率waf_totalINT15458特征规则命中次数waf_malicious_pctDECIMAL(5,2)44.25恶意攻击占比infected_hostsINT0感染主机数risk_levelTINYINT1最高威胁等级-- 建表月报指标宽表 CREATE TABLE ops_monthly_report ( period DATE PRIMARY KEY, vm_count INT, cpu_alloc_ratio DECIMAL(5,2), mem_alloc_ratio DECIMAL(5,2), storage_util DECIMAL(5,2), waf_total INT, waf_malicious_pct DECIMAL(5,2), infected_hosts INT, risk_level TINYINT ); -- 查最近三期直接看同一指标的变化 SELECT period, vm_count, cpu_alloc_ratio, waf_malicious_pct, risk_level FROM ops_monthly_report ORDER BY period DESC LIMIT 3;宽表的好处是查询直白取任意指标做环比就是一次LAG窗口函数。period做主键天然去重重复导入同一期会报错避免脏数据。4.2 环比计算与容量预警阈值容量类指标最怕温水煮青蛙。CPU 分配比到 94.71%、虚拟主机 97/100都已经贴近上限用 SQL 直接把环比和剩余空间算出来。SELECT period, vm_count, vm_count - LAG(vm_count) OVER (ORDER BY period) AS vm_mom, -- 环比新增 100 - cpu_alloc_ratio AS cpu_headroom, -- CPU可分配余量 waf_malicious_pct - LAG(waf_malicious_pct) OVER (ORDER BY period) AS malicious_mom -- 恶意攻击占比环比 FROM ops_monthly_report ORDER BY period;LAG(...) OVER (ORDER BY period)取上一期同字段的值相减得到环比增量。cpu_headroom用 100 减去分配比例本月只有 5.29 个百分点低于 10 就该提扩容。把阈值固化成视图或应用层规则虚拟主机数 ≥ 授权上限 90%提示申请 license本月 97% 已超线CPU 或内存分配比 ≥ 90%进入容量观察名单WAF 恶意攻击占比环比上升 ≥ 5 个百分点安全侧复核规则最高威胁等级 ≥ 3触发工单1 到 2 级仅归档。4.3 把等级字符串映射成告警等级PDF 里的威胁等级是「1 级」这种字符串落库要转数值。用一张映射表统一处理顺带把颜色也带上方便出报表。LEVEL_MAP { 1: {level: 1, color: 蓝色, severity: low, action: 归档}, 2: {level: 2, color: 绿色, severity: low, action: 归档}, 3: {level: 3, color: 黄色, severity: medium, action: 开工单}, 4: {level: 4, color: 橙色, severity: high, action: 电话通知}, 5: {level: 5, color: 红色, severity: high, action: 电话通知}, } def route_alert(level_str: str) - dict: key re.sub(r\D, , level_str) # 从“1 级”里只留数字 return LEVEL_MAP.get(key, {level: 0, action: 人工确认}) print(route_alert(1 级)) # {level: 1, color: 蓝色, severity: low, action: 归档}re.sub(r\D, , ...)去掉所有非数字字符兼容「1 级」「等级 1」「1级」多种写法。查不到时返回「人工确认」而不是静默丢弃避免新等级出现时被漏掉。4.4 常见解析坑第一中文数字与阿拉伯数字混排比如「97 台」中间常有空格换行正则不加\s*就会漏第二单位大小写内存写的是 TB如果某期改成 GB直接转浮点会差 1024 倍抽的时候必须把单位一起捕获再换算第三百分比有的带小数点有的不带([\d.])%能兼容但int()会报错统一用float()再按需取整第四表格跨页时 pdfplumber 会切成两张表统计前要按表头合并第五图片标题「图 1」「图 2」里的数据和图内曲线不一致时以文字结论为准曲线图不建议自动读数。把这五条写进解析器的注释下次换人维护能省很多时间。5. 用图注锚点定位图文混排月报中的指标前面假定文字顺序规整但这类月报大量使用「图 3 主机感染及处理病毒情况」这种图注加图片的结构纯文本抽取时图注文字往往和正文数字错位导致指标张冠李戴。一个实用技巧是先把图注当锚点在锚点附近窗口内找数字。import fitz, re def metrics_near_caption(pdf_path: str, caption_kw: str, window: int 300) - list: doc fitz.open(pdf_path) hits [] for page in doc: text page.get_text(text) # 找到所有图注出现的位置 for m in re.finditer(caption_kw, text): start max(0, m.start() - window) # 前后各取一个窗口 end min(len(text), m.end() window) seg text[start:end] nums re.findall(r(\d(?:\.\d)?)\s*(%|例|台|次|核|TB)?, seg) hits.append({caption: caption_kw, page: page.number 1, numbers: nums}) doc.close() return hits # 定位“主机感染”图注附近的数字验证 0 例 0 台 for item in metrics_near_caption(IT运维及信息安全月报.pdf, 主机感染): print(item[page], item[numbers][:8])window控制搜索半径300 字符大约对应图注上下各一段文字太大会把无关指标卷进来太小会漏。re.findall里单位用非捕获组(?:...)?可选匹配这样「0 例」「99 台」「44.25%」都能抓成 (数值, 单位) 对。拿到结果后人工核对一两条确认锚点和数字的对应关系稳定再固化到解析流程里。这个方法的判断依据很简单图注是人写的、位置固定的强特征比依赖正文语序可靠得多。当某期月报版式调整、正则全线失效时先跑一遍锚点定位往往能快速判断是文字层变了还是坐标变了。定位准了前面第 4 章的阈值告警才有可信的输入整条从 PDF 到看板的链路才算真正可用。本文还有配套的精品资源点击获取