云计算百科
云计算领域专业知识百科平台

Python 自动化提取国家网络安全通报 PDF:恶意 IP 情报提取脚本实战

Python 自动化提取国家网络安全通报 PDF:恶意 IP 情报提取脚本实战

本文介绍一个用于解析国家网络安全通报中心《重点防范境外恶意网址和恶意IP》系列 PDF、自动提取结构化威胁情报(IOC)的 Python 脚本:从原始通报到可直接入库的恶意 IP 记录,全程脚本化完成。


一、背景与痛点

国家网络安全通报中心会定期发布《重点防范境外恶意网址和恶意IP》系列通报(如"续二十一"),每期包含 10 条左右的境外恶意地址情报,字段包括:

  • 恶意地址(域名或 IP)
  • 关联 IP 地址
  • 归属地
  • 威胁类型(僵尸网络 / 后门)
  • 病毒家族(Mirai、AsyncRAT、CondiBot 等)
  • 攻击描述

这类通报以 PDF 形式发布,内容结构化程度高、格式相对固定,非常适合自动化提取。如果靠人工逐条复制粘贴整理,不仅效率低,还容易漏行、串行、引入格式错误。于是就有了这个脚本:输入通报 PDF,一键输出整理好的结构化 TXT,方便后续入库恶意 IP 管理系统或做进一步分析。

二、脚本功能概览

项目说明
输入 国家网络安全通报 PDF(如《重点防范境外恶意网址和恶意IP(续二十一).pdf》)
输出 结构化 TXT,每条记录含 IP地址 / 关联域名 / 归属地 / 威胁类型 / 病毒家族 / 攻击描述
核心库 pdfplumber(PDF 文本提取)
处理流程 提取全文 → 截断正文 → 正则分块 → 字段提取 → 保存

一次运行输出示例:

IP地址: 31.77.227.102
关联域名: msupdate.us
归属地: 美国/犹他州/奥勒姆
威胁类型: 僵尸网络
病毒家族: Mirai
攻击描述: 这是一种Linux僵尸网络病毒,通过网络下载、漏洞利用、Telnet和SSH暴力破解等方式进行扩散…

三、核心实现解析

3.1 PDF 文本提取

脚本基于 pdfplumber 逐页提取文本,并按页拼接:

def extract_pdf_text(pdf_path: str) > str:
texts = []
with pdfplumber.open(pdf_path) as pdf:
for page in pdf.pages:
text = page.extract_text() or ""
texts.append(text)
return "\\n".join(texts)

page.extract_text() 会按 PDF 内部文本流顺序返回该页文字,是 pdfplumber 最常用的能力。

3.2 记录分块

通报正文中每条情报以"(一)恶意地址:…“、”(二)恶意地址:…"开头,直到"二、排查方法"结束。利用这个规律,先截断正文,再用正则按块切分:

raw_text = raw_text.split('二、排查方法')[0]

pattern = re.compile(
r'([一二三四五六七八九十]+)恶意地址:.*?(?=(?:([一二三四五六七八九十]+)恶意地址:|二、排查方法|三、处置建议|\\Z))',
re.S
)
blocks = pattern.findall(raw_text)

关键点:

  • ([一二三四五六七八九十]+)恶意地址: 匹配序号 + 起始字段;
  • .*? 非贪婪匹配到下一个序号块或章节标题为止;
  • re.S 让 . 可以跨行匹配。

3.3 字段提取

每个块内按"字段名:值"的形式提取。为了避免误吞下一个字段,用正向预查(lookahead)在下一个已知字段名处截断:

def get_field(block: str, field: str) > str:
pattern = rf'{re.escape(field)}[::]\\s*(.*?)(?=\\s*(?:恶意地址|关联IP地址|归属地|威胁类型|病毒家族|描述|攻击描述)[::]|\\Z)'
m = re.search(pattern, block, re.S)
return clean_text(m.group(1)) if m else ''

clean_text 负责清洗:去掉 \\r、合并连续空白、消除中文标点前的多余空格,保证输出整洁。

3.4 IP 与域名逻辑

部分记录(如"恶意地址"本身是 IP 的条目)没有"关联IP地址"字段,脚本用 is_ip() 判断并做兜底:

