化学结构编辑器集成实战:JSME、Ketcher与RDKit构建分子输入方案
2026/9/2 10:43:27 网站建设 项目流程

做化学信息学、医药研发平台或者化学教育工具时,总会遇到一个绕不开的交互难题:怎么让用户把“一个分子”准确又方便地输入到系统里?

直接让用户手敲 SMILES,比如乙醇是CCO,苯环是c1ccccc1,对化学专业用户来说也有些枯燥,更不用说普通使用者了。传一张图片上来,又牵扯结构识别、格式转换、数据校验等一系列问题。于是,“化学结构编辑器”就成了这类系统的标配组件。

这篇文章不打算只介绍某个商业软件,而是从“化学结构编辑器到底解决什么问题”说起,盘点桌面端和 Web 端的主流方案,再带着大家用 JSME、Ketcher 和 RDKit 组合出一套“前端绘制 + 后端校验”的最小可用流程。无论你是做化学相关工具链的开发者,还是刚开始接触化学信息学,都能在本文找到可以直接复用的思路和代码。

1. 化学结构编辑器:让分子“画得出来,也读得懂”

1.1 化学结构编辑器到底是什么

化学结构编辑器,本质上是一个面向分子结构的图形化编辑工具。它提供画布、原子工具、化学键工具、环模板、官能团模板等能力,让用户像画流程图一样把分子结构画出来。

但和普通绘图软件不同,它产出的不是一张“看起来像分子”的图片,而是一份能被化学软件理解的结构数据。换句话说,编辑器在后台维护了一个分子图模型:原子是什么、原子之间怎么连接、化学键是单键还是双键、空间构型如何。用户画出的每一个环、每一条键,都会同步映射成结构化的分子描述。

“结构化”这一点至关重要。因为化学结构不仅是视觉符号,更是一套严谨的编码系统。一个苯环如果只画成六边形而不标注双键位置,化学语义就不完整;而结构编辑器会自动帮你补全这些规则,确保画出来的结构是“化学上成立”的。

1.2 为什么需要将绘制过程做得更自然

早期结构编辑器的交互方式很“工程化”:先选原子类型,再点位置添加原子,然后手动连键。这样画出来的结构准确,但效率极低,尤其画复杂天然产物时堪称灾难。

现代化学结构编辑器的核心目标,就是让绘制过程更接近“纸上画草稿”的直觉:

  • 鼠标拖拽就能连续扩展化学键;
  • 画一个环,编辑器自动生成正六边形并补全原子;
  • 从模板库拖入苯环、羧基、氨基等常用片段;
  • 支持手写结构识别的 AI 工具,把草图直接变成标准结构。

这种“自然化”不仅提升体验,也降低了化学结构录入的门槛,让更多非专业人员也能参与结构检索、结构对比和结果提交。本文标题里说的“让化学结构绘制更加自然”,正是当下结构编辑器最重要的演进方向。

1.3 编辑器输出的常见结构格式

当我们从编辑器中拿到结果,通常是下面几种格式之一:

格式全称特点示例
SMILESSimplified Molecular-Input Line-Entry System一行字符串,轻量、易传输CCO(乙醇)
MOL / SDFMDL Molfile / Structure-Data File块状文本,包含原子坐标和键信息M END结尾的多行文本
InChIInternational Chemical Identifier标准化结构标识,适合去重和比对InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3
MOL2Tripos 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桌面不可集成专业药物化学研究
JSMEWeb快速表单、轻量录入
KetcherWeb企业级化学平台
MolScribe服务端模型结构图批量识别
PubChem SketcherWeb不可集成在线检索辅助

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.txt

4. 实战:在 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()获取当前结构 SMILESvar 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 definedJSME 脚本引入路径错误检查jsme.nocache.js是否在页面加载前引入
Ketcher 初始化后无法获取结构没有等待onInit回调把实例保存到 ref 或状态中,而不是直接在组件中取
getSmiles返回 Promise误把异步 API 当同步调用使用await.then()获取结果
RDKit 无法解析 SMILESSMILES 写法不合法或包含未知原子Chem.MolFromSmiles判断 None,返回具体报错
同一结构每次生成的 SMILES 不同未使用规范 SMILESChem.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 结构检索、化合物相似度计算和结构图的批量生成等实战内容,下一篇见。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询