Python爬虫实战:高效采集NVD漏洞数据库
1. 项目概述爬取NVD漏洞数据的实用价值美国国家漏洞数据库NVD作为全球最权威的漏洞信息库之一收录了超过20万条CVE漏洞记录。对于安全研究人员、企业IT管理者和开发者而言定期获取这些数据具有多重价值漏洞情报监控实时掌握新披露漏洞的严重程度和影响范围风险评估依据通过CVSS评分快速判断漏洞修复优先级安全工具开发为漏洞扫描器、SIEM系统提供基础数据支持学术研究素材分析漏洞趋势、攻击模式演变的量化依据传统手动查询方式效率低下而NVD提供的API存在调用频率限制官方建议每分钟不超过5次请求。通过Python爬虫实现自动化采集可以灵活获取结构化数据特别适合需要定制化字段或历史数据归档的场景。2. 技术方案设计思路2.1 网页爬取 vs API调用NVD同时提供网页端和API两种数据获取方式我们选择网页爬取方案主要基于以下考量字段完整性网页包含漏洞描述、解决方案等API未提供的细节信息历史数据可获取早年间未通过API标准化的老旧漏洞记录灵活性不受API版本变更和字段限制影响成本控制避免企业级API的调用配额管理注意虽然网页爬取更灵活但必须严格遵守NVD的robots.txt规定。实测发现NVD允许爬虫访问/cve目录但要求请求间隔至少6秒。2.2 目标数据结构设计我们需要提取的字段包括{ cve_id: CVE-2023-1234, severity: CRITICAL, cvss_score: 9.8, published_date: 2023-01-15, last_modified: 2023-03-22, description: 在XX组件中发现缓冲区溢出漏洞..., affected_products: [Windows 10, Windows Server 2019] }2.3 技术栈选型请求库Requests Retrying比urllib3更友好的重试机制解析工具BeautifulSoup4 lxml比正则表达式更稳定的选择数据存储csv/json双格式输出兼顾可读性和程序可处理性反反爬随机User-Agent 动态延迟模拟人类操作模式3. 核心实现细节解析3.1 请求层实现要点from retrying import retry import random import time class NVDCrawler: def __init__(self): self.base_url https://nvd.nist.gov/vuln/search/results self.delay 6 random.uniform(0, 3) # 基础6秒随机扰动 retry(stop_max_attempt_number3, wait_exponential_multiplier1000) def fetch_page(self, page_num): params { form_type: Basic, results_type: overview, search_type: all, startIndex: (page_num - 1) * 20 } headers { User-Agent: self._get_random_ua() } time.sleep(self.delay) response requests.get(self.base_url, paramsparams, headersheaders) response.raise_for_status() return response.text def _get_random_ua(self): ua_list [ Mozilla/5.0 (Windows NT 10.0; Win64; x64)..., Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)... ] return random.choice(ua_list)关键设计说明指数退避重试网络异常时自动重试每次间隔时间倍增动态延迟在6秒基础上增加0-3秒随机扰动避免固定周期被识别分页参数startIndex每页增加20NVD的默认分页大小3.2 解析层关键技术NVD页面采用动态生成的HTML结构需要通过实际DOM分析确定选择器from bs4 import BeautifulSoup def parse_page(html): soup BeautifulSoup(html, lxml) items [] for row in soup.select(table[data-testidvuln-results-table] tr): cve_id row.select_one(th a).text.strip() severity row.select_one(span[data-testidvuln-cvss3-panel-score]).text cvss_score float(severity.split()[0]) date_cols row.select(td)[2].find_all(span) pub_date date_cols[0].text mod_date date_cols[1].text if len(date_cols) 1 else pub_date desc row.select_one(p[data-testidvuln-summary-]).text items.append({ cve_id: cve_id, severity: severity, cvss_score: cvss_score, published_date: pub_date, last_modified: mod_date, description: desc }) return items解析难点处理动态属性使用data-testid而非class作为选择器锚点空值处理未修改的漏洞last_modified等于published_date文本清洗CVSS评分需要从9.8 CRITICAL中分离数值4. 数据存储与导出方案4.1 多格式输出实现import csv import json from datetime import datetime class DataExporter: staticmethod def to_csv(data, filename): with open(f{filename}.csv, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesdata[0].keys()) writer.writeheader() writer.writerows(data) staticmethod def to_json(data, filename): with open(f{filename}.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) staticmethod def to_excel(data, filename): # 使用pandas简化Excel导出 import pandas as pd df pd.DataFrame(data) df.to_excel(f{filename}.xlsx, indexFalse)4.2 字段类型转换处理原始数据需要规范化def normalize_data(raw_items): for item in raw_items: # 日期标准化 item[published_date] datetime.strptime( item[published_date], %m/%d/%Y).strftime(%Y-%m-%d) # 严重等级枚举化 item[severity] item[severity].split()[-1].upper() # 描述文本清洗 item[description] .join( item[description].split()).strip() return raw_items5. 实战问题排查指南5.1 常见问题解决方案问题现象可能原因解决方案返回403状态码请求头特征明显1. 更换User-Agent2. 添加Referer头解析到空列表HTML结构变更1. 检查最新DOM结构2. 改用更宽松的选择器日期解析失败格式区域差异1. 强制指定locale2. 使用dateutil解析器内存占用过高未分页处理1. 分批处理数据2. 使用生成器替代列表5.2 性能优化建议增量采集# 记录最后采集的CVE_ID last_cve get_last_record() if item[cve_id] last_cve: break异步请求需控制并发数import aiohttp async def fetch_all(pages): async with aiohttp.ClientSession() as session: tasks [fetch_page(session, p) for p in range(1, pages1)] return await asyncio.gather(*tasks)分布式扩展使用Redis存储已采集ID集合通过Celery分发采集任务6. 合规使用注意事项访问频率控制单IP请求间隔≥6秒每日采集量建议≤1000页避免商业时段高峰期操作数据使用限制禁止重新发布原始数据衍生分析需注明数据来源不得用于漏洞利用工具开发技术伦理建议添加明显的用户代理标识提供可联系的爬虫负责人邮箱遵循最少必要原则采集数据在实际运行中建议先小规模测试如采集前10页确认无误后再进行全量采集。对于企业级应用场景更推荐申请官方API密钥获取数据。