数据采集与爬虫技术:爬取药物-靶点关系数据

模型需要数据喂养,这一点大家都清楚。但做药物靶点图谱,最缺的就是干净的结构化数据。别指望一个公开数据集能搞定所有事情,很多时候,你得自己去“拿”。

这一章,我重点聊聊怎么从几个核心公开数据库里,把药物-靶点关系数据抓下来。我个人习惯用Python写爬虫,下面就把我的常用套路拆开给你看。

核心思路:针对 DrugBank、ChEMBL、DGIdb 等数据库,利用它们提供的 RESTful API 或者预下载文件,批量获取药物分子与靶点蛋白之间的关联记录。而不是去写一个通用爬虫满网站乱逛,那样效率极低。

为什么我们要自己爬?

你可能会问,不是有现成的数据集吗?是的,有。但存在几个问题:

  • 时效性:公开数据集更新慢,新批准的药物几个月后才入库。
  • 覆盖度:单一数据集可能只包含已知的、验证过的关系。你需要补充一些文献挖掘的结果。
  • 清洗成本:拿到手的数据通常跟目标图谱的Schema(模式)不匹配,需要大量转换。自己爬的时候,可以按需求定制字段。

说白了,爬虫不只是复制粘贴数据,它是为你的知识图谱量身定做的“数据搬运工”。

数据源与合规边界

动手之前,先得搞清楚一个底线:

数据库 数据获取方式 爬取难度 注意事项
DrugBank 官方 XML/CSV 下载(需申请) 低(文件解析) 商业用途需授权,学术免费
ChEMBL RESTful API + 批量下载 中(API限流) 完全开放,注意频率
DGIdb Web API 查询 低(按需查询) 单次查询有上限
STITCH TSV 文件下载 低(大文件处理) 数据量大,需要存储

我曾经历过一次教训:有同事为了赶进度,用高并发爬虫去抓一个学术数据库。结果禁了IP,整个实验室的访问都被封了。切记:尊重 robots.txt,控制请求频率,最好用官方API。

爬虫的技术路线

我通常把爬虫拆成几个阶段:请求 -> 解析 -> 清洗 -> 存储。下面这张图可以帮你快速建立整体认知。

药物-靶点爬虫流程概览 数据源 Python 爬虫 请求与限流 数据解析 结构化存储 关系三元组

嗯,这里要注意一点:不是所有数据库都开着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万行关系。我一股脑全读到内存里,机器直接卡死。后来改成逐行读取并写入数据库,平稳运行。千万注意内存管理!

嗯,到这里,你应该已经对爬虫的整体流程有个清楚的把握了。数据已经拿到手,而且清洗成了三元组格式。下一件事情,就是把这些数据导入到图数据库里,然后开始真正的知识图谱构建工作。