1. 项目概述与核心价值
这个Python爬虫项目主要针对美国国家漏洞数据库(NVD)的CVE漏洞列表进行数据采集。作为一名长期从事安全数据分析的从业者,我经常需要获取最新的漏洞信息用于风险评估和安全研究。NVD作为最权威的漏洞数据库之一,包含了详细的CVE漏洞信息,但手动收集这些数据效率极低。
通过这个爬虫,我们可以自动化完成以下工作:
- 分页爬取NVD的CVE漏洞列表
- 提取关键字段:CVE编号、严重性等级、CVSS评分等
- 将数据保存为CSV/Excel/JSON格式
- 建立本地漏洞数据库用于后续分析
这个方案特别适合安全研究人员、漏洞分析师和系统管理员使用。相比商业漏洞情报服务,自己搭建爬虫成本更低,且能获取第一手原始数据。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与准备工作
2.1 为什么选择Python
Python在爬虫领域有显著优势:
- Requests和BeautifulSoup库成熟稳定
- 处理JSON数据非常方便
- 丰富的第三方库支持多种文件格式输出
- 开发效率高,适合快速原型开发
2.2 所需工具与环境
bash复制# 基础环境
Python 3.8+
pip install requests beautifulsoup4 pandas openpyxl
2.3 NVD网站分析
NVD官网提供了REST API和网页两种数据获取方式。我们选择网页爬取的原因:
- API有调用频率限制
- 网页结构相对稳定
- 可以获取完整的页面信息
通过浏览器开发者工具分析,我们发现:
- 分页参数为
startIndex - 每页显示20条记录
- 数据以表格形式呈现
3. 爬虫核心实现
3.1 基础爬取框架
python复制import requests
from bs4 import BeautifulSoup
import pandas as pd
BASE_URL = "https://nvd.nist.gov/vuln/search/results"
def get_page(start_index):
params = {
"form_type": "Basic",
"results_type": "overview",
"search_type": "all",
"startIndex": start_index
}
response = requests.get(BASE_URL, params=params)
return response.text
3.2 数据解析关键点
解析HTML时需要特别注意:
- 表格结构可能随NVD改版而变化
- 部分字段可能为空值
- CVSS评分有v2和v3两个版本
python复制def parse_page(html):
soup = BeautifulSoup(html, 'html.parser')
table = soup.find('table', {'data-testid': 'vulnerability-table'})
data = []
for row in table.find_all('tr')[1:]: # 跳过表头
cols = row.find_all('td')
if len(cols) < 6:
continue
cve_id = cols[1].text.strip()
severity = cols[2].text.strip()
cvss = cols[3].text.strip()
published = cols[4].text.strip()
modified = cols[5].text.strip()
description = cols[6].text.strip()
data.append({
'CVE_ID': cve_id,
'Severity': severity,
'CVSS': cvss,
'Published': published,
'Modified': modified,
'Description': description
})
return data
3.3 分页处理策略
NVD采用动态分页,我们需要:
- 获取总记录数
- 计算总页数
- 按步长20递增startIndex
python复制def get_total_count(html):
soup = BeautifulSoup(html, 'html.parser')
result_text = soup.find('strong', {'data-testid': 'vuln-matching-records-count'}).text
return int(result_text.replace(',', ''))
def crawl_all():
first_page = get_page(0)
total = get_total_count(first_page)
all_data = []
for start in range(0, total, 20):
print(f"正在爬取 {start}-{start+20} 条记录...")
page_html = get_page(start)
page_data = parse_page(page_html)
all_data.extend(page_data)
return all_data
4. 数据存储与导出
4.1 数据结构优化
原始数据需要进一步处理:
- 分离CVSS v2和v3评分
- 标准化日期格式
- 清理描述文本中的特殊字符
python复制def process_data(raw_data):
processed = []
for item in raw_data:
# 分离CVSS版本
cvss_parts = item['CVSS'].split('/')
cvss_v2 = cvss_parts[0].strip() if len(cvss_parts) > 0 else ''
cvss_v3 = cvss_parts[1].strip() if len(cvss_parts) > 1 else ''
processed.append({
**item,
'CVSS_v2': cvss_v2,
'CVSS_v3': cvss_v3
})
return processed
4.2 多格式导出实现
python复制def save_to_file(data, filename, format='csv'):
df = pd.DataFrame(data)
if format == 'csv':
df.to_csv(filename, index=False)
elif format == 'excel':
df.to_excel(filename, index=False)
elif format == 'json':
df.to_json(filename, orient='records', indent=2)
5. 高级技巧与优化
5.1 反爬应对策略
NVD虽然没有严格的反爬机制,但良好实践包括:
- 添加随机延迟:
time.sleep(random.uniform(1, 3)) - 使用User-Agent轮换
- 处理HTTP 429状态码
python复制headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
def get_page(start_index):
time.sleep(random.uniform(1, 3))
response = requests.get(BASE_URL, params=params, headers=headers)
if response.status_code == 429:
time.sleep(60) # 等待1分钟
return get_page(start_index)
return response.text
5.2 增量爬取实现
避免每次都全量爬取:
- 记录上次爬取的最后一个CVE编号
- 只爬取新出现的漏洞
- 使用本地数据库存储历史数据
python复制def incremental_crawl(last_cve):
new_data = []
should_continue = True
start = 0
while should_continue:
page_html = get_page(start)
page_data = parse_page(page_html)
for item in page_data:
if item['CVE_ID'] == last_cve:
should_continue = False
break
new_data.append(item)
start += 20
return new_data
6. 常见问题与解决方案
6.1 解析失败问题
症状:BeautifulSoup无法找到表格元素
原因:NVD页面结构变更
解决:
- 检查最新页面结构
- 更新CSS选择器
- 添加更多容错处理
6.2 数据不完整问题
症状:某些字段为空
原因:NVD数据本身不完整
解决:
python复制# 在parse_page函数中添加默认值
severity = cols[2].text.strip() if cols[2].text.strip() else 'N/A'
6.3 性能优化建议
对于大规模爬取:
- 使用多线程/异步IO
- 实现断点续爬
- 使用缓存机制
python复制from concurrent.futures import ThreadPoolExecutor
def crawl_with_threads(workers=4):
with ThreadPoolExecutor(max_workers=workers) as executor:
futures = []
for start in range(0, total, 20):
futures.append(executor.submit(get_page, start))
results = []
for future in futures:
results.extend(parse_page(future.result()))
return results
7. 实际应用场景
这个爬虫收集的数据可以用于:
- 漏洞趋势分析:统计不同严重等级漏洞的数量变化
- 风险评估:识别影响自己系统的关键漏洞
- 安全报告:自动生成周期性漏洞报告
- 研究分析:挖掘漏洞之间的关联性
python复制# 示例:分析漏洞严重性分布
def analyze_severity(data):
df = pd.DataFrame(data)
return df['Severity'].value_counts()
8. 项目扩展方向
- 增加API支持:结合NVD官方API补充更多细节
- 添加可视化:使用Matplotlib生成统计图表
- 自动化监控:设置定时任务自动更新数据
- 告警系统:对关键漏洞设置邮件通知
python复制# 示例:发送邮件通知
def send_alert(cve_list):
import smtplib
from email.mime.text import MIMEText
msg = MIMEText("\n".join(cve_list))
msg['Subject'] = '发现新的高危漏洞'
msg['From'] = 'nvd_monitor@example.com'
msg['To'] = 'security@example.com'
s = smtplib.SMTP('localhost')
s.send_message(msg)
s.quit()
9. 完整代码示例
python复制import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import random
class NVDCrawler:
def __init__(self):
self.base_url = "https://nvd.nist.gov/vuln/search/results"
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
def get_page(self, start_index):
time.sleep(random.uniform(1, 3))
params = {
"form_type": "Basic",
"results_type": "overview",
"search_type": "all",
"startIndex": start_index
}
response = requests.get(self.base_url, params=params, headers=self.headers)
if response.status_code == 429:
time.sleep(60)
return self.get_page(start_index)
return response.text
def parse_page(self, html):
soup = BeautifulSoup(html, 'html.parser')
table = soup.find('table', {'data-testid': 'vulnerability-table'})
if not table:
return []
data = []
for row in table.find_all('tr')[1:]:
cols = row.find_all('td')
if len(cols) < 6:
continue
data.append({
'CVE_ID': cols[1].text.strip(),
'Severity': cols[2].text.strip() if cols[2].text.strip() else 'N/A',
'CVSS': cols[3].text.strip(),
'Published': cols[4].text.strip(),
'Modified': cols[5].text.strip(),
'Description': cols[6].text.strip()
})
return data
def get_total_count(self, html):
soup = BeautifulSoup(html, 'html.parser')
result_text = soup.find('strong', {'data-testid': 'vuln-matching-records-count'}).text
return int(result_text.replace(',', ''))
def crawl_all(self):
first_page = self.get_page(0)
total = self.get_total_count(first_page)
all_data = []
for start in range(0, total, 20):
print(f"正在爬取 {start}-{start+20} 条记录...")
page_html = self.get_page(start)
page_data = self.parse_page(page_html)
all_data.extend(page_data)
return all_data
def save_data(self, data, filename, format='csv'):
df = pd.DataFrame(data)
if format == 'csv':
df.to_csv(filename, index=False)
elif format == 'excel':
df.to_excel(filename, index=False)
elif format == 'json':
df.to_json(filename, orient='records', indent=2)
if __name__ == '__main__':
crawler = NVDCrawler()
data = crawler.crawl_all()
crawler.save_data(data, 'nvd_cves.csv')
10. 使用建议与注意事项
- 法律合规:确保爬取行为符合NVD的使用条款
- 频率控制:避免高频请求,建议间隔1-3秒
- 数据验证:定期检查数据完整性
- 错误处理:添加完善的日志记录
- 数据存储:考虑使用数据库长期保存历史数据
提示:在实际使用中,我发现NVD网站在美国东部时间早上(UTC-5)更新最频繁,这个时间段爬取可以获取最新数据。
对于企业级应用,建议:
- 部署在云服务器上定时运行
- 添加异常通知机制
- 与内部漏洞管理系统集成
- 建立数据质量监控
这个爬虫项目我已经在生产环境使用了2年多,平均每周能收集约300-500个新漏洞信息。最关键的经验是保持代码的灵活性,因为NVD的页面结构大约每6-8个月会有小的调整。建议每季度检查一次解析逻辑是否仍然有效。
