当前位置: 首页 > news >正文

避坑指南:爬取NMPA药品数据时,为什么你的Requests和Selenium总失败?

破解NMPA药品数据爬取难题:从失败案例到系统性解决方案

当阳光透过百叶窗照在显示器上时,我又一次盯着那个熟悉的错误提示发呆。这已经是本周第三次尝试爬取国家药品监督管理局(NMPA)公开数据失败,每次看似简单的请求背后都暗藏着各种意想不到的陷阱。许多开发者都曾在这个看似简单的任务上栽过跟头——明明是按照常规方法操作,却总是莫名其妙地失败,甚至前一天还能正常运行的代码,隔天就突然失效。本文将带你深入分析这些典型失败案例背后的技术原理,并提供一套系统性的解决方案框架。

1. 编码迷局:GBK与UTF-8的陷阱

在爬取NMPA网站时,最容易被忽视却最致命的问题就是字符编码。许多开发者习惯性地使用UTF-8编码处理所有网页,但NMPA系统的部分页面实际上采用了GBK编码标准。

1.1 识别编码问题的典型症状

当遇到以下情况时,很可能是编码问题在作祟:

  • 中文字符显示为乱码(如"鍏ㄥ浗鑽搧鎶界")
  • URL中的中文参数无法正确解析
  • 页面部分内容正常显示,部分出现乱码
# 错误示范:统一使用UTF-8解码 response = requests.get(url) content = response.text # 默认使用response.encoding指定的编码,可能是错误的 # 正确做法:主动检测并指定编码 import chardet raw_data = response.content encoding = chardet.detect(raw_data)['encoding'] content = raw_data.decode(encoding)

1.2 URL编码的特殊处理

NMPA系统的URL参数编码有其特殊性,直接使用标准库的urlencode可能无法正常工作:

from urllib.parse import quote # 医疗器械生产企业(许可)的GBK编码处理 param = '医疗器械生产企业(许可)' encoded_param = quote(param.encode('gbk')) # 必须显式指定GBK编码 # 对比不同编码结果 print(f"UTF-8编码: {quote(param)}") print(f"GBK编码: {encoded_param}")

关键发现:NMPA系统中,不同功能模块可能使用不同编码标准。例如,药品相关页面多用UTF-8,而医疗器械相关则倾向使用GBK。

2. 会话维持与状态管理

许多开发者在首次测试时能成功获取数据,但刷新或连续请求时却突然失败。这种现象通常与会话状态管理有关。

2.1 会话Cookie的维持

NMPA系统会通过会话Cookie跟踪用户状态,无状态的请求容易被识别为异常访问:

# 错误示范:每次请求创建新会话 def get_page(url): return requests.get(url).text # 每次都是新会话 # 正确做法:保持会话 session = requests.Session() session.headers.update({'User-Agent': 'Mozilla/5.0...'}) def get_page(url): return session.get(url).text

2.2 动态参数验证

现代Web应用常使用动态生成的token或时间戳验证请求合法性。通过浏览器开发者工具(F12)观察正常请求,可以发现NMPA页面往往携带这些隐藏参数:

参数名示例值说明
__VIEWSTATE/wEPDwU...ASP.NET视图状态
__EVENTVALIDATION/wEWBQ...事件验证
_t1654234567890时间戳

实战技巧:使用Selenium首次加载页面提取这些参数,再用于后续Requests请求,结合两种技术的优势。

3. 反爬虫机制与应对策略

NMPA作为政府网站,虽然数据是公开的,但也有基本的防护措施防止过度采集。

3.1 常见反爬手段分析

  • User-Agent检测:缺乏或使用明显爬虫UA的请求会被拒绝
  • 行为模式识别:固定间隔的规律请求容易被识别
  • WebDriver检测:检测浏览器自动化工具特征
  • IP频率限制:单位时间内来自同一IP的请求过多会被暂时封锁

3.2 Selenium的隐蔽性配置

from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0...)") options.add_argument("--disable-blink-features=AutomationControlled") options.add_experimental_option("excludeSwitches", ["enable-automation"]) options.add_experimental_option("useAutomationExtension", False) driver = webdriver.Chrome(options=options) driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": """ Object.defineProperty(navigator, 'webdriver', { get: () => undefined }) """ })

3.3 请求节奏控制

模拟人类操作的不规律间隔是避免被封锁的关键:

import random import time def random_delay(): time.sleep(random.uniform(1, 3)) # 1-3秒随机延迟 # 在关键操作间插入延迟 random_delay() driver.find_element(...).click() random_delay()

4. 页面结构解析的稳定性方案

即使成功获取页面内容,解析环节也充满变数。NMPA网站的HTML结构可能随时间变化,过于依赖特定结构的解析代码容易失效。

4.1 容错性解析策略

from bs4 import BeautifulSoup def safe_extract(soup, selectors): """多选择器容错提取""" for selector in selectors: element = soup.select_one(selector) if element: return element.text.strip() return None # 使用示例 soup = BeautifulSoup(html, 'lxml') title = safe_extract(soup, [ 'div.content-title', # 新版选择器 'h1.title-text', # 旧版选择器 'title' # 最后回退方案 ])

4.2 数据校验机制

建立数据质量检查点,避免存储错误或残缺数据:

