药源网Scrapy爬虫实战:10万药品数据采集与跨源校验

发布时间:2026/10/12 17:53:23
药源网Scrapy爬虫实战:10万药品数据采集与跨源校验
简介本资源是一套基于Python开发的药品数据采集与建库系统面向计算机、电子信息及生物信息相关专业学生与初阶开发者解决药品结构化数据获取难、数据库建设门槛高的实际问题。压缩包共51个文件含23个核心Python脚本覆盖Scrapy爬虫主逻辑、Selenium反爬处理、正则/XPath解析、ICD编码映射等、9个JSON格式药品元数据、4个CSV药品清单及2个配置文件整体10.73MB结构清晰模块分工明确便于分层学习与功能拓展。已有579人学习下载适合作为课程设计、期末大作业或毕业设计的实战参考项目。读者可直接运行源码获取超10万条中成药与化学药品信息获得完整爬虫工程实践链路——从药源网数据抓取、CFDA官方数据交叉校验到ATC/ICD-9/ICD-10编码体系整合以及中文医学知识图谱基础数据构建含疾病、症状、治疗关系CSV具备较强的数据工程延展性。1. 药源网爬虫不是“点开即用”的玩具它是一套能跑通10万药品数据、但必须亲手调参填坑的Scrapy实战系统你搜“药源网爬虫”十有八九点进的是截图几行requests代码的“教学Demo”——点开运行403报错换UA503封IP加sleep效率掉到每小时200条想存MySQL字段全乱码。而这份源码包是真正在生产级尺度上跑通过的它不只爬了药源网yaopinnet.com主站的中成药、化学药详情页还同步拉取CFDA国产/进口药品注册数据库做交叉校验用ICD-9/ICD-10编码对齐疾病分类并把全部结构化数据落地为drug.csv、disease.csv、symptom.csv三张核心表——最终入库102,847条药品记录字段覆盖【通用名】【商品名】【成分】【适应症】【禁忌】【不良反应】【规格】【批准文号】【生产企业】等临床与监管强相关字段。它适合两类人一是课程设计/毕设需要交“可演示、有数据、能答辩”的计算机类学生二是医疗AI方向的工程师想快速拿到一批带语义标签ATC分类、ICD映射的中文药品原始数据而不是从零写XPath或应付JS渲染。但请注意这不是一个pip install就能run的轮子它的价值恰恰藏在那些被注释掉的Selenium备用方案、icdformat.py里手动清洗的编码映射逻辑、以及scrapy.cfg中被反复调试过的DOWNLOAD_DELAY和CONCURRENT_REQUESTS值里。2. 从环境搭到数据落库Scrapy项目结构拆解与关键配置实操2.1 项目目录树背后的真实分工为什么mycrawler/不是根目录打开zip包第一眼看到的是myspider/而非scrapy startproject mycrawler生成的标准结构——这是本项目最务实的设计选择。myspider/下直接存放spiders/、items.py、pipelines.py省去一层嵌套而ICD-9/、ICD-10/、CFDA/等独立文件夹本质是离线数据源缓存区不是爬虫模块。真正驱动爬取的是get_item.py单页解析脚本和knowledgegraph_crawl.py知识图谱关联爬取它们与Scrapy主框架解耦——这意味着你可以先用get_item.py本地测试单个URL解析逻辑再集成进Scrapy pipeline避免每次改XPath都要启Scrapy调度器。ATC/目录下的atc.py和ATC.json是关键ATCAnatomical Therapeutic Chemical分类是WHO药品编码标准项目用它对药源网中模糊的“功效分类”做标准化映射比如把“清热解毒”统一转为L01抗肿瘤药或J01抗生素这个映射表不是网上下载的而是作者根据药源网页面文本规则手工归纳后存为JSON后续所有药品都打上ATC一级代码标签。提示不要试图直接运行scrapy crawl yaopinnet——项目未提供yaopinnet/spiders/yaopinnet_spider.py实际爬虫入口在knowledgegraph_crawl.py中调用CrawlerProcess启动。这是为绕过Scrapy默认的start_urls硬编码限制实现动态URL队列管理如从drug_jinkou_cfda.json中读取进口药批准文号生成详情页URL。2.2 Scrapy核心配置CONCURRENT_REQUESTS与DOWNLOAD_DELAY的血泪平衡点scrapy.cfg里最关键的不是BOT_NAME而是这两行# scrapy.cfg [settings] BOT_NAME myspider SPIDER_MODULES myspider.spiders NEWSPIDER_MODULE myspider.spiders [deploy] project myspider # 以下为真实调优参数非默认值 [settings] CONCURRENT_REQUESTS 4 DOWNLOAD_DELAY 3.5 RANDOMIZE_DOWNLOAD_DELAY TrueCONCURRENT_REQUESTS 4不是保守而是药源网反爬的实际阈值实测当并发≥5时连续请求10分钟后开始返回空HTMLhtmlbody/body/htmlDOWNLOAD_DELAY 3.5秒是作者在2023年Q4实测出的临界值——低于3.2秒IP被限频HTTP 429高于4.0秒单日爬取量跌破8000条无法覆盖10万数据量。RANDOMIZE_DOWNLOAD_DELAY True开启后实际延迟在[2.8s, 4.2s]区间浮动有效规避固定间隔检测。注意这个值必须配合COOKIES_ENABLED True使用否则Session丢失导致登录态失效药源网部分页面需维持Cookie才能访问详情。2.3 数据管道Pipeline如何把HTML碎片变成drug.csvpipelines.py定义了三重清洗链基础字段提取用lxml.etree解析HTMLXPath定位//div[classdrug-info]/h1/text()取通用名//table[classdrug-table]//tr[td批准文号]/td[2]/text()取批准文号文本归一化调用icdformat.py中的clean_text()函数处理“【禁忌】尚不明确”→“尚不明确”“不良反应偶见皮疹”→“皮疹”删除所有全角标点、多余空格、广告文案如“本品由XX制药荣誉出品”跨源校验对drug_guochan_cfda.json中同通用名的药品比对规格字段是否一致不一致时以CFDA数据为准因CFDA为官方注册信息权威性高于药源网用户编辑内容。最终输出的drug.csv不是简单拼接而是按通用名规格去重后的结果——因为同一药品不同厂家可能有相同通用名但不同规格如“阿司匹林肠溶片 100mg”和“阿司匹林肠溶片 300mg”去重逻辑在pipelines.py第87行if (item[name], item[spec]) not in seen:中实现。3. 解析逻辑深挖XPath与正则如何协同应对药源网的DOM变异3.1 药源网页面结构的三大变异类型及XPath对策药源网详情页存在三种DOM结构变异直接导致XPath失效变异类型典型表现XPath应对策略实际代码位置表格行顺序随机“适应症”行可能在第3行或第5行但td适应症/td总在某tr内改用//tr[td适应症]/td[2]/text()而非//tr[3]/td[2]/text()get_item.py第42行字段合并单元格“不良反应”与“禁忌”共用一个td用br分隔先normalize-space()去空格换行再用re:match()提取不良反应(.?)禁忌get_item.py第65行JavaScript动态注入部分药品的“生产企业”由JS写入span idcompany启用Selenium备用方案在knowledgegraph_crawl.py中调用driver.find_element(By.ID, company).textknowledgegraph_crawl.py第112行注意get_item.py中所有XPath均以//开头而非/html/body/...这是为兼容页面DOCTYPE声明缺失导致的lxml解析差异——药源网部分页面无!DOCTYPE htmllxml会将根节点识别为html而非html xmlnshttp://www.w3.org/1999/xhtml绝对路径XPath必然失败。3.2 正则表达式在字段清洗中的不可替代性XPath擅长定位但清洗依赖正则。pipelines.py中clean_adverse_reactions()函数用到三个关键正则# pipelines.py import re def clean_adverse_reactions(text): # 1. 去除括号内补充说明保留主干“皮疹偶见” → “皮疹” text re.sub(r[^]*?, , text) # 2. 合并重复描述“恶心、呕吐、恶心、腹泻” → “恶心、呕吐、腹泻” items [x.strip() for x in re.split(r[、], text) if x.strip()] unique_items list(dict.fromkeys(items)) # 保持顺序去重 # 3. 标准化术语“头晕目眩” → “头晕”“心悸胸闷” → “心悸、胸闷” standardized [] for item in unique_items: if 头晕目眩 in item: standardized.append(头晕) elif 心悸胸闷 in item: standardized.append(心悸) standardized.append(胸闷) else: standardized.append(item) return 、.join(standardized)这段代码解决的是临床文本的固有噪声药源网用户录入时习惯用复合症状词如“心悸胸闷”而结构化分析需要原子化症状。正则re.sub(r[^]*?, , text)比字符串replace()更鲁棒能处理嵌套括号如“皮疹偶见停药后消失”dict.fromkeys()去重比set()保留原始顺序确保“恶心、呕吐、腹泻”的语序不被破坏——这对后续NLP任务如症状共现分析至关重要。3.3 ATC分类映射为什么不能直接用WHO官网JSONATC/atc.py中map_atc_code()函数的逻辑是# ATC/atc.py def map_atc_code(drug_name, indication): # 规则1含“青霉素”“头孢”字样的归为J01抗生素 if re.search(r(青霉素|头孢), drug_name): return J01 # 规则2indication含“高血压”“心绞痛”归为C07β受体阻滞剂或C09ACEI if re.search(r(高血压|心绞痛), indication): if 美托洛尔 in drug_name or 阿替洛尔 in drug_name: return C07 else: return C09 # 规则3其他情况查预置映射表 return ATC_MAPPING.get(drug_name, R03) # 默认为平喘药WHO官网ATC JSON只提供药品名到ATC码的静态映射但药源网数据中大量药品只有通用名如“复方丹参片”无英文名无法直查WHO表且“复方丹参片”的ATC码应为C10降脂药而非其成分“丹参”的C01心脏治疗药。作者采用规则引擎人工映射表双轨制先用关键词规则覆盖高频药品抗生素、降压药再用ATC.json补全长尾——该JSON是作者从药源网10万条数据中人工标注前1000条后训练简易分类器生成的准确率约89%比纯正则高12个百分点。4. 跨源数据校验CFDA注册库如何修正药源网的“数据漂移”4.1 CFDA数据结构解析为什么drug_jinkou_cfda.json比官网API更可靠药源网数据存在三类漂移批准文号错误如将“国药准字Z20025678”误录为“国药准字Z2002567”规格缺失仅写“片剂”未注明“0.1g/片”企业名称缩写写“华润三九”CFDA库中为“华润三九医药股份有限公司”。drug_jinkou_cfda.json和drug_guochan_cfda.json是作者用Selenium模拟人工查询CFDA官网https://www.nmpa.gov.cn/导出的结构化JSON字段包括approval_number批准文号、product_name商品名、specification规格、manufacturer生产企业全称、approval_date批准日期。相比CFDA官方API需申请密钥、QPS限制5次/秒JSON文件优势在于可离线比对无需网络请求字段完整包含API不返回的approval_date已做基础清洗如统一“片剂”→“片”“胶囊剂”→“胶囊”。4.2 校验Pipeline的执行逻辑何时覆盖何时报警pipelines.py中CFDACheckPipeline的校验流程# pipelines.py class CFDACheckPipeline: def __init__(self): self.imported_drugs json.load(open(CFDA/drug_jinkou_cfda.json, encodingutf-8)) self.domestic_drugs json.load(open(CFDA/drug_guochan_cfda.json, encodingutf-8)) def process_item(self, item, spider): # 1. 用批准文号匹配CFDA数据 cfda_match None for cfda_item in self.imported_drugs self.domestic_drugs: if cfda_item[approval_number] item.get(approval_number): cfda_match cfda_item break if cfda_match: # 2. 规格不一致以CFDA为准记录日志 if item[spec] ! cfda_match[specification]: spider.logger.warning( f规格校验不一致药源网[{item[name]}] {item[spec]} ≠ CFDA {cfda_match[specification]} ) item[spec] cfda_match[specification] # 3. 生产企业名称不一致以CFDA全称为准但保留药源网简称用于搜索 if item[manufacturer] ! cfda_match[manufacturer]: item[manufacturer_full] cfda_match[manufacturer] item[manufacturer] item[manufacturer] # 不覆盖保留原始简称 return item关键设计点不强制覆盖所有字段只修正spec规格和approval_number批准文号因这两项直接影响药品唯一性manufacturer保留原始值因药源网简称如“恒瑞”更利于医生日常检索日志预警而非中断当CFDA无匹配项时不抛异常而是记录spider.logger.info(fCFDA未找到批准文号{item[approval_number]})保证爬虫持续运行——毕竟10万条中约3.2%的批准文号在CFDA库中缺失多为历史批文或港澳台药品。4.3 ICD-9/ICD-10疾病编码对齐为什么需要icd9_new.jsonICD-9/icd9_new.json是作者对原始icd9.json的增强版解决两个问题编码粒度 mismatch药源网“适应症”写“冠心病”ICD-9标准码为414.01冠状动脉粥样硬化性心脏病但原始icd9.json只到414慢性缺血性心脏病一级中文术语差异药源网用“脑梗塞”ICD-9用“脑梗死”需建立{脑梗塞: 434.9, 脑梗死: 434.9}映射。icd9_new.json结构为{ 冠心病: {icd9: 414.01, icd10: I25.10, description: 冠状动脉粥样硬化性心脏病}, 脑梗塞: {icd9: 434.9, icd10: I63.9, description: 急性缺血性脑卒中} }icdformat.py中map_icd_codes()函数遍历item[indications]适应症列表对每个症状词查此JSON返回[{icd9:414.01,icd10:I25.10}]数组最终存入drug.csv的icd9_codes和icd10_codes字段。这种设计使下游可直接用ICD码做疾病聚类无需二次翻译。5. 避坑指南药源网爬虫的五个真实翻车现场与自救方案5.1 现象爬取3000条后突然全部返回空HTMLhtmlbody/body/html原因药源网部署了基于请求频率的IP信誉系统连续请求超阈值后服务器返回空响应而非403且Cookie Session失效。解决立即停止爬虫更换代理IP非免费代理需购买稳定住宅IP并在settings.py中增加RETRY_TIMES 3和RETRY_HTTP_CODES [500, 502, 503, 408, 429]让Scrapy自动重试同时将DOWNLOAD_DELAY从3.5秒提升至5.0秒观察2小时后是否恢复。5.2 现象drug.csv中出现大量“暂无数据”“尚未收录”等占位符原因药源网部分药品详情页结构缺失如新上架药品XPath定位失败后返回默认值而get_item.py中未做空值过滤。解决在pipelines.py的process_item()开头添加校验if not item.get(name) or item[name].strip() in [暂无数据, 尚未收录, ]: raise DropItem(f丢弃无效药品: {item})并确保DropItem异常被Scrapy捕获默认启用。5.3 现象icd9_new.json映射后CSV中ICD码字段为空原因icdformat.py中map_icd_codes()函数对“高血压”“糖尿病”等泛化词未做模糊匹配而药源网常写“II型糖尿病”“原发性高血压”原始JSON只存“糖尿病”“高血压”。解决修改icdformat.py添加关键词截断逻辑def fuzzy_icd_match(symptom): # 截取前2个字符匹配如“II型糖尿病”→“糖尿病” base_term re.sub(r[IVX]型|原发性|继发性, , symptom).strip() return ICD_MAP.get(base_term, ICD_MAP.get(symptom, []))5.4 现象scrapy crawl knowledgegraph报错ModuleNotFoundError: No module named selenium原因knowledgegraph_crawl.py中调用Selenium作为备用方案但requirements.txt未声明selenium依赖且未提示需额外安装ChromeDriver。解决执行pip install selenium并下载与本机Chrome版本匹配的ChromeDriverhttps://chromedriver.chromium.org/解压后将chromedriver.exeWindows或chromedriverMac/Linux放入系统PATH或在代码中指定路径from selenium import webdriver options webdriver.ChromeOptions() options.add_argument(--headless) driver webdriver.Chrome(/path/to/chromedriver, optionsoptions) # 显式指定路径5.5 现象ATC.json加载时报UnicodeDecodeError: gbk codec cant decode byte 0x80原因Windows系统默认用GBK编码读取JSON但ATC.json为UTF-8编码且含中文字符。解决在atc.py中显式指定编码import json with open(ATC/ATC.json, r, encodingutf-8) as f: # 强制UTF-8 ATC_MAPPING json.load(f)6. 进阶技巧用drug.csv构建药品知识图谱的三步验证法6.1 第一步字段完整性验证——确认10万条不是“数字幻觉”很多人下载后直接信“10万余条”但实际drug.csv可能因中途断电只存了8万行。验证方法不是数行数而是检查关键字段的非空率# Linux/Mac终端执行Windows用PowerShell awk -F, NR1 {if($1! $3! $5!) cnt} END {print 核心字段完整率:, cnt/(NR-1)*100 %} drug.csv预期结果应≥98.5%。若低于95%说明爬取过程存在大面积字段缺失——此时要检查pipelines.py中process_item()是否对空字段做了raise DropItem或get_item.py的XPath是否普遍失效。6.2 第二步ATC分类一致性验证——揪出规则引擎的漏网之鱼ATC一级代码共14类A消化道、B血液、C心血管…理想分布应符合药品市场规律C类最多V类最少。用Python快速验证import pandas as pd df pd.read_csv(drug.csv) atc_counts df[atc_code].value_counts().sort_index() print(atc_counts) # 检查是否存在非法代码如X99或空值 invalid_codes df[~df[atc_code].isin([A, B, C, D, G, H, J, L, M, N, P, R, S, V])] print(f非法ATC码数量: {len(invalid_codes)})若invalid_codes非空说明atc.py的规则未覆盖某些药品需回溯get_item.py中drug_name和indication字段补充关键词规则。6.3 第三步ICD映射有效性验证——用疾病共现矩阵反推数据质量真正的知识图谱价值在于关系。取disease.csv疾病列表和drug.csv中的icd10_codes构建疾病-药品共现矩阵# 构建共现矩阵示意代码 from collections import defaultdict disease_drug_map defaultdict(set) for _, row in df.iterrows(): if pd.notna(row[icd10_codes]): for icd in str(row[icd10_codes]).split(|): # 假设多ICD用|分隔 disease_drug_map[icd].add(row[name]) # 找出“高血压”I10关联的TOP10药品 hypertension_drugs list(disease_drug_map.get(I10, set()))[:10] print(高血压常用药:, hypertension_drugs)若输出为[氨氯地平片, 缬沙坦胶囊, 美托洛尔片, ...]说明ICD映射准确若出现[复方丹参片, 六味地黄丸]等非一线降压药则需检查icd9_new.json中“高血压”是否错误关联了中药——这暴露的是人工映射表的质量问题而非爬虫本身。从那以后我每次拿到新的药品爬虫数据都强制走一遍这三步验证先看字段完整率再扫ATC分布最后用ICD共现反向检验。不是为了证明数据“完美”而是快速定位它在哪一环开始失真——因为医疗数据容错率极低一个错的ATC码可能导致整个药物推荐模型偏航。希望帮到你。本文还有配套的精品资源点击获取