1. 项目背景与目标解析
美国国家漏洞数据库(NVD)作为全球最权威的漏洞信息库之一,每天收录数百个新增CVE漏洞。对于安全研究人员、企业IT管理者和开发者而言,定期获取这些漏洞数据至关重要。但NVD官网并未提供批量导出功能,手动复制效率极低——这正是我们需要用Python爬虫解决的痛点。
这个项目的核心目标是构建一个自动化采集工具,能够:
- 完整抓取NVD分页列表中的所有CVE条目
- 精确提取六大关键字段:CVE编号、CVSS严重性等级、基础评分、发布日期、最后更新日期和漏洞描述
- 支持将结果保存为CSV、Excel和JSON三种格式
- 处理反爬机制并保证数据完整性
提示:NVD的API虽然有官方接口,但存在请求频率限制(每分钟5次),而网页爬取可以绕过这个限制,适合需要大量历史数据的研究场景。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与环境准备
2.1 核心工具栈
- Requests:处理HTTP请求,相比urllib3有更简洁的API
- BeautifulSoup4:HTML解析,比正则表达式更稳定
- Pandas:数据清洗和格式转换
- Openpyxl:Excel文件生成
- Fake-useragent:随机UA生成
bash复制# 安装依赖库
pip install requests beautifulsoup4 pandas openpyxl fake-useragent
2.2 反爬应对策略
NVD网站虽然没有严格的反爬机制,但仍需注意:
- 请求间隔:建议每页间隔2-3秒
- Headers配置:
python复制headers = { 'User-Agent': fake_useragent.UserAgent().random, 'Accept-Language': 'en-US,en;q=0.9', 'Referer': 'https://nvd.nist.gov/' } - 异常处理:对503/429状态码自动重试
3. 爬虫核心实现流程
3.1 页面结构分析
NVD列表页(如https://nvd.nist.gov/vuln/search)的关键DOM特征:
- 分页控件:
<ul class="pagination"> - 每行数据:
<tr data-testid="vuln-row-{index}"> - 字段位置:
- CVE-ID:
<a data-testid="vuln-detail-link-{id}"> - CVSS评分:
<span class="severityDetail"> - 发布日期:
<span data-testid="vuln-published-on-{id}">
- CVE-ID:
3.2 分页抓取逻辑
python复制def get_total_pages():
url = "https://nvd.nist.gov/vuln/search"
params = {
"results_type": "overview",
"form_type": "Basic",
"search_type": "all"
}
res = requests.get(url, params=params, headers=headers)
soup = BeautifulSoup(res.text, 'html.parser')
last_page = soup.select_one('li.pagination-last a')['href']
return int(parse_qs(urlparse(last_page).query)['startIndex'][0]) // 20 + 1
3.3 数据提取实现
python复制def parse_page(html):
soup = BeautifulSoup(html, 'html.parser')
rows = soup.select('tr[data-testid^="vuln-row-"]')
data = []
for row in rows:
item = {
'CVE-ID': row.select_one('[data-testid^="vuln-detail-link"]').text.strip(),
'Severity': row.select_one('.severityDetail').text.split()[0] if row.select_one('.severityDetail') else 'N/A',
'CVSS': row.select_one('[data-testid="vuln-cvss3-panel-score"]').text if row.select_one('[data-testid="vuln-cvss3-panel-score"]') else row.select_one('[data-testid="vuln-cvss2-panel-score"]').text,
'Published': row.select_one('[data-testid^="vuln-published-on"]').text,
'Modified': row.select_one('[data-testid^="vuln-last-modified-on"]').text,
'Description': row.select_one('[data-testid^="vuln-summary-"]').text.strip()
}
data.append(item)
return data
4. 数据存储与格式转换
4.1 多格式导出实现
python复制def save_data(data, filename):
df = pd.DataFrame(data)
# CSV格式
df.to_csv(f"{filename}.csv", index=False)
# Excel格式
writer = pd.ExcelWriter(f"{filename}.xlsx", engine='openpyxl')
df.to_excel(writer, index=False)
writer.close()
# JSON格式
df.to_json(f"{filename}.json", orient='records', indent=2)
4.2 字段标准化处理
在存储前需要对数据进行清洗:
- 日期格式统一转为ISO格式:
pd.to_datetime(df['Published']).dt.strftime('%Y-%m-%d') - CVSS评分转为数值类型:
df['CVSS'] = pd.to_numeric(df['CVSS']) - 处理空值:
df.fillna('N/A', inplace=True)
5. 实战中的问题与解决方案
5.1 常见异常处理
-
元素定位失效:部分旧漏洞可能缺失CVSSv3评分
python复制cvss = row.select_one('[data-testid="vuln-cvss3-panel-score"]') or \ row.select_one('[data-testid="vuln-cvss2-panel-score"]') -
请求被拦截:添加请求重试机制
python复制def safe_request(url, max_retry=3): for _ in range(max_retry): try: res = requests.get(url, headers=headers, timeout=10) if res.status_code == 200: return res time.sleep(2) except Exception as e: print(f"Request failed: {str(e)}") time.sleep(5) return None
5.2 性能优化技巧
-
使用Session保持连接:
python复制
session = requests.Session() session.headers.update(headers) -
异步请求加速(可选):
python复制import aiohttp async def fetch_page(session, url): async with session.get(url) as response: return await response.text() -
增量采集模式:
python复制last_date = pd.read_csv('last_run.csv')['Modified'].max() new_data = [item for item in data if item['Modified'] > last_date]
6. 扩展应用场景
6.1 安全预警系统集成
将爬虫部署为定时任务,结合SMTP实现高危漏洞自动告警:
python复制def check_critical(data):
critical = [item for item in data if item['Severity'] == 'CRITICAL']
if critical:
send_alert_email(critical)
def send_alert_email(vulns):
# 实现邮件发送逻辑
pass
6.2 漏洞知识图谱构建
将采集的数据导入Neo4j图数据库,建立CVE与CPE的关联关系:
code复制CREATE (cve:CVE {id: $cve_id, score: $cvss})
MERGE (prod:Product {name: $cpe_name})
CREATE (cve)-[r:AFFECTS]->(prod)
6.3 历史数据分析
使用Pandas进行统计分析:
python复制# 按年份统计高危漏洞数量
df['Year'] = pd.to_datetime(df['Published']).dt.year
high_risk = df[df['CVSS'] >= 7.0]
stats = high_risk.groupby('Year').size().reset_index(name='Count')
7. 法律与伦理注意事项
- 遵守robots.txt:NVD允许爬取但禁止商业化滥用
- 请求频率控制:建议不超过每分钟20次请求
- 数据使用限制:部分漏洞详情可能涉及敏感信息
- 存储安全:包含漏洞利用细节的数据需加密存储
重要:建议在本地保留原始数据副本时,设置至少7天的自动清理周期,避免积累过多敏感信息。
我在实际运行中发现,NVD的页面结构每6-12个月会有小幅调整,建议定期检查爬虫的健壮性。一个实用的技巧是在代码中加入版本检测逻辑:
python复制def check_page_version(html):
if "New NVD Design" in html:
print("Warning: Page layout may have changed")
return False
return True
