#!/usr/bin/env python3
-- coding: utf-8 --
"""
CECS标准征求意见栏目数据提取工具
功能:从CECS标准征求意见栏目获取数据并保存为CSV格式
"""
导入所需库
try:
import requests
import pandas as pd
from bs4 import BeautifulSoup
import re
import time
import os
from datetime import datetime
# 标记库是否可用
libraries_available = {
'requests': True,
'pandas': True,
'BeautifulSoup': True
}
except ImportError as e:
print(f"警告: 缺少必要的库。请安装以下库:\n{'-' * 50}\npip install requests pandas beautifulsoup4\n{'-' * 50}")
# 根据导入错误标记缺失的库
libraries_available = {
'requests': 'requests' not in str(e),
'pandas': 'pandas' not in str(e),
'BeautifulSoup': 'bs4' not in str(e)
}
def get_cecs_zqyj_content(url="http://www.cecs.org.cn/xhbz/zqyj/index.html", max_retries=3, timeout=30):
"""
使用requests获取CECS标准征求意见栏目的页面内容,支持重试机制
Args:
url: CECS标准征求意见栏目的URL
max_retries: 最大重试次数
timeout: 每次请求的超时时间(秒)
Returns:
str: 页面的HTML内容,如果获取失败则返回None
"""
if not libraries_available['requests']:
print("错误: requests库未安装,无法获取网页内容。")
return None
# 设置请求头模拟浏览器访问
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
}
# 重试机制
for attempt in range(1, max_retries + 1):
try:
# 发送请求
print(f"正在获取CECS标准征求意见栏目的内容 (尝试 {attempt}/{max_retries})...")
response = requests.get(url, headers=headers, timeout=timeout)
# 检查响应状态
if response.status_code == 200:
# 网页编码设置为UTF-8
response.encoding = 'utf-8'
print(f"成功获取页面内容,状态码: {response.status_code}")
return response.text
else:
print(f"获取页面失败,状态码: {response.status_code}")
if attempt < max_retries:
print(f"{attempt}秒后重试...")
time.sleep(attempt) # 指数退避策略
continue
else:
return None
except requests.RequestException as e:
print(f"请求出错: {str(e)}")
if attempt < max_retries:
print(f"{attempt}秒后重试...")
time.sleep(attempt) # 指数退避策略
continue
else:
return None
def save_to_csv(data, output_dir="."):
"""
使用pandas将提取的标准征求意见信息保存为CSV格式文件
Args:
data: 包含标准征求意见信息的字典列表
output_dir: 输出文件的目录,默认为当前目录
Returns:
str: 保存的CSV文件路径,如果保存失败则返回None
"""
if not libraries_available['pandas']:
print("错误: pandas库未安装,无法保存为CSV文件。")
return None
if not data:
print("警告: 没有数据可保存")
return None
try:
# 创建pandas DataFrame
df = pd.DataFrame(data)
# 确保输出目录存在
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# 生成带有时间戳的文件名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"cecs_zqyj_standards_{timestamp}.csv"
filepath = os.path.join(output_dir, filename)
# 保存为CSV文件,使用UTF-8-SIG编码以支持中文
df.to_csv(filepath, index=False, encoding='utf-8-sig')
print(f"成功将 {len(data)} 条标准征求意见信息保存到CSV文件:")
print(f"文件路径: {filepath}")
return filepath
except Exception as e:
print(f"保存CSV文件时出错: {str(e)}")
return None
def extract_zqyj_info(html_content):
"""
使用BeautifulSoup解析HTML,提取CECS标准征求意见信息
Args:
html_content: 页面的HTML内容
Returns:
list: 包含标准征求意见信息的字典列表
"""
if not libraries_available['BeautifulSoup']:
print("错误: BeautifulSoup库未安装,无法解析HTML。")
return []
try:
# 创建BeautifulSoup对象
soup = BeautifulSoup(html_content, 'html.parser')
# 存储提取的标准征求意见信息
standards_info = []
# 增强的日期提取正则表达式,专门针对2025-12-07这样的格式
date_pattern = re.compile(r'\d{4}[-/]\d{1,2}[-/]\d{1,2}', re.IGNORECASE)
# 1. 首先从整个页面中提取所有的日期信息,保存它们的位置
all_dates = date_pattern.finditer(html_content)
date_positions = [(match.start(), match.group(0)) for match in all_dates]
print(f"找到 {len(date_positions)} 个日期格式的文本")
# 2. 尝试通过不同的方式查找标准征求意见信息
# 查找所有可能包含标准信息的列表项或div
items = []
# 查找常见的内容列表容器
content_containers = soup.find_all(['ul', 'ol', 'div'],
class_=re.compile(r'list|content|article|news|items', re.I))
for container in content_containers:
# 在每个容器中查找可能的条目
potential_items = container.find_all(['li', 'div', 'tr'],
class_=re.compile(r'item|content|article|news|entry', re.I))
items.extend(potential_items)
# 如果没有找到特定的条目容器,尝试获取页面中所有的链接
if not items:
links = soup.find_all('a')
items = links
print(f"找到 {len(items)} 个可能包含标准信息的元素")
# 遍历所有找到的项目,提取标准信息
for i, item in enumerate(items):
# 尝试获取文本内容
text = item.get_text(strip=True)
# 尝试获取链接
link = None
if item.name == 'a':
link = item.get('href', '')
else:
a_tag = item.find('a')
if a_tag:
link = a_tag.get('href', '')
# 处理链接,确保是完整的URL
base_url = "http://www.cecs.org.cn"
if link:
# 如果链接是相对路径,添加基础URL
if not link.startswith('http'):
# 确保路径正确拼接
if link.startswith('/'):
full_link = base_url + link
else:
# 假设这是相对于xhbz/zqyj目录的链接
full_link = base_url + "/xhbz/zqyj/" + link
else:
full_link = link
else:
full_link = ''
# 查找此元素在HTML中的位置
item_position = -1
if link:
# 如果有链接,尝试通过链接定位元素位置
item_position = html_content.find(f'href="{link}"')
if item_position == -1:
item_position = html_content.find(f"href='{link}'")
if item_position == -1 and text:
# 如果没有链接或无法通过链接定位,尝试通过文本定位
# 只使用文本的前30个字符进行匹配
search_text = text[:30]
item_position = html_content.find(search_text)
# 尝试从文本中直接提取日期信息
date_match = date_pattern.search(text)
date = date_match.group(0) if date_match else ''
# 如果文本中没有日期,但我们有元素位置,尝试查找附近的日期
if not date and item_position != -1 and date_positions:
# 找到离元素最近的日期
nearest_date = None
min_distance = float('inf')
for pos, date_str in date_positions:
distance = abs(pos - item_position)
if distance < min_distance:
min_distance = distance
nearest_date = date_str
# 如果找到的日期足够近(500个字符内),就使用它
if nearest_date and min_distance < 500:
date = nearest_date
# 如果文本内容不为空,则添加到结果列表
if text and len(text) > 5:
# 构建标准信息字典
standard_info = {
'序号': i + 1,
'标题': text,
'链接': full_link,
'日期': date
}
standards_info.append(standard_info)
# 3. 专门查找页面中所有包含日期的文本元素
date_elements = soup.find_all(string=date_pattern)
for date_text in date_elements:
# 提取日期
date_match = date_pattern.search(date_text)
date = date_match.group(0) if date_match else ''
# 获取包含这个日期的元素
parent_element = date_text.parent
# 检查这个日期是否已经在我们的结果中
date_exists = False
for std in standards_info:
if std['日期'] == date:
# 检查这个条目是否包含这个元素的文本
if parent_element.get_text(strip=True) in std['标题']:
date_exists = True
break
# 如果日期不在结果中,为它创建一个条目
if not date_exists and date:
# 获取链接并确保完整性
element_link = parent_element.get('href', '') if parent_element.name == 'a' else ''
# 处理链接,确保是完整的URL
base_url = "http://www.cecs.org.cn"
if element_link:
if not element_link.startswith('http'):
if element_link.startswith('/'):
full_element_link = base_url + element_link
else:
full_element_link = base_url + "/xhbz/zqyj/" + element_link
else:
full_element_link = element_link
else:
full_element_link = ''
standards_info.append({
'序号': len(standards_info) + 1,
'标题': parent_element.get_text(strip=True) or f'日期条目: {date}',
'链接': full_element_link,
'日期': date
})
# 4. 如果没有找到任何信息,尝试直接提取页面中的文本段落
if not standards_info:
paragraphs = soup.find_all('p')
for i, p in enumerate(paragraphs):
text = p.get_text(strip=True)
# 尝试从文本中提取日期信息
date_match = date_pattern.search(text)
date = date_match.group(0) if date_match else ''
if text and len(text) > 5:
standard_info = {
'序号': i + 1,
'标题': text,
'链接': '',
'日期': date
}
standards_info.append(standard_info)
# 去重
seen_titles = set()
unique_standards = []
for std in standards_info:
title = std['标题']
if title not in seen_titles:
seen_titles.add(title)
unique_standards.append(std)
# 重新排序,让有日期的条目优先显示
unique_standards.sort(key=lambda x: 0 if x['日期'] else 1)
# 重新编号
for i, std in enumerate(unique_standards):
std['序号'] = i + 1
standards_info = unique_standards
# 统计有日期的条目
dated_entries = sum(1 for std in standards_info if std['日期'])
print(f"成功提取 {len(standards_info)} 条唯一的标准征求意见信息,其中 {dated_entries} 条包含日期")
return standards_info
except Exception as e:
print(f"解析HTML出错: {str(e)}")
return []
def main():
"""
主函数
"""
检查必要的库是否可用
if not all(libraries_available.values()):
missing_libraries = [lib for lib, available in libraries_available.items() if not available]
print(f"错误: 缺少以下必要的库: {', '.join(missing_libraries)}")
print("请先安装必要的库后再运行程序。")
return
# 获取CECS标准征求意见栏目的内容
html_content = get_cecs_zqyj_content()
if html_content:
print(f"成功获取CECS标准征求意见栏目的内容,长度: {len(html_content)} 字符")
# 提取标准征求意见信息
standards_info = extract_zqyj_info(html_content)
if standards_info:
print(f"成功提取 {len(standards_info)} 条标准征求意见信息")
# 保存为CSV文件
csv_file = save_to_csv(standards_info)
if csv_file:
print(f"数据已成功保存到CSV文件: {csv_file}")
# 显示保存的数据预览
print("\n数据预览:")
df_preview = pd.DataFrame(standards_info)
print(df_preview.head(10).to_string(index=False))
else:
print("保存CSV文件失败")
else:
print("未能提取到标准征求意见信息")
else:
print("无法获取CECS标准征求意见栏目的内容")
if name == "main ":
main()