1. 项目背景与需求分析
国家自然科学基金作为我国基础研究领域最重要的科研资助渠道之一,其结题管理流程的规范化程度直接影响科研管理效率。传统手动下载结题书的方式存在几个明显痛点:
- 项目负责人需要反复登录基金委ISIS系统,在多层菜单中导航
- 批量下载时需逐个点击,无法实现自动化操作
- 结题高峰期系统响应缓慢,人工操作耗时耗力
- 科研管理人员需要为每个项目单独保存文档,归档工作繁琐
我在协助实验室管理20余个基金项目时,曾花费整整两天时间处理结题材料。这种重复劳动促使我开发了这套自动化工具,核心解决以下场景需求:
- 项目负责人需要定期检查结题状态并获取最新版本文档
- 科研秘书需批量下载整个院系的结题材料进行归档
- 研究人员希望将结题书自动同步到指定云存储或知识管理系统
- 需要将结题信息与内部科研管理系统进行数据对接
2. 技术方案设计
2.1 整体架构设计
系统采用三层架构实现:
code复制[登录认证层] → [任务调度层] → [文件处理层]
│ │ │
↓ ↓ ↓
基金委ISIS系统 定时触发机制 本地存储/云同步
关键技术选型考虑:
- Requests:比urllib3更人性化的HTTP库,支持会话保持和自动重试
- BeautifulSoup4:轻量级HTML解析,适合ISIS系统的页面结构
- Schedule:实现定时任务调度,避免使用重量级框架
- PyPDF2:用于结题书PDF的元信息检查和基础处理
注意:基金委系统有严格的防爬机制,代码中必须设置合理延迟(建议3-5秒/请求)并模拟人类操作特征
2.2 核心功能模块
2.2.1 认证与会话管理
python复制def login(username, password):
session = requests.Session()
login_url = "https://isisn.nsfc.gov.cn/egrantweb/login"
# 需要先获取动态token
home_page = session.get(login_url)
token = parse_csrf_token(home_page.text)
payload = {
'username': username,
'password': password,
'_csrf': token
}
# 关键头信息模拟浏览器行为
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0)',
'Referer': login_url
}
response = session.post(login_url, data=payload, headers=headers)
validate_login(response)
return session
2.2.2 结题书列表获取
python复制def get_conclusion_list(session, project_id):
list_url = f"https://isisn.nsfc.gov.cn/egrantweb/project/conclusionList?projectId={project_id}"
# 必须携带referer才能通过验证
response = session.get(list_url, headers={
'Referer': 'https://isisn.nsfc.gov.cn/egrantweb/project/main'
})
soup = BeautifulSoup(response.text, 'html.parser')
items = []
for tr in soup.select('table.list-table tr'):
if not tr.get('id'):
continue
items.append({
'id': tr['id'].split('_')[1],
'name': tr.select_one('.conclusion-name').text.strip(),
'date': tr.select_one('.submit-date').text.strip()
})
return items
3. 实现细节与避坑指南
3.1 反爬机制应对策略
基金委系统采用多层防护:
- 动态CSRF Token:每次登录前需从首页HTML中提取
- 请求头验证:缺失Referer或异常User-Agent会被拦截
- 行为检测:高频请求会触发验证码或临时封禁
解决方案:
python复制# 在关键请求间插入随机延迟
from random import uniform
from time import sleep
def safe_request(session, url, **kwargs):
sleep(round(uniform(2.5, 4.9), 2)) # 浮动延迟更自然
return session.get(url, **kwargs)
# 使用真实浏览器指纹
headers = {
'Accept-Language': 'zh-CN,zh;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive'
}
3.2 文件下载处理
结题书下载需注意:
- 二进制流需要正确解码
- 文件名需从Content-Disposition提取
- 网络中断需要断点续传
优化后的下载函数:
python复制def download_file(session, file_id, save_path):
url = f"https://isisn.nsfc.gov.cn/egrantweb/project/downloadConclusion?id={file_id}"
with session.get(url, stream=True) as r:
r.raise_for_status()
# 处理中文文件名
filename = re.search(r'filename="(.+?)"',
r.headers['content-disposition']).group(1)
filename = filename.encode('iso-8859-1').decode('utf-8')
full_path = os.path.join(save_path, filename)
with open(full_path, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
return full_path
4. 系统扩展与高级功能
4.1 邮件通知集成
使用yagmail库实现状态通知:
python复制import yagmail
def send_notification(email, project_id, status):
yag = yagmail.SMTP('your_email@gmail.com', 'app_password')
contents = [
f"项目{project_id}结题书状态变更:{status}",
"系统自动抓取结果见附件"
]
yag.send(email, '基金结题状态通知', contents)
4.2 云存储自动同步
阿里云OSS上传示例:
python复制import oss2
def upload_to_oss(local_path, project_id):
auth = oss2.Auth('your_key', 'your_secret')
bucket = oss2.Bucket(auth, 'https://oss-cn-beijing.aliyuncs.com', 'your_bucket')
object_name = f"nsfc_conclusion/{project_id}/{os.path.basename(local_path)}"
bucket.put_object_from_file(object_name, local_path)
return f"https://your_bucket.oss-cn-beijing.aliyuncs.com/{object_name}"
5. 部署与调度方案
5.1 Windows定时任务配置
- 创建批处理文件
run.bat:
bat复制@echo off
C:\Python39\python.exe D:\scripts\nsfc_fetcher.py >> D:\logs\nsfc.log 2>&1
- 使用任务计划程序设置每日执行:
- 触发器:每日 02:00
- 操作:启动程序
run.bat - 条件:只在网络连接时启动
5.2 Linux系统服务化
创建systemd服务文件/etc/systemd/system/nsfc-fetcher.service:
ini复制[Unit]
Description=NSFC Conclusion Fetcher
After=network.target
[Service]
User=nsfc
WorkingDirectory=/opt/nsfc-fetcher
ExecStart=/usr/bin/python3 /opt/nsfc-fetcher/main.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
6. 异常处理与日志系统
6.1 结构化日志配置
python复制import logging
from logging.handlers import TimedRotatingFileHandler
def setup_logger():
logger = logging.getLogger("nsfc_fetcher")
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 按天滚动日志,保留7天
handler = TimedRotatingFileHandler(
'nsfc_fetcher.log', when='midnight', backupCount=7)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
6.2 常见异常处理
python复制try:
conclusion_list = get_conclusion_list(session, project_id)
except requests.exceptions.RequestException as e:
logger.error(f"获取结题列表失败: {str(e)}")
if isinstance(e, requests.exceptions.SSLError):
# 处理证书错误
session.verify = False
retry_count += 1
elif e.response.status_code == 403:
# 重新登录
session = login(username, password)
这套系统在我们实验室稳定运行两年,累计自动下载结题书超过300份。最关键的经验是:一定要模拟正常人类操作节奏,在代码中加入合理的随机延迟;同时要完善异常处理机制,确保网络波动时能够自动恢复。对于需要处理大量项目的用户,建议将项目ID列表存储在外部数据库或Excel中,方便批量管理。
