Files
2026-08-03 18:42:47 +08:00

168 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""
eggNOG GO注释Pipeline
从蛋白序列自动获取GO注释
"""
import os
import requests
import time
from collections import defaultdict
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Set, Optional
EGGNOG_SCAN_URL = "http://eggnogapi5.embl.de/fast_webscan"
EGGNOG_GO_URL = "http://eggnogapi5.embl.de/nog_data/json/go_terms"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept": "application/json"
}
@dataclass
class EggNOGOutput:
gene2go_file: str
gene_count: int
go_term_count: int
failed_genes: List[str] = field(default_factory=list)
def read_fasta(fasta_file: str) -> List[tuple]:
"""读取FASTA文件,返回[(gene_id, sequence), ...]"""
sequences = []
with open(fasta_file) as f:
name, seq = None, []
for line in f:
line = line.strip()
if line.startswith('>'):
if name:
sequences.append((name, ''.join(seq)))
name = line[1:].split()[0] # 去掉.T01等后缀
seq = []
else:
seq.append(line)
if name:
sequences.append((name, ''.join(seq)))
return sequences
def submit_to_eggnog(session: requests.Session, fasta_seq: str) -> Optional[str]:
"""提交序列到eggNOG,返回nogname"""
payload = {"fasta": fasta_seq, "nqueries": 1, "db": "euk"}
try:
resp = session.post(EGGNOG_SCAN_URL, json=payload, headers=HEADERS, timeout=60)
if resp.status_code == 200:
data = resp.json()
if 'seq_matches' in data and data['seq_matches']:
result = data['seq_matches'][0]
if 'hit' in result:
hit = result['hit']
if 'matches' in hit and hit['matches']:
return hit['matches'][0].get('nogname')
except Exception as e:
print(f" 提交错误: {e}")
return None
def get_go_terms(session: requests.Session, nogname: str) -> Set[str]:
"""获取GO terms"""
if not nogname:
return set()
url = f"{EGGNOG_GO_URL}/{nogname}"
try:
resp = session.get(url, headers=HEADERS, timeout=30)
if resp.status_code == 200:
data = resp.json()
go_ids = set()
for category, terms in data.get('go_terms', {}).items():
for term in terms:
go_id = term[0]
if go_id.startswith('GO:'):
go_ids.add(go_id)
return go_ids
except Exception as e:
print(f" GO获取错误: {e}")
return set()
def run_eggnog_pipeline(
pep_file: str,
output_file: str,
delay: float = 1.0
) -> EggNOGOutput:
"""
运行eggNOG GO注释流程
Args:
pep_file: 蛋白FASTA文件路径
output_file: 输出gene2go.txt路径
delay: 请求间隔(秒)
Returns:
EggNOGOutput结果对象
"""
if not os.path.exists(pep_file):
raise FileNotFoundError(f"蛋白文件不存在: {pep_file}")
# 读取序列
sequences = read_fasta(pep_file)
print(f"[eggNOG] 共 {len(sequences)} 条序列")
session = requests.Session()
gene2go = defaultdict(set)
failed_genes = []
for i, (gene_id, seq) in enumerate(sequences):
print(f"[eggNOG] [{i+1}/{len(sequences)}] {gene_id}")
fasta = f">{gene_id}\n{seq}"
# 1. 获取nogname
nogname = submit_to_eggnog(session, fasta)
if not nogname:
print(f" 获取nogname失败")
failed_genes.append(gene_id)
time.sleep(delay)
continue
# 2. 获取GO terms
go_ids = get_go_terms(session, nogname)
gene2go[gene_id] = go_ids
time.sleep(delay)
session.close()
# 保存结果
os.makedirs(os.path.dirname(output_file) if os.path.dirname(output_file) else '.', exist_ok=True)
with open(output_file, 'w') as f:
for gene_id in sorted(gene2go.keys()):
for go_id in sorted(gene2go[gene_id]):
f.write(f"{gene_id}\t{go_id}\n")
# 统计
all_go_terms = set()
for gos in gene2go.values():
all_go_terms.update(gos)
return EggNOGOutput(
gene2go_file=output_file,
gene_count=len(gene2go),
go_term_count=len(all_go_terms),
failed_genes=failed_genes
)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='eggNOG GO注释Pipeline')
parser.add_argument('-i', '--input', required=True, help='蛋白FASTA文件')
parser.add_argument('-o', '--output', required=True, help='输出gene2go.txt')
parser.add_argument('--delay', type=float, default=1.0, help='请求间隔(秒)')
args = parser.parse_args()
result = run_eggnog_pipeline(args.input, args.output, args.delay)
print(f"\n完成! {result.gene_count} 个基因, {result.go_term_count} 个GO terms")
if result.failed_genes:
print(f"失败: {len(result.failed_genes)} 个基因")