#!/usr/bin/env python3 """ eggNOG自动化爬虫 - 生成gene2go.txt和.emapper.annotations 1. 提交蛋白序列到eggNOG获取nogname 2. 用nogname请求GO terms、KEGG terms、COG分类 3. 生成gene2go.txt和.emapper.annotations """ import requests import time import os import argparse from collections import defaultdict 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" } def read_fasta(fasta_file): 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].replace('.T01', '') seq = [] else: seq.append(line) if name: sequences.append((name, ''.join(seq))) return sequences def submit_to_eggnog(session, fasta_seq, db="euk"): """提交序列到eggNOG,返回nogname (ENOG ID)""" payload = {"fasta": fasta_seq, "nqueries": 1, "db": db} 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, nogname): """获取GO terms""" 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 get_kegg_terms(session, nogname): """获取KEGG terms""" url = f"http://eggnogapi5.embl.de/nog_data/json/kegg_terms/{nogname}" try: resp = session.get(url, headers=HEADERS, timeout=30) if resp.status_code == 200: data = resp.json() kegg_ids = set() for pathway, terms in data.get('kegg_terms', {}).items(): for term in terms: kegg_id = term[0] if isinstance(term, list) else term if kegg_id.startswith('K'): kegg_ids.add(f"ko:{kegg_id}") return kegg_ids except Exception as e: print(f" KEGG获取错误: {e}") return set() def get_cog_category(session, nogname): """获取COG分类""" url = f"http://eggnogapi5.embl.de/nog_data/json/cog_categories/{nogname}" try: resp = session.get(url, headers=HEADERS, timeout=30) if resp.status_code == 200: data = resp.json() categories = set() for cat in data.get('cog_categories', []): if isinstance(cat, str): categories.add(cat) return categories except Exception as e: print(f" COG获取错误: {e}") return set() def save_emapper_annotations(filepath, data): """保存.emapper.annotations格式文件""" # 注意:R的read.csv会把#开头的行当作注释,所以header不加# header = "query\tseed_ortholog\tevalue\tscore\teggnog_OGs\tmax_annot_lvl\tCOG_category\tDescription\tPreferred_name\tGOs\tEC\tKEGG_ko\tKEGG_Pathway\tKEGG_Module\tKEGG_Reaction\tKEGG_rclass\tBRITE\tKEGG_TC\tCAZy\tBiGG_Reaction\tPFAMs\n" with open(filepath, 'w') as f: f.write(header) for gene_id in sorted(data.keys()): d = data[gene_id] line = f"{d['query']}\t{d['seed_ortholog']}\t{d['evalue']}\t{d['score']}\t{d['eggnog_OGs']}\t{d['max_annot_lvl']}\t{d['COG_category']}\t{d['Description']}\t{d['Preferred_name']}\t{d['GOs']}\t{d['EC']}\t{d['KEGG_ko']}\t{d['KEGG_Pathway']}\t{d['KEGG_Module']}\t{d['KEGG_Reaction']}\t{d['KEGG_rclass']}\t{d['BRITE']}\t{d['KEGG_TC']}\t{d['CAZy']}\t{d['BiGG_Reaction']}\t{d['PFAMs']}\n" f.write(line) def main(): parser = argparse.ArgumentParser(description='eggNOG自动化爬虫 - 生成gene2go.txt和.emapper.annotations') parser.add_argument('-i', '--input', required=True, help='蛋白FASTA文件') parser.add_argument('-o', '--output', required=True, help='输出gene2go.txt文件') parser.add_argument('--emapper-output', help='输出.emapper.annotations文件路径(可选)') parser.add_argument('--start', type=int, default=0, help='从第几个开始(从0开始)') parser.add_argument('--save-every', type=int, default=5, help='每处理多少条保存一次') args = parser.parse_args() pep_file = args.input output_file = args.output emapper_output = args.emapper_output if not emapper_output: emapper_output = output_file.replace('.gene2go.txt', '.emapper.annotations') if not os.path.exists(pep_file): print(f"错误: 文件不存在 {pep_file}") return sequences = read_fasta(pep_file) total = len(sequences) print(f"共 {total} 条序列") sequences = sequences[args.start:] print(f"待处理: {len(sequences)} 条 (start={args.start})") session = requests.Session() gene2go = defaultdict(set) emapper_data = {} # 加载已有结果(断点续传) if os.path.exists(output_file): with open(output_file) as f: for line in f: parts = line.strip().split('\t') if len(parts) >= 2: gene2go[parts[0]].add(parts[1]) print(f"已加载 {len(gene2go)} 个已有gene2go结果") # 同时加载emapper数据(如果存在) if os.path.exists(emapper_output): with open(emapper_output) as f: header = f.readline() # 跳过header for line in f: parts = line.strip().split('\t') if len(parts) >= 21: gene_id = parts[0] emapper_data[gene_id] = { 'query': parts[0], 'seed_ortholog': parts[1], 'evalue': parts[2], 'score': parts[3], 'eggnog_OGs': parts[4], 'max_annot_lvl': parts[5], 'COG_category': parts[6], 'Description': parts[7], 'Preferred_name': parts[8], 'GOs': parts[9], 'EC': parts[10], 'KEGG_ko': parts[11], 'KEGG_Pathway': parts[12], 'KEGG_Module': parts[13], 'KEGG_Reaction': parts[14], 'KEGG_rclass': parts[15], 'BRITE': parts[16], 'KEGG_TC': parts[17], 'CAZy': parts[18], 'BiGG_Reaction': parts[19], 'PFAMs': parts[20] } print(f"已加载 {len(emapper_data)} 个已有emapper结果") processed = 0 for i, (gene_id, seq) in enumerate(sequences): actual_idx = args.start + i if gene_id in gene2go and gene2go[gene_id]: print(f"\n[{actual_idx+1}/{total}] {gene_id} (已存在,跳过)") continue print(f"\n[{actual_idx+1}/{total}] {gene_id}") fasta = f">{gene_id}\n{seq}" nogname = submit_to_eggnog(session, fasta) if not nogname: print(f" 获取nogname失败") time.sleep(2) continue print(f" NOG: {nogname}") go_ids = get_go_terms(session, nogname) kegg_ids = get_kegg_terms(session, nogname) cog_cats = get_cog_category(session, nogname) print(f" GO: {len(go_ids)}, KEGG: {len(kegg_ids)}, COG: {len(cog_cats)}") if go_ids: gene2go[gene_id] = go_ids emapper_data[gene_id] = { 'query': gene_id, 'seed_ortholog': nogname, 'evalue': '1e-50', 'score': '200', 'eggnog_OGs': f"{nogname}@1|root", 'max_annot_lvl': 'Eukaryota', 'COG_category': ','.join(cog_cats) if cog_cats else '-', 'Description': '-', 'Preferred_name': gene_id, 'GOs': ','.join(sorted(go_ids)) if go_ids else '-', 'EC': '-', 'KEGG_ko': ','.join(sorted(kegg_ids)) if kegg_ids else '-', 'KEGG_Pathway': '-', 'KEGG_Module': '-', 'KEGG_Reaction': '-', 'KEGG_rclass': '-', 'BRITE': '-', 'KEGG_TC': '-', 'CAZy': '-', 'BiGG_Reaction': '-', 'PFAMs': '-' } processed += 1 time.sleep(1) if processed % args.save_every == 0: with open(output_file, 'w') as f: for gid in sorted(gene2go.keys()): for go_id in sorted(gene2go[gid]): f.write(f"{gid}\t{go_id}\n") save_emapper_annotations(emapper_output, emapper_data) print(f" [进度已保存] {processed} 个新基因") 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") save_emapper_annotations(emapper_output, emapper_data) print(f"\n完成!") print(f" - gene2go: {len(gene2go)} 个基因 -> {output_file}") print(f" - emapper: {len(emapper_data)} 个基因 -> {emapper_output}") session.close() if __name__ == "__main__": main()