def is_ip(addr: str) > bool:
return bool(re.fullmatch(r'(?:\\d{1,3}\\.){3}\\d{1,3}', addr.strip()))

if not ip and is_ip(addr):
ip = addr
domain = '无'
else:
domain = addr if addr else '无'
ip = ip if ip else '无'

即:恶意地址是域名时,输出"关联域名=域名 + 关联IP=IP";恶意地址本身就是 IP 时,输出"关联域名=无 + 关联IP=该IP"。

3.5 输出保存

按固定字段顺序写出,记录间以空行分隔,方便人工阅读和后续程序解析:

def save_to_txt(records, output_path='整理结果.txt'):
with open(output_path, 'w', encoding='utf-8') as f:
for i, r in enumerate(records):
f.write(f"IP地址: {r['IP地址']}\\n")
f.write(f"关联域名: {r['关联域名']}\\n")
# … 其余字段
if i != len(records) 1:
f.write("\\n")

四、应用场景

  • 威胁情报日常整理:每期通报发布后一键提取,替代人工复制粘贴,秒级生成结构化数据。
  • 恶意 IP 管理系统入库:提取结果可作为系统数据源。实际使用中,已将输出同步到恶意 IP 管理系统维护的 IP 列表与结构化情报库(按 IP 去重追加,旧数据不覆盖)。
  • 批量历史回溯:稍作改造即可遍历历史通报文件批量提取,沉淀完整的威胁情报历史库。
  • 二次分析输入:结构化输出可作为其他安全工具、脚本或数据分析流程的输入(如统计家族分布、归属地分布)。
  • 五、实战踩坑与优化方向

    以下问题均来自真实运行环境,可作为后续迭代方向:

    5.1 序号字符编码不一致导致漏条(重要)

    通报 PDF 内嵌字体对部分汉字使用了康熙部首码点(U+2F00 区)与 CJK 部首补充区(U+2E80 区),例如"一、二、八、十"被编码为 ⼀(U+2F00)、⼆(U+2F06)、⼋(U+2F0B)、⼗(U+2F17),而"三、四、五、六、七、九"是普通汉字(U+4E00 区)。正则 [一二三四五六七八九十] 无法匹配部首码点,导致一、二、八、十条目被整体跳过(10 条只提取出 6 条)。

    优化:提取文本后做部首码点归一化,U+2F00 区用 unicodedata.normalize('NFKC'),U+2E80 补充区(如 ⻔→门、⻢→马)手动映射:

    import unicodedata

    KANGXI_SUPP = {'⺠': '民', '⻔': '门', '⻢': '马', '⻥': '鱼'}

    def norm_kangxi(s: str) > str:
    out = []
    for ch in s:
    cp = ord(ch)
    if 0x2F00 <= cp <= 0x2FD5:
    out.append(unicodedata.normalize('NFKC', ch))
    elif ch in KANGXI_SUPP:
    out.append(KANGXI_SUPP[ch])
    else:
    out.append(ch)
    return ''.join(out)

    注意:不要对整个文本直接 NFKC,否则全角冒号、全角括号会被半角化,破坏原有正则。

    5.2 页眉页脚混入正文

    微信文章导出的 PDF 每页顶部有页眉(日期 + 标题),底部有页脚(公众号链接 + 页码)。它们会被 extract_text() 当正文提取,穿插在跨页记录的字段之间(例如"病毒家族"后面混入 https://mp.weixin.qq.com/… 1/3)。

    优化:逐行过滤,命中页眉/页脚特征的行直接丢弃:

    if re.fullmatch(r'https?://\\S+\\s+\\d+/\\d+', s): # 页脚:链接+页码
    continue
    if re.search(r'^\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}\\s+重点防范', s): # 页眉
    continue

    5.3 pdfminer 的 FontBBox 警告

    运行时控制台可能出现:

    Could not get FontBBox from font descriptor because None cannot be parsed as 4 floats

    这是 pdfplumber 底层 pdfminer.six 解析字体元数据时的非致命警告(该 PDF 部分字体描述符缺少 FontBBox 字段),不影响提取结果。可在脚本开头压制日志:

    import logging
    logging.getLogger('pdfminer').setLevel(logging.ERROR)

    5.4 入库去重与冲突处理

    对接恶意 IP 管理系统时,建议按 IP 做去重合并;若 IP 已存在但字段内容不一致,保留旧记录并单独上报人工确认,避免误覆盖历史数据。

    六、完整代码

    import re
    from pathlib import Path
    import pdfplumber

    def clean_text(s: str) > str:
    s = s.replace('\\r', '')
    s = re.sub(r'\\s+', ' ', s)
    s = re.sub(r'\\s+([、。;:,,.])', r'\\1', s)
    return s.strip()

    def get_field(block: str, field: str) > str:
    pattern = rf'{re.escape(field)}[::]\\s*(.*?)(?=\\s*(?:恶意地址|关联IP地址|归属地|威胁类型|病毒家族|描述|攻击描述)[::]|\\Z)'
    m = re.search(pattern, block, re.S)
    return clean_text(m.group(1)) if m else ''

    def is_ip(addr: str) > bool:
    return bool(re.fullmatch(r'(?:\\d{1,3}\\.){3}\\d{1,3}', addr.strip()))

    def extract_pdf_text(pdf_path: str) > str:
    texts = []
    with pdfplumber.open(pdf_path) as pdf:
    for page in pdf.pages:
    text = page.extract_text() or ""
    texts.append(text)
    return "\\n".join(texts)

    def parse_records(raw_text: str):
    raw_text = raw_text.split('二、排查方法')[0]

    pattern = re.compile(
    r'([一二三四五六七八九十]+)恶意地址:.*?(?=(?:([一二三四五六七八九十]+)恶意地址:|二、排查方法|三、处置建议|\\Z))',
    re.S
    )
    blocks = pattern.findall(raw_text)

    results = []
    for block in blocks:
    block = clean_text(block)

    addr = get_field(block, '恶意地址')
    ip = get_field(block, '关联IP地址')
    location = get_field(block, '归属地')
    threat = get_field(block, '威胁类型')
    family = get_field(block, '病毒家族')
    desc = get_field(block, '描述') or get_field(block, '攻击描述')

    if not ip and is_ip(addr):
    ip = addr
    domain = '无'
    else:
    domain = addr if addr else '无'
    ip = ip if ip else '无'

    results.append({
    'IP地址': ip,
    '关联域名': domain,
    '归属地': location if location else '无',
    '威胁类型': threat if threat else '无',
    '病毒家族': family if family else '无',
    '攻击描述': desc if desc else '无'
    })

    return results

    def save_to_txt(records, output_path='整理结果.txt'):
    with open(output_path, 'w', encoding='utf-8') as f:
    for i, r in enumerate(records):
    f.write(f"IP地址: {r['IP地址']}\\n")
    f.write(f"关联域名: {r['关联域名']}\\n")
    f.write(f"归属地: {r['归属地']}\\n")
    f.write(f"威胁类型: {r['威胁类型']}\\n")
    f.write(f"病毒家族: {r['病毒家族']}\\n")
    f.write(f"攻击描述: {r['攻击描述']}\\n")
    if i != len(records) 1:
    f.write("\\n")

    if __name__ == "__main__":
    pdf_file = "重点防范境外恶意网址和恶意IP(续二十一).pdf"
    text = extract_pdf_text(pdf_file)
    records = parse_records(text)
    save_to_txt(records)
    print(f"完成,共提取 {len(records)} 条记录")

    运行方式:

    pip install pdfplumber
    python pdf文档内容提取.py

    七、扩展思路

  • 多期批量处理:遍历目录下所有通报 PDF,自动去重合并成统一 IOC 库;
  • 输出格式扩展:支持 CSV / JSON / STIX 2.1,方便对接威胁情报平台;
  • 定时调度:配合定时任务,通报发布后自动提取、自动入库;
  • 关联分析:结合 WHOIS、DNS 解析做恶意基础设施关联分析(同一注册者、同 NS 等)。

  • 本文代码与优化建议均来自实际运行环境,数据真实可复现。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Python 自动化提取国家网络安全通报 PDF:恶意 IP 情报提取脚本实战
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!