数据采集与爬虫技术:爬取药物-靶点关系数据
模型需要数据喂养,这一点大家都清楚。但做药物靶点图谱,最缺的就是干净的结构化数据。别指望一个公开数据集能搞定所有事情,很多时候,你得自己去“拿”。
这一章,我重点聊聊怎么从几个核心公开数据库里,把药物-靶点关系数据抓下来。我个人习惯用Python写爬虫,下面就把我的常用套路拆开给你看。
核心思路:针对 DrugBank、ChEMBL、DGIdb 等数据库,利用它们提供的 RESTful API 或者预下载文件,批量获取药物分子与靶点蛋白之间的关联记录。而不是去写一个通用爬虫满网站乱逛,那样效率极低。
为什么我们要自己爬?
你可能会问,不是有现成的数据集吗?是的,有。但存在几个问题:
- 时效性:公开数据集更新慢,新批准的药物几个月后才入库。
- 覆盖度:单一数据集可能只包含已知的、验证过的关系。你需要补充一些文献挖掘的结果。
- 清洗成本:拿到手的数据通常跟目标图谱的Schema(模式)不匹配,需要大量转换。自己爬的时候,可以按需求定制字段。
说白了,爬虫不只是复制粘贴数据,它是为你的知识图谱量身定做的“数据搬运工”。
数据源与合规边界
动手之前,先得搞清楚一个底线:
| 数据库 | 数据获取方式 | 爬取难度 | 注意事项 |
|---|---|---|---|
| DrugBank | 官方 XML/CSV 下载(需申请) | 低(文件解析) | 商业用途需授权,学术免费 |
| ChEMBL | RESTful API + 批量下载 | 中(API限流) | 完全开放,注意频率 |
| DGIdb | Web API 查询 | 低(按需查询) | 单次查询有上限 |
| STITCH | TSV 文件下载 | 低(大文件处理) | 数据量大,需要存储 |
我曾经历过一次教训:有同事为了赶进度,用高并发爬虫去抓一个学术数据库。结果禁了IP,整个实验室的访问都被封了。切记:尊重 robots.txt,控制请求频率,最好用官方API。
爬虫的技术路线
我通常把爬虫拆成几个阶段:请求 -> 解析 -> 清洗 -> 存储。下面这张图可以帮你快速建立整体认知。
嗯,这里要注意一点:不是所有数据库都开着API等你。比如DrugBank,它的官方途径是下载XML压缩包,里面包含了所有药物、靶点、通路、适应症等信息。反而用爬虫去页面抓取是最蠢的做法。
实战:爬取DrugBank药物靶点数据
我会以DrugBank为例,讲讲最常用的模式。你申请下载好 drugbank.xml 后,需要解析它。
import xml.etree.ElementTree as ET
import gzip
def parse_drugbank(drugbank_xml_gz_path):
"""
从DrugBank XML中提取药物-靶点关系
我常用下面这个套路,效率很高
"""
relationships = []
with gzip.open(drugbank_xml_gz_path, 'rb') as f:
tree = ET.parse(f)
root = tree.getroot()
ns = {'db': 'http://www.drugbank.ca'}
for drug in root.findall('db:drug', ns):
drug_id = drug.find('db:drugbank-id', ns)
if drug_id is not None:
drug_id = drug_id.text
drug_name = drug.find('db:name', ns)
drug_name = drug_name.text if drug_name is not None else ''
# 抽取靶点信息
targets = drug.find('db:targets', ns)
if targets is None:
continue
for target in targets.findall('db:target', ns):
polypeptide = target.find('db:polypeptide', ns)
if polypeptide is not None:
target_id = polypeptide.get('id')
target_name = polypeptide.find('db:name', ns)
target_name = target_name.text if target_name is not None else ''
relationships.append({
'drug_id': drug_id,
'drug_name': drug_name,
'target_id': target_id,
'target_name': target_name,
'action': 'target' # 这里简化,实际上可以更精细
})
return relationships
# 调用
relations = parse_drugbank('drugbank.xml.gz')
print(f'一共爬取了 {len(relations)} 条关系')
小技巧:解析XML时,结构可能非常复杂。我建议先打印节点结构玩一玩。遇到 target 下有多级 polypeptide 的情况,可以用递归处理。
数据清洗与结构化存储
数据抓下来后,问题才刚开始。你想想看,同一款药物,在不同数据库里可能用不同ID表示。比如DrugBank ID是DB00001,但在ChEMBL里它的ID可能是CHEMBL12345。你需要做一个ID映射。
我习惯的做法是:先存储成关系三元组的格式,也就是 (subject, predicate, object),方便后续导入图数据库。
import csv
def save_as_triples(relationships, output_path):
with open(output_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['subject_id', 'subject_name', 'predicate', 'object_id', 'object_name'])
for rel in relationships:
writer.writerow([
f'drug:{rel["drug_id"]}',
rel['drug_name'],
'targets',
f'target:{rel["target_id"]}',
rel['target_name']
])
save_as_triples(relations, 'drug_target_triples.csv')
爬虫踩坑实录
讲了这么多,其实爬虫不难,难在应对各种意外情况。我总结几个最常见的坑:
- XML结构变更:数据库版本升级后,命名空间或标签可能会变。我经历过一次,爬虫跑了半天全抓空了。解决方案:永远写一个小的校验脚本,先跑10条数据看看格式对不对。
- API限流:ChEMBL的API限制是每秒3-5个请求,稍微快一点就会被暂时封禁。这时候用
time.sleep()没商量。 - 大文件内存溢出:DrugBank的完整XML解压后有2-3GB。内存不够时,用迭代解析(比如
iterparse)代替一次性加载。
我曾经踩过的坑:有个项目需要爬取STITCH数据库的TSV文件,文件里面有2000万行关系。我一股脑全读到内存里,机器直接卡死。后来改成逐行读取并写入数据库,平稳运行。千万注意内存管理!
嗯,到这里,你应该已经对爬虫的整体流程有个清楚的把握了。数据已经拿到手,而且清洗成了三元组格式。下一件事情,就是把这些数据导入到图数据库里,然后开始真正的知识图谱构建工作。