Files
PlantAI-dev/gfa_core/api_clients/ncbi_client.py
T
2026-08-03 18:42:47 +08:00

284 lines
7.9 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
NCBI API 客户端
NCBI E-utilities API
URL: https://www.ncbi.nlm.nih.gov/
API Docs: https://www.ncbi.nlm.nih.gov/books/NBK25501/
"""
import requests
import time
import xml.etree.ElementTree as ET
from typing import Dict, List, Optional, Any
from dataclasses import dataclass
@dataclass
class NCBIGene:
"""NCBI 基因信息"""
gene_id: str
symbol: str
description: str
organism: str
chromosome: Optional[str] = None
location: Optional[str] = None
class NCBIClient:
"""
NCBI E-utilities API 客户端
用于查询基因、蛋白质、序列等信息
"""
BASE_URL = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils"
def __init__(self, api_key: Optional[str] = None, rate_limit: float = 0.34):
"""
初始化客户端
Args:
api_key: NCBI API 密钥(可提高速率限制到 10 requests/second)
rate_limit: 请求间隔(秒),无 key 时为 3 requests/second
"""
self.api_key = api_key
self.rate_limit = rate_limit if api_key else 0.34
self.last_request_time = 0
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'bbav20311/1.0 (Research Tool)'
})
def _rate_limited_request(self, url: str, params: Dict = None) -> requests.Response:
"""带速率限制的请求"""
elapsed = time.time() - self.last_request_time
if elapsed < self.rate_limit:
time.sleep(self.rate_limit - elapsed)
# 添加 API key
if params is None:
params = {}
if self.api_key:
params['api_key'] = self.api_key
response = self.session.get(url, params=params)
self.last_request_time = time.time()
return response
def search(self, db: str, term: str, retmax: int = 20) -> List[str]:
"""
搜索 NCBI 数据库
Args:
db: 数据库名称(gene, protein, nucleotide, etc.)
term: 搜索词
retmax: 返回结果数
Returns:
ID 列表
"""
url = f"{self.BASE_URL}/esearch.fcgi"
params = {
'db': db,
'term': term,
'retmode': 'json',
'retmax': retmax,
}
try:
response = self._rate_limited_request(url, params)
response.raise_for_status()
data = response.json()
return data.get('esearchresult', {}).get('idlist', [])
except requests.exceptions.RequestException as e:
print(f"[WARN] NCBI API request failed: {e}")
return []
def fetch(self, db: str, ids: List[str], rettype: str = 'json') -> Dict[str, Any]:
"""
获取记录详情
Args:
db: 数据库名称
ids: ID 列表
rettype: 返回类型
Returns:
记录详情
"""
url = f"{self.BASE_URL}/efetch.fcgi"
params = {
'db': db,
'id': ','.join(ids),
'retmode': rettype,
}
try:
response = self._rate_limited_request(url, params)
response.raise_for_status()
if rettype == 'json':
return response.json()
else:
return {'text': response.text}
except requests.exceptions.RequestException as e:
print(f"[WARN] NCBI API request failed: {e}")
return {}
def summary(self, db: str, ids: List[str]) -> List[Dict[str, Any]]:
"""
获取记录摘要
Args:
db: 数据库名称
ids: ID 列表
Returns:
摘要列表
"""
url = f"{self.BASE_URL}/esummary.fcgi"
params = {
'db': db,
'id': ','.join(ids),
'retmode': 'json',
}
try:
response = self._rate_limited_request(url, params)
response.raise_for_status()
data = response.json()
result = data.get('result', {})
uids = result.get('uids', [])
summaries = []
for uid in uids:
summaries.append(result.get(uid, {}))
return summaries
except requests.exceptions.RequestException as e:
print(f"[WARN] NCBI API request failed: {e}")
return []
def search_gene(self, symbol: str, organism: str = "plants") -> List[NCBIGene]:
"""
搜索基因
Args:
symbol: 基因符号
organism: 物种
Returns:
基因信息列表
"""
term = f"{symbol}[Gene] AND {organism}[Organism]"
gene_ids = self.search('gene', term)
if not gene_ids:
return []
summaries = self.summary('gene', gene_ids)
genes = []
for summary in summaries:
gene = NCBIGene(
gene_id=str(summary.get('uid', '')),
symbol=summary.get('name', ''),
description=summary.get('description', ''),
organism=summary.get('organism', ''),
chromosome=summary.get('chromosome', ''),
location=summary.get('maploc', '')
)
genes.append(gene)
return genes
def get_sequence(self, db: str, id: str, seq_start: Optional[int] = None,
seq_stop: Optional[int] = None) -> str:
"""
获取序列
Args:
db: 数据库(nuccore, protein)
id: 序列 ID
seq_start: 起始位置
seq_stop: 终止位置
Returns:
FASTA 格式序列
"""
url = f"{self.BASE_URL}/efetch.fcgi"
params = {
'db': db,
'id': id,
'rettype': 'fasta',
'retmode': 'text',
}
if seq_start and seq_stop:
params['seq_start'] = seq_start
params['seq_stop'] = seq_stop
try:
response = self._rate_limited_request(url, params)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"[WARN] NCBI API request failed: {e}")
return ""
def link(self, dbfrom: str, dbto: str, ids: List[str]) -> Dict[str, List[str]]:
"""
获取数据库间链接
Args:
dbfrom: 源数据库
dbto: 目标数据库
ids: ID 列表
Returns:
链接关系
"""
url = f"{self.BASE_URL}/elink.fcgi"
params = {
'dbfrom': dbfrom,
'db': dbto,
'id': ','.join(ids),
'retmode': 'json',
}
try:
response = self._rate_limited_request(url, params)
response.raise_for_status()
data = response.json()
links = {}
for linkset in data.get('linksets', []):
for linksetdb in linkset.get('linksetdbs', []):
dbname = linksetdb.get('dbto', '')
links[dbname] = linksetdb.get('links', [])
return links
except requests.exceptions.RequestException as e:
print(f"[WARN] NCBI API request failed: {e}")
return {}
# 使用示例
if __name__ == "__main__":
client = NCBIClient()
# 搜索 SPL 基因
genes = client.search_gene("SPL", "Arabidopsis")
print(f"Found {len(genes)} genes")
for gene in genes:
print(f" - {gene.symbol}: {gene.description}")