利用Python脚本实现PubChem SID/CID到SMILES的批量映射与数据增强

张开发
2026/4/10 17:55:03 15 分钟阅读

分享文章

利用Python脚本实现PubChem SID/CID到SMILES的批量映射与数据增强
1. 为什么需要批量获取SMILES数据在药物研发和生物信息学领域SMILES字符串就像化合物的身份证号码。这种用ASCII字符表示分子结构的特殊语言能让计算机快速理解化合物的结构特征。想象一下你手头有5000个化合物的PubChem SID或CID编号现在需要为每个化合物添加对应的SMILES信息——如果手动操作不仅效率低下还容易出错。我去年参与的一个抗病毒药物筛选项目就遇到过这种情况。团队收集了3000多个潜在活性化合物的CID但原始数据表缺少结构信息。当时如果手动处理按每个化合物30秒计算至少需要25小时连续工作。而用Python脚本批量处理算上调试时间也只用了不到2小时。SMILES数据对后续分析至关重要。比如在做分子对接时需要SMILES来生成3D结构构建QSAR模型时SMILES是计算分子描述符的基础。没有这些结构信息很多计算化学工作就无法开展。2. 准备工作与环境配置2.1 安装必要的Python库工欲善其事必先利其器。我们需要三个核心工具pandas数据处理神器requests网络请求必备tqdm可选进度条显示安装命令很简单pip install pandas requests tqdm2.2 了解PubChem的API接口PubChem提供了多种数据获取方式我们主要用两种PUG REST API适合中小规模数据查询每次最多100个CIDFTP批量下载适合超大规模数据整个数据库的子集这里有个实用技巧先在浏览器测试API调用。比如访问这个链接https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/2244/property/CanonicalSMILES/JSON能看到CID为2244的阿司匹林的SMILES数据。3. 核心代码实现3.1 单次查询函数我们先从基础功能做起——根据CID获取SMILESimport requests def get_smiles_by_cid(cid): base_url https://pubchem.ncbi.nlm.nih.gov/rest/pug try: response requests.get( f{base_url}/compound/cid/{cid}/property/CanonicalSMILES/JSON, timeout10 ) return response.json()[PropertyTable][Properties][0][CanonicalSMILES] except Exception as e: print(fError fetching CID {cid}: {str(e)}) return None这个函数有几个关键点设置了10秒超时避免长时间等待使用try-except捕获网络异常返回None表示查询失败3.2 批量查询优化直接循环调用单次查询效率太低我推荐使用PubChem的批量查询功能def get_batch_smiles(cid_list, chunk_size100): base_url https://pubchem.ncbi.nlm.nih.gov/rest/pug all_results {} for i in range(0, len(cid_list), chunk_size): chunk cid_list[i:i chunk_size] cid_string ,.join(map(str, chunk)) try: response requests.post( f{base_url}/compound/cid/property/CanonicalSMILES/JSON, data{cid: cid_string}, timeout30 ) data response.json() for item in data[PropertyTable][Properties]: all_results[item[CID]] item[CanonicalSMILES] except Exception as e: print(fError in batch {i//chunk_size}: {str(e)}) return all_results这里有几个优化点分批处理默认每批100个CID使用POST请求避免URL过长合并所有结果返回统一字典4. 数据增强实战4.1 处理原始数据文件假设我们有个CSV文件compounds.csv结构如下CIDNameMW2244Aspirin180.16处理脚本如下import pandas as pd from tqdm import tqdm def enhance_data(input_file, output_file): df pd.read_csv(input_file) # 确保CID列存在 if CID not in df.columns: raise ValueError(CID column not found in input file) # 获取所有CID cid_list df[CID].unique().tolist() # 批量查询 print(Fetching SMILES from PubChem...) smiles_dict get_batch_smiles(cid_list) # 添加SMILES列 df[SMILES] df[CID].map(smiles_dict) # 保存结果 df.to_csv(output_file, indexFalse) print(fResults saved to {output_file}) # 统计成功率 success_rate df[SMILES].notna().mean() print(fSuccess rate: {success_rate:.2%})4.2 错误处理与重试机制网络请求难免失败完善的错误处理很重要。我改进后的版本包含自动重试失败请求记录失败CID供后续处理进度显示def robust_get_smiles(cid_list, max_retries3): results {} failed_cids [] for cid in tqdm(cid_list, descProcessing CIDs): for attempt in range(max_retries): try: smiles get_smiles_by_cid(cid) if smiles: results[cid] smiles break except Exception as e: if attempt max_retries - 1: failed_cids.append(cid) if failed_cids: print(fFailed to fetch {len(failed_cids)} CIDs) with open(failed_cids.txt, w) as f: f.write(\n.join(map(str, failed_cids))) return results5. 高级技巧与性能优化5.1 多线程加速当处理上万条记录时单线程太慢。我用concurrent.futures实现了多线程版本from concurrent.futures import ThreadPoolExecutor def threaded_batch_query(cid_list, workers8): with ThreadPoolExecutor(max_workersworkers) as executor: results list(tqdm( executor.map(get_smiles_by_cid, cid_list), totallen(cid_list) )) return dict(zip(cid_list, results))注意要点线程数建议4-8个太多会被PubChem限制使用tqdm显示进度结果与输入CID列表保持对应5.2 本地缓存策略频繁查询相同化合物很浪费资源。我添加了本地缓存功能import json from pathlib import Path class SmilesCache: def __init__(self, cache_filesmiles_cache.json): self.cache_file Path(cache_file) self.cache self._load_cache() def _load_cache(self): if self.cache_file.exists(): with open(self.cache_file) as f: return json.load(f) return {} def save_cache(self): with open(self.cache_file, w) as f: json.dump(self.cache, f) def get_smiles(self, cid): cid str(cid) if cid in self.cache: return self.cache[cid] smiles get_smiles_by_cid(cid) if smiles: self.cache[cid] smiles return smiles使用方法cache SmilesCache() smiles cache.get_smiles(2244) # 首次查询网络 smiles cache.get_smiles(2244) # 第二次从缓存读取 cache.save_cache() # 退出前保存6. 完整项目结构经过多次迭代我将这个功能封装成了可复用的Python包目录结构如下pubchem_smiles/ ├── __init__.py ├── api.py # 核心API调用 ├── cache.py # 缓存管理 ├── cli.py # 命令行接口 ├── utils.py # 工具函数 └── tests/ # 单元测试典型使用方式from pubchem_smiles import SmilesEnhancer enhancer SmilesEnhancer(cache_enabledTrue) df enhancer.enhance_dataframe(df, id_columnCID)或者在命令行直接运行python -m pubchem_smiles.cli -i input.csv -o output.csv --cid-column CID7. 常见问题解决在实际使用中我遇到过几个典型问题超时错误增加timeout参数添加重试逻辑CID不存在先用pubchem.get_compounds()验证CID有效性API限流添加延迟如time.sleep(0.1)避免频繁请求SMILES格式不一致统一使用CanonicalSMILES而非IsomericSMILES特别提醒PubChem的API有使用限制非商业用途每分钟最多5个请求。如果需要大规模查询建议申请API Key提升限额使用FTP下载完整数据集分批次处理中间添加延迟8. 扩展应用场景这个技术方案不仅限于SMILES获取稍作修改就能用于分子描述符计算获取LogP、TPSA等物理化学性质化合物分类获取MeSH术语或ChEBI分类交叉数据库映射将PubChem CID转换为ChEMBL ID文献关联获取化合物相关的研究论文比如要同时获取SMILES和分子量def get_multiple_properties(cid): url fhttps://pubchem.../property/MolecularWeight,CanonicalSMILES/JSON response requests.get(url) data response.json() return { MW: data[PropertyTable][Properties][0][MolecularWeight], SMILES: data[PropertyTable][Properties][0][CanonicalSMILES] }9. 替代方案比较除了直接调用API还有几种备选方案PubChem FTP下载优点一次性获取全部数据缺点数据量大需要本地存储和处理RDKit本地处理优点不依赖网络缺点需要预先下载结构数据商业软件如ChemAxon或OpenEye的工具包功能全面但需要授权费用对大多数研究团队来说API查询本地缓存的组合最实用。我在三个项目中测试过处理10万条记录的平均成功率达98.7%耗时约4小时含网络延迟。

更多文章