Files

284 lines
7.9 KiB
Python
Raw Permalink Normal View History

2026-08-03 18:42:47 +08:00
"""
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}")