def validate_record(record): required_fields = ['批准文号', '产品名称', '生产企业'] checks = [ all(field in record for field in required_fields), len(record.get('批准文号', '')) >= 10, '****' not in record.get('产品名称', '') # 排除脱敏数据 ] return all(checks)

5. 系统化调试方法论

当爬虫失败时,系统化的调试方法比盲目尝试更有效。

5.1 问题诊断流程图

开始 │ ├─ 请求是否得到响应? │ ├─ 否 → 检查网络、代理、DNS │ └─ 是 → │ ├─ 状态码是否为200? │ │ ├─ 否 → 分析4xx/5xx原因 │ │ └─ 是 → │ │ ├─ 内容是否为空? │ │ │ ├─ 是 → 检查反爬机制 │ │ │ └─ 否 → │ │ ├─ 数据是否完整? │ │ │ ├─ 否 → 检查解析逻辑 │ │ │ └─ 是 → 成功 │ │ └─ 编码是否正确? │ │ ├─ 否 → 调整解码方式 │ │ └─ 是 → 成功 │ └─ 内容是否被拒绝? │ ├─ 是 → 检查会话、Cookie、Headers │ └─ 否 → 继续 │ └─ 行为是否被限制? ├─ 是 → 调整访问频率、模拟行为 └─ 否 → 检查其他可能性

5.2 关键日志记录点

在代码中 strategic 位置添加日志记录:

import logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', filename='nmpa_crawler.log' ) def log_response(response): logging.info(f"URL: {response.url}") logging.info(f"Status: {response.status_code}") logging.info(f"Encoding: {response.encoding}") logging.info(f"Headers: {response.headers}") if len(response.text) < 1000: # 避免日志过大 logging.debug(f"Content sample: {response.text[:500]}")

6. 替代方案与数据源考量

当直接爬取遇到难以克服的障碍时,考虑以下替代方案:

6.1 官方API探索

通过浏览器开发者工具分析XHR请求,有时能发现隐藏的API接口:

http://app1.nmpa.gov.cn/api/xxx/query?page=1&size=20

6.2 数据更新策略

根据数据特性制定不同的更新策略:

数据类型更新频率建议策略
药品注册信息低频每月全量更新
抽检结果中频每周增量更新
企业许可低频变更时更新
召回信息高频每日监控更新

6.3 分布式采集架构

对于大规模数据采集,建议采用分布式架构提高可靠性:

任务调度器 → URL队列 → 采集节点集群 → 数据存储 ↑ ↑ ↑ 监控报警 去重中间件 失败重试机制

在项目实践中,我发现最有效的调试方法是保持请求与浏览器完全一致。使用Chrome开发者工具的"Copy as cURL"功能,将正常工作的浏览器请求转换为Python代码,往往能揭示出自己遗漏的关键细节。

http://www.jsqmd.com/news/864099/

相关文章:

  • Fusion360新手必看:这10个隐藏快捷键和技巧,让你建模效率翻倍
  • MATLAB自动控制系统设计:手把手教你用Bode图搞定超前校正(附完整代码)
  • 从实验箱到现实:拆解QPSK在4G/5G信号中的实际应用与误码分析
  • Claude Code 用户如何通过 Taotoken 解决 API 访问不稳定问题
  • 小爱音箱音乐自由:解锁无限播放的终极指南
  • 润滑油粘度检测不稳定?GB/T 265运动粘度测定仪稳定性强、操作简单性价比高 - 品牌推荐大师
  • ARM SIMD浮点舍入指令VRINTA与VRINTM详解
  • 论文重复率是怎么算的?
  • BetterJoy:Switch手柄Windows适配终极指南
  • 告别卡顿和黑屏:用VNC+SSH远程玩转树莓派4B的完整配置(含Raspberry Pi OS Bookworm换源)
  • 在西安闲置名表怎么变现价更高?内行总结全攻略 - 奢侈品回收测评
  • 从零开始将OpenClaw助手工具接入Taotoken平台的具体操作指南
  • 论文格式改到崩溃?paperxie 智能排版一键搞定,告别导师连环批注
  • 从4G到5G的网元‘变形记’:对照IUV架构图,搞懂MME如何拆成AMF和SMF
  • 告别FastQC+Trimmomatic组合拳:用fastp v0.23.4一站式搞定NGS数据质控与清洗
  • 告别‘悬空’和‘穿模’:Cesium地形上精准放置GLB模型与广告牌的避坑指南
  • Manus惊天反转:10亿美元回购Meta、“龙虾“奔港IPO,中国AI资本棋局再落一子
  • AIDD入门 | 从SMILES到生成式分子设计:AI如何进入药物发现
  • 算法复杂度分析实战:从递归、DP到图算法与性能优化
  • Spek:终极免费音频频谱分析器,让声音可视化变得简单快速
  • TrollInstallerX一键安装终极教程:iOS 14-16.6.1设备轻松安装TrollStore
  • 如何快速优化Windows显示效果:5种缩放模式的完整指南
  • qt信号和槽链接的接入与断开
  • 产品追溯场景太难?实测企业级Agent全链路跟踪能力,揭秘提效真相
  • 嵌入式开发实战:基于RZ/G2L与Yocto SDK搭建高效交叉编译环境
  • Mythos模型:AI驱动的自主漏洞挖掘与安全对齐实践
  • UEFITOOL 0.28完全指南:从零开始掌握BIOS固件分析与修改
  • 5分钟掌握wxauto:用Python彻底解放你的微信操作时间
  • STM32F103驱动GY-30光照传感器避坑指南:模拟IIC与硬件IIC到底怎么选?
  • 5大核心功能解析:SPT-AKI Profile Editor让你完全掌控离线版塔科夫存档