做化学信息学、医药研发平台或者化学教育工具时,总会遇到一个绕不开的交互难题:怎么让用户把“一个分子”准确又方便地输入到系统里?
直接让用户手敲 SMILES,比如乙醇是CCO,苯环是c1ccccc1,对化学专业用户来说也有些枯燥,更不用说普通使用者了。传一张图片上来,又牵扯结构识别、格式转换、数据校验等一系列问题。于是,“化学结构编辑器”就成了这类系统的标配组件。
这篇文章不打算只介绍某个商业软件,而是从“化学结构编辑器到底解决什么问题”说起,盘点桌面端和 Web 端的主流方案,再带着大家用 JSME、Ketcher 和 RDKit 组合出一套“前端绘制 + 后端校验”的最小可用流程。无论你是做化学相关工具链的开发者,还是刚开始接触化学信息学,都能在本文找到可以直接复用的思路和代码。
1. 化学结构编辑器:让分子“画得出来,也读得懂”
1.1 化学结构编辑器到底是什么
化学结构编辑器,本质上是一个面向分子结构的图形化编辑工具。它提供画布、原子工具、化学键工具、环模板、官能团模板等能力,让用户像画流程图一样把分子结构画出来。
但和普通绘图软件不同,它产出的不是一张“看起来像分子”的图片,而是一份能被化学软件理解的结构数据。换句话说,编辑器在后台维护了一个分子图模型:原子是什么、原子之间怎么连接、化学键是单键还是双键、空间构型如何。用户画出的每一个环、每一条键,都会同步映射成结构化的分子描述。
“结构化”这一点至关重要。因为化学结构不仅是视觉符号,更是一套严谨的编码系统。一个苯环如果只画成六边形而不标注双键位置,化学语义就不完整;而结构编辑器会自动帮你补全这些规则,确保画出来的结构是“化学上成立”的。
1.2 为什么需要将绘制过程做得更自然
早期结构编辑器的交互方式很“工程化”:先选原子类型,再点位置添加原子,然后手动连键。这样画出来的结构准确,但效率极低,尤其画复杂天然产物时堪称灾难。
现代化学结构编辑器的核心目标,就是让绘制过程更接近“纸上画草稿”的直觉:
- 鼠标拖拽就能连续扩展化学键;
- 画一个环,编辑器自动生成正六边形并补全原子;
- 从模板库拖入苯环、羧基、氨基等常用片段;
- 支持手写结构识别的 AI 工具,把草图直接变成标准结构。
这种“自然化”不仅提升体验,也降低了化学结构录入的门槛,让更多非专业人员也能参与结构检索、结构对比和结果提交。本文标题里说的“让化学结构绘制更加自然”,正是当下结构编辑器最重要的演进方向。
1.3 编辑器输出的常见结构格式
当我们从编辑器中拿到结果,通常是下面几种格式之一:
| 格式 | 全称 | 特点 | 示例 |
|---|---|---|---|
| SMILES | Simplified Molecular-Input Line-Entry System | 一行字符串,轻量、易传输 | CCO(乙醇) |
| MOL / SDF | MDL Molfile / Structure-Data File | 块状文本,包含原子坐标和键信息 | 以M END结尾的多行文本 |
| InChI | International Chemical Identifier | 标准化结构标识,适合去重和比对 | InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3 |
| MOL2 | Tripos Mol2 | 保留更丰富的力场信息 | 常用于分子对接 |
实际开发中,前端编辑器通常输出 SMILES 和 MOL 格式,后端则用 RDKit 这类化学信息学工具做校验、归一化和属性计算。格格式之间可以互相转换,但转换过程必须小心,因为不同工具对芳香性、互变异构、手性标记的处理存在差异。
2. 主流化学结构编辑器方案盘点
2.1 桌面端代表作:ChemDraw、MarvinSketch
在科研论文和学术报告中,ChemDraw 几乎是事实标准。它功能全面,支持 NMR 预测、结构命名、反应方程式排版,尤其适合科研人员深度使用。缺点是商业授权费用不低,且无法直接嵌入到自研 Web 系统。
MarvinSketch 来自 ChemAxon,跨平台能力强,在药物化学领域应用广泛,同样提供丰富的结构编辑和属性计算能力。这类桌面编辑器适合“专业用户 + 专业场景”,但对于系统开发商来说,最关心的是能不能把结构编辑能力集成到自己的产品里。
2.2 Web 端与开源方案:JSME、Ketcher
Web 端结构编辑器是当前系统集成的重点,因为浏览器天然免安装、易分发。
JSME 是一个非常经典的开源编辑器,由瑞士诺华科学家 Peter Ertl 开发。它体积小、部署简单、兼容性极好,通过一个 JavaScript 文件就能在页面中启动一个完整编辑器,支持 SMILES / MOL 的读取与导出。缺点是界面相对朴素,复杂结构编辑效率一般。
Ketcher 是 EPAM 维护的开源结构编辑器,界面现代化,功能更接近桌面级编辑器,支持结构模板、R基、SGroup、立体化学等高级特性。很多知名化学数据平台都直接或二次开发了 Ketcher。它的集成方式也更工程化,适合作为前端工程的一部分引入。
除了这两者,PubChem 在线绘制器、ChemDoodle Web Components 等也都是不错的选择。选择哪一款,取决于你的团队技术栈、部署复杂度预算以及需要的化学功能深度。
2.3 让绘制更自然的 AI 辅助能力
“自然”的另一层含义,是允许用户用最原始的方式——手绘——输入结构。
传统 OCR 很难处理化学结构图,但近几年基于深度学习的结构识别模型已经比较成熟。例如 MolScribe 这类模型,可以通过图片识别直接预测出分子的 SMILES,支持从论文截图、手绘草图中提取结构。这类工具一般部署在服务端,前端上传图片,模型返回候选结构,用户在编辑器中二次确认或修正。
在结构编辑器内部,AI 也在发挥作用。比如:
- 手绘键自动吸附到标准键角;
- 根据原子价态自动补齐缺失氢;
- 点击环中心快速生成并插入环模板;
- 识别用户画出的“歪七扭八”的环并自动拉正。
这些能力让“画得准”不再依赖用户的操作精度,而是由编辑器在底层帮你兜底。
2.4 方案对比小结
| 编辑器 | 类型 | 是否开源 | 集成难度 | 适用场景 |
|---|---|---|---|---|
| ChemDraw | 桌面 | 否 | 不可集成 | 科研绘图、论文撰写 |
| MarvinSketch | 桌面 | 否 | 不可集成 | 专业药物化学研究 |
| JSME | Web | 是 | 低 | 快速表单、轻量录入 |
| Ketcher | Web | 是 | 中 | 企业级化学平台 |
| MolScribe | 服务端模型 | 是 | 中 | 结构图批量识别 |
| PubChem Sketcher | Web | 否 | 不可集成 | 在线检索辅助 |
3. 环境准备与方案选型
3.1 本文示例场景
为了让例子更有参考价值,我们设定这样一个场景:需要在一个 Web 项目中增加“结构录入”功能,用户在前端画出分子,点击提交后,后端对结构做解析和校验,最后把规范化结果存下来并展示结构图。
这个场景覆盖了绝大多数实际需求,也是结构编辑器集成最常见的形态。
3.2 技术栈选择
前端采用 JSME 或 Ketcher,后端使用 Python + RDKit 做结构校验与处理。选择这个组合的原因有两点:
- 前端编辑器直接产出 SMILES/MOL,和后端 RDKit 天然衔接;
- RDKit 是目前开源化学信息学领域功能最全的工具库,文档丰富、社区活跃。
版本需要根据你的项目实际情况调整。JSME 和 Ketcher 的 API 在不同版本之间略有差异,RDKit 版本也会影响部分标准化方法的行为。本文以常见稳定版本为例,重点演示配置和集成思路,遇到版本差异时建议直接查阅对应版本的官方文档。
3.3 示例项目结构
chemical-editor-demo/ ├── frontend/ │ ├── jsme-demo/ # JSME 静态页面示例 │ │ └── index.html │ └── ketcher-demo/ # Ketcher + React 示例 │ ├── package.json │ └── src/ │ └── App.tsx └── backend/ └── rdkit_service/ # RDKit 服务端处理 ├── structure.py └── requirements.txt4. 实战:在 Web 页面中嵌入轻量编辑器 JSME
4.1 下载并引入 JSME
JSME 提供了一个 JavaScript 文件,官方发布目录是https://jsme-editor.github.io/dist/jsme/jsme.nocache.js。出于稳定性和离线部署考虑,建议把整个jsme目录下载到本地静态资源目录,而不是直接引用 CDN 地址。
下载完成后,在 HTML 中引入:
<script type="text/javascript" src="jsme/jsme.nocache.js"></script>JSME 的初始化回调函数是jsmeOnLoad,当编辑器引擎加载完成后,框架会自动调用这个全局函数。我们需要在这个回调里创建编辑器实例。
4.2 初始化编辑器
创建一个index.html,完整代码如下:
<!-- 文件路径:frontend/jsme-demo/index.html --> <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>JSME 化学结构编辑器示例</title> <script type="text/javascript" src="jsme/jsme.nocache.js"></script> <script> // JSME 引擎加载完成后自动调用 function jsmeOnLoad() { var options = { // 可选参数,例如 "legacy" 表示启用旧版按键兼容 options: "legacy" }; window.jsmeApplet = new JSApplet.JSME( "jsme-container", "800px", "500px", options ); // 设置初始结构:乙醇 window.jsmeApplet.setSmiles("CCO"); } // 获取当前绘制的结构 function getStructure() { if (!window.jsmeApplet) { console.warn("JSME 尚未初始化完成"); return; } var smiles = window.jsmeApplet.smiles(); var molfile = window.jsmeApplet.molFile(); document.getElementById("smiles-result").value = smiles; console.log(molfile); } // 清空画布 function clearStructure() { if (window.jsmeApplet) { window.jsmeApplet.setSmiles(""); } } </script> </head> <body> <h2>JSME 化学结构编辑器</h2> <div id="jsme-container"></div> <div style="margin-top: 12px;"> <button onclick="getStructure()">获取结构</button> <button onclick="clearStructure()">清空</button> </div> <div style="margin-top: 12px;"> <label>SMILES 输出:</label> <input type="text" id="smiles-result" style="width: 500px;"> </div> </body> </html>4.3 为什么jsmeOnLoad是全局函数
JSME 是 GWT 编译产物,它需要把 JavaScript 和 Java Applet 时代遗留下来的生命周期逻辑完整保留。jsmeOnLoad就是这个生命周期的入口:只有引擎完全 ready 之后,JSApplet.JSME构造函数才能安全调用。如果你直接在页面load事件里创建实例,有时会报错或出现画布空白,这就是典型的初始化时序问题。
4.4 JSME 常用方法说明
| 方法 | 作用 | 示例 |
|---|---|---|
setSmiles(smiles) | 将 SMILES 渲染到画布 | jsmeApplet.setSmiles("CCO") |
smiles() | 获取当前结构 SMILES | var s = jsmeApplet.smiles() |
molFile() | 获取当前结构 MOL 文本 | var m = jsmeApplet.molFile() |
setMolFile(mol) | 用 MOL 文本渲染结构 | jsmeApplet.setMolFile(molText) |
setCallBackAfterStructureChanged(cb) | 结构变化时回调 | 用于实时联动属性计算 |
在更复杂的系统中,建议用setCallBackAfterStructureChanged监听用户绘图变化,实时更新分子式、分子量等字段,而不是等用户点“获取结构”按钮。
5. 实战:使用 Ketcher 搭建现代化编辑体验
5.1 Ketcher 项目初始化
如果前端团队已经使用 React,我更推荐 Ketcher。它界面更接近 ChemDraw 风格,模板库也更丰富。先初始化一个 React + TypeScript 项目:
npm create vite@latest ketcher-demo -- --template react-ts cd ketcher-demo npm install ketcher-react ketcher-core安装完成之后,直接在 App 组件中引入编辑器。不同版本对样式文件路径的定义不完全一致,具体以安装后的包结构为准。
5.2 在 React 中挂载 Ketcher
// 文件路径:frontend/ketcher-demo/src/App.tsx import { useRef } from 'react'; import { Editor } from 'ketcher-react'; import 'ketcher-react/dist/index.css'; function App() { const ketcherRef = useRef<any>(null); const handleInit = (ketcher: any) => { // 把 ketcher 实例保存到 ref 中,方便组件其他方法调用 ketcherRef.current = ketcher; console.log('Ketcher 初始化完成'); }; // 获取 SMILES 和 MOL const getStructureData = async () => { const ketcher = ketcherRef.current; if (!ketcher) { console.warn('Ketcher 尚未初始化'); return; } const smiles = await ketcher.getSmiles(); const molfile = await ketcher.getMolfile(); console.log('SMILES:', smiles); console.log('MOL:', molfile); }; // 用 SMILES 渲染初始结构 const setExampleStructure = async () => { const ketcher = ketcherRef.current; if (!ketcher) return; await ketcher.setMolecule('c1ccccc1'); // 苯环 }; return ( <div> <h2>Ketcher 结构编辑器</h2> <div style={{ width: '900px', height: '600px', border: '1px solid #ddd' }}> <Editor onInit={handleInit} /> </div> <div style={{ marginTop: 12 }}> <button onClick={getStructureData}>获取结构</button> <button onClick={setExampleStructure}>渲染苯环</button> </div> </div> ); } export default App;5.3 Ketcher API 的异步特点
注意 JSME 的smiles()是同步返回,而 Ketcher 的getSmiles()、getMolfile()返回的是 Promise。原因是 Ketcher 的底层渲染和结构计算封装了更复杂的异步逻辑,因此在调用时需要用await或.then()。
如果你在业务代码中发现“取出来的是 undefined”或“拿到的是 Promise 对象”,大概率就是把异步 API 当成同步方法用了。
5.4 把结构数据提交给后端
结构编辑器只是第一步,后端校验才是决定数据质量的关键。下面给一个前端提交示例:
const submitStructure = async () => { const ketcher = ketcherRef.current; if (!ketcher) return; const molfile = await ketcher.getMolfile(); const response = await fetch('/api/structures', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source: 'ketcher', molfile: molfile, userId: 'demo-user' }) }); const result = await response.json(); console.log('后端处理结果:', result); };6. 实战:用 RDKit 做结构解析与校验
6.1 安装 RDKit
RDKit 提供 Python 包,推荐在虚拟环境中安装:
pip install rdkit安装完成后验证一下版本:
import rdkit from rdkit import Chem print(rdkit.__version__)如果是生产环境,建议锁定rdkit版本号,避免依赖升级导致行为变化。
6.2 解析并校验 SMILES
后端接到的数据可能是 SMILES,也可能是 MOL 文本。无论哪种格式,第一步都是解析成本地Mol对象。解析失败说明结构不合法,这时候要返回明确错误。
# 文件路径:backend/rdkit_service/structure.py from rdkit import Chem from rdkit.Chem import AllChem, Draw, inchi from rdkit.Chem.MolStandardize import rdMolStandardize def validate_smiles(smiles: str) -> dict: """ 校验 SMILES 是否可解析,返回规范化结构和基础属性。 """ if not smiles or not isinstance(smiles, str): return {"ok": False, "error": "SMILES 不能为空"} mol = Chem.MolFromSmiles(smiles) if mol is None: return {"ok": False, "error": f"无法解析 SMILES: {smiles}"} # 生成规范 SMILES,这一步会统一原子书写顺序 canonical_smiles = Chem.MolToSmiles(mol) # 统一电荷表示,去掉带有正负号的原子片段 uncharger = rdMolStandardize.Uncharger() mol_uncharged = uncharger.uncharge(mol) canonical_smiles = Chem.MolToSmiles(mol_uncharged) # 计算分子式 formula = Chem.rdMolDescriptors.CalcMolFormula(mol_uncharged) # 计算精确分子量 exact_mass = Chem.Descriptors.ExactMolWt(mol_uncharged) # 生成 InChI inchi_key = inchi.MolToInchiKey(mol_uncharged) return { "ok": True, "canonical_smiles": canonical_smiles, "formula": formula, "exact_mass": round(exact_mass, 4), "inchi_key": inchi_key } if __name__ == "__main__": # 测试 print(validate_smiles("CCO")) print(validate_smiles("invalid_smiles"))预期输出类似:
{'ok': True, 'canonical_smiles': 'CCO', 'formula': 'C2H6O', 'exact_mass': 46.0419, 'inchi_key': 'LFQSCWFLJHTTHZ-UHFFFAOYSA-N'} {'ok': False, 'error': '无法解析 SMILES: invalid_smiles'}6.3 生成 2D 结构图
很多系统需要在列表页展示结构缩略图。RDKit 可以直接根据 SMILES 生成结构图:
# 文件路径:backend/rdkit_service/structure.py 追加函数 def smiles_to_image(smiles: str, output_path: str) -> str: """ 根据 SMILES 生成 2D 结构图并保存到本地。 """ mol = Chem.MolFromSmiles(smiles) if mol is None: raise ValueError(f"无法解析 SMILES: {smiles}") # 生成 2D 坐标 result = AllChem.Compute2DCoords(mol) if result != 0: print("坐标生成存在警告,但通常仍可正常显示") # 绘制并保存 img = Draw.MolToImage(mol, size=(400, 300)) img.save(output_path) return output_path调用方式:
python -c "from rdkit_service.structure import smiles_to_image; print(smiles_to_image('CCO', 'ethanol.png'))"sanitize等步骤已经在MolFromSmiles内部自动完成,所以生成 2D 坐标时一般无需再手动加氢。如果你的目标是精确再现论文中的 3D 构象,则需要使用AllChem.EmbedMolecule做构象搜索,那是另一个话题。
6.4 为什么需要“后端再校验一次”
前端编辑器给出的结构,理论上已经合法,但实际系统中仍可能混入异常数据:
- 用户在前端没有点击“获取结构”就提交了空值;
- 编辑器版本过旧,输出了不规范的 MOL;
- 网络传输中 SMILES 被截断;
- 恶意用户绕过前端直接提交非法结构。
因此,后端 RDKit 校验绝不能省。它不仅是数据质量的把关人,也是防止异常结构进入数据库的安全边界。
7. 常见问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| JSME 画布空白 | JSME 引擎未加载完成就实例化 | 确认在jsmeOnLoad回调中创建编辑器 |
页面报JSApplet is not defined | JSME 脚本引入路径错误 | 检查jsme.nocache.js是否在页面加载前引入 |
| Ketcher 初始化后无法获取结构 | 没有等待onInit回调 | 把实例保存到 ref 或状态中,而不是直接在组件中取 |
getSmiles返回 Promise | 误把异步 API 当同步调用 | 使用await或.then()获取结果 |
| RDKit 无法解析 SMILES | SMILES 写法不合法或包含未知原子 | 用Chem.MolFromSmiles判断 None,返回具体报错 |
| 同一结构每次生成的 SMILES 不同 | 未使用规范 SMILES | 用Chem.MolToSmiles生成 canonical SMILES |
| 结构图坐标重叠 | 2D 坐标未生成 | 调用AllChem.Compute2DCoords(mol)后再绘图 |
| InChI 相同但 SMILES 不同 | 互变异构导致字符串差异 | 统一使用 InChIKey 做去重和检索键 |
排错时建议遵循固定顺序:先看编辑器是否成功初始化,再检查取出的结构字符串是否合法,最后在后端单独用 RDKit 解析,逐步定位是前端问题、传输问题还是后端解析问题。
8. 最佳实践与工程建议
8.1 服务端必须做结构校验
很多团队在前期图省事,直接信任前端传上来的 SMILES。等到数据库中积累了大量重复或异常结构后,再去清洗数据就非常痛苦。我的经验是:后端接收入口统一做四件事——解析、校验、规范化、生成 InChIKey。InChIKey 可以作为化合物表的唯一索引,天然解决同一结构不同 SMILES 表达的重复问题。
8.2 存储格式的选择
长期存储建议保留两类字段:
- 原始 MOL 文本,保留原子坐标和编辑器的原生成信息;
- 规范 SMILES 和 InChIKey,用于检索、去重和属性计算。
只存 SMILES 的缺点是丢失了 2D 坐标,后续每次展示都要重新计算;只存 MOL 的缺点是不方便直接做字符串检索。双字段是工程上更稳妥的做法。
8.3 编辑器选型建议
- 中小型管理系统、表单类页面:优先 JSME,部署成本低,运行稳定;
- 化学专业平台、需要复杂结构编辑:优先 Ketcher,界面和功能都更接近桌面编辑器;
- 需要批量识别论文结构图:再加一层 AI 结构识别服务,识别结果回填到编辑器中人工确认。
不要一上来就追求“功能最全”,而要看你的用户是否真的需要 R 基、SGroup、立体化学这些高级特性。大部分内部系统用 JSME 完全够用。
8.4 安全与性能注意
- 对上传的 MOL 文本设置大小限制,防止超大文件拖垮后端;
- RDKit 解析放在独立服务或独立进程中,避免因异常结构导致主服务崩溃;
- 结构图生成考虑加缓存,相同 InChIKey 不必每次重新渲染;
- 如果系统面向公开网络,务必对结构提交接口做用户鉴权,防止被脚本刷接口。
8.5 关注自然绘制能力的落地边界
手绘识别和 AI 辅助非常诱人,但要合理管理用户预期。手绘识别适合“从草图得到标准结构”的二次确认流程,不太适合直接作为唯一录入入口。更稳妥的设计是:用户手绘图片上传,后端返回候选结构,前端把候选结构渲染到编辑器中,用户确认或修改后再提交。这样既保留了自然输入的便捷性,又保证了最终进入系统的结构是经过人工确认的合法结构。
9. 总结与下一步学习方向
本文从“化学结构编辑器是干什么的”开始,梳理了桌面端、Web 端和 AI 辅助识别三类方案,然后给出了 JSME、Ketcher 和 RDKit 的最小集成示例。核心思路是:前端负责“画得自然”,后端负责“存得规范”。
如果你准备在真实项目中落地,建议按下面顺序推进:先在本地跑通 JSME 或 Ketcher 的页面集成,确认能稳定取出 SMILES 和 MOL;再在后端接入 RDKit,完成解析、校验、规范 SMILES 和 InChIKey 生成;最后把结构图缓存、化合物去重和权限控制补上。
接下来可以继续学习的方向包括:RDKit 的 3D 构象生成、子结构搜索与相似度检索、反应 SMILES 与化学反应预测、以及如何把手绘结构识别模型集成到现有编辑流程中。化学结构编辑器的底层是化学信息学,而化学信息学的深处还连接着计算化学与人工智能,每一步都值得慢慢探索。
如果这篇文章对你有帮助,欢迎收藏备用。后面我还会继续整理 RDKit 结构检索、化合物相似度计算和结构图的批量生成等实战内容,下一篇见。