关于 zinc-database
ZINC22 是面向虚拟筛选的可购化合物数据库,通过 CartBlanche 提供查询接口,适用于药物发现和分子对接工作。它可按 ZINC ID 查询化合物记录,也支持由 SMILES 精确查找标识符及供应商信息。
主要功能
- 01按 ZINC ID 获取化合物记录
- 02将 SMILES 精确映射到 ZINC ID
- 03查看供应商目录与可购状态
- 04获取分子属性和结构标识符
- 05使用 RDKit 在本地筛选属性
适用场景
- 01核查候选化合物是否可购买
- 02将 SMILES 候选集整理为 ZINC 标识符
- 03为分子对接准备化合物集合
- 04用 RDKit 按属性筛选候选分子
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jaechang-hits/ sciagent-skills --skill zinc-database安装后在 Claude Code 中输入/zinc-database确认已加载。
详细介绍
ZINC 化学品库数据库(ZINC22 / CartBlanche)
概览
ZINC(ZINC Is Not Commercial)是一个免费的商业可购化合物数据库,专为虚拟筛选整理。当前版本 ZINC22 收录了数十亿种按需合成和现货分子,并通过 CartBlanche 网页应用提供服务,网址为 cartblanche22.docking.org。CartBlanche 提供了一个小型 JSON API,可用于按 ZINC ID 查询物质、将 SMILES 解析为对应的 ZINC 标识符,以及查看供应商和目录中的可购情况。
重要提示——旧版 ZINC15 REST API 已无法用于自动化访问。
zinc15.docking.org(以及zinc.docking.org)现在启用了全站 CAPTCHA:每次请求/substances.json、/tranches/...、/substances/{id}.json等路径时,都会被重定向到/captcha/页面或返回403 Forbidden。任何使用mwt__gte/availability/similarity查询参数访问zinc15.docking.org的脚本都会失败。请改用下文介绍的 ZINC22 CartBlanche 端点。
关于 ZINC22/CartBlanche,以下三点会影响此技能的使用方式:
- 不支持服务器端属性范围查询。 ZINC22/CartBlanche 不支持
mwt__gte/logp__lte/hbd__lte搜索。按分子属性筛选的方法有两种:(a) 选择 tranches(MW x logP 二维网格)进行批量下载,或 (b) 获取化合物后用 RDKit 在本地筛选。本技能介绍使用本地 RDKit 的方法。 - SMILES 查询是异步的。 POST 提交 SMILES 后会收到一个
taskID,之后需轮询获取结果。精确匹配查询可在数秒内完成。 - 可靠的程序化搜索 = 精确匹配。 API 范围更广的类似物搜索(
dist> 0)速度很慢,而且在公共服务上经常超时;Smallworld 相似性搜索仅支持通过网站操作。请将精确 SMILES → ZINC ID 查询视为可靠的基础操作;有限的替代方法请参阅“类似物 / 相似性搜索”说明。
适用场景
- 查询已知 ZINC ID,获取其 SMILES、计算属性以及可购情况/供应商信息
- 将 SMILES 格式的分子解析为对应的 ZINC22 标识符,以确认是否有货
- 下单前确认命中化合物是否可购,以及哪些目录中有售
- 整理 SMILES/ZINC ID 集合,用于对接筛选活动
- 构建按属性筛选的化合物库时,将此处获取的 SMILES/tranche 与本地 RDKit 筛选结合使用(
rdkit-cheminformatics) - 查询已知药物的生物活性数据,请使用
chembl-database-bioactivity;查询已批准药物的结构,请使用drugbank-database-access
前提条件
- Python 包:
requests、pandas(本地属性筛选还需rdkit) - 数据要求:ZINC ID 或 SMILES 字符串
- 环境:需要互联网连接;无需 API 密钥;SMILES 查询为异步操作(需轮询获取结果)
- 速率限制:请合理使用——串行执行搜索、缓存结果,并且轮询 task 端点的间隔不要短于几秒
pip install requests pandas
# optional, for local property filtering:
pip install rdkit
快速开始
import requests
BASE = "https://cartblanche22.docking.org"
HEADERS = {"User-Agent": "sciagent-zinc-skill/1.0"}
# Look up a substance by ZINC ID (synchronous, returns JSON immediately)
r = requests.get(f"{BASE}/substance/ZINC000000029632.json", headers=HEADERS, timeout=30)
r.raise_for_status()
c = r.json()
td = c["tranche_details"]
print(f"ZINC ID : {c['zinc_id']} (db: {c['db']})")
print(f"SMILES : {c['smiles']}")
print(f"MW : {td['mwt']:.2f} logP: {td['logp']:.2f} heavy atoms: {td['heavy_atoms']}")
print(f"InChIKey: {td['inchikey']}")
print(f"Catalogs: {len(c.get('catalogs', []))} supplier entries")
预期输出(节选):
ZINC ID : ZINC000000029632 (db: zinc20)
SMILES : O=C([C@H]1CCCN1C(=O)Cc1c[nH]c2ccccc12)N1CCc2ccccc2C1
MW : 387.48 logP: 3.29 heavy atoms: 29
InChIKey: QCEMBLKSDIWPBK-JOCHJYFZSA-N
Catalogs: 4 supplier entries
核心 API
CartBlanche 基础 URL 和下文会重复使用的两个辅助函数:
import requests, time
BASE = "https://cartblanche22.docking.org"
HEADERS = {"User-Agent": "sciagent-zinc-skill/1.0"}
def get_substance(zinc_id):
"""Fetch full record for one ZINC ID (synchronous)."""
r = requests.get(f"{BASE}/substance/{zinc_id}.json", headers=HEADERS, timeout=30)
r.raise_for_status()
return r.json()
def smiles_lookup(smiles, dist=0, adist=0, database="zinc22", timeout=120, poll=5):
"""Resolve a SMILES to ZINC22 substance dicts via the async task API.
dist=0, adist=0 -> exact match (fast, reliable; use this).
dist/adist > 0 -> near-neighbor analog search (slow; may time out — see note).
Returns a list of substance dicts (possibly empty).
"""
files = {"smiles": (None, smiles), "dist": (None, str(dist)),
"adist": (None, str(adist)), "database": (None, database)}
sub = requests.post(f"{BASE}/smiles.json", files=files, headers=HEADERS, timeout=60)
sub.raise_for_status()
task = sub.json()["task"] # async: returns {"task": "<uuid>"}
deadline = time.time() + timeout
while time.time() < deadline:
res = requests.get(f"{BASE}/search/result/{task}", headers=HEADERS, timeout=30).json()
if res.get("status") == "SUCCESS":
data = res.get("result")
# result is {"zinc22": [...], "zinc20": [...]} when hits exist,
# or an empty list when there are none.
if isinstance(data, dict):
return data.get("zinc22", []) + data.get("zinc20", [])
return data or []
time.sleep(poll) # status == "PROGRESS" until done
raise TimeoutError(f"ZINC22 search task {task} did not finish in {timeout}s")
查询 1:按 ZINC ID 获取化合物
获取已知 ZINC 标识符对应的完整记录(SMILES、计算属性、分子式、环和杂原子数量、供应商目录)。
c = get_substance("ZINC000000029632")
td = c["tranche_details"]
print(f"ZINC ID : {c['zinc_id']}")
print(f"SMILES : {c['smiles']}")
print(f"Formula : {c['mol_formula']}")
print(f"MW : {td['mwt']:.2f}")
print(f"logP : {td['logp']:.2f}")
print(f"Heavy atoms: {td['heavy_atoms']}")
print(f"Rings : {c['rings']} Hetero atoms: {c['hetero_atoms']}")
print(f"InChIKey : {td['inchikey']}")
print(f"Suppliers : {len(c.get('catalogs', []))}")
现代格式的 ZINCbq.../ZINCbj... ID 和旧版 12 位 ZINC000... ID 均可通过此端点解析。
查询 2:从 SMILES 解析 ZINC ID(精确匹配)
查找 SMILES 字符串所表示分子对应的 ZINC22 标识符。此方法是可靠的搜索基础操作。
hits = smiles_lookup("CC(=O)Nc1ccc(O)cc1") # acetaminophen, exact (dist=0)
print(f"Exact matches: {len(hits)}")
for h in hits[:5]:
td = h["tranche_details"]
print(f" {h['zinc_id']:22s} MW {td['mwt']:.1f} logP {td['logp']:.2f} {h['smiles']}")
同一个分子可能对应 ZINC22 子数据库中的多个 ZINC ID(例如 ZINCbq... 和 ZINCbj...)——每个 ID 都代表不同构建版本中的同一结构。
查询 3:可购买性和供应商目录
可购买性已嵌入物质记录的 catalogs 中,无需单独请求。
c = get_substance("ZINC000000029632")
catalogs = c.get("catalogs", [])
print(f"Supplier entries: {len(catalogs)}")
for cat in catalogs:
print(f" {cat.get('catalog_name', 'n/a'):24s} | "
f"purchasable: {cat.get('purchase')} | "
f"code: {cat.get('supplier_code', 'n/a')} | "
f"price: {cat.get('price', 'n/a')} {cat.get('unit', '')}")
purchase: 1 表示该化合物可从该目录订购;条目可能包含价格、数量和交货时间(shipping)。
查询 4:按属性筛选分子库(使用 RDKit 本地筛选)
ZINC22 不支持服务器端 MW/logP 查询。通用做法是:收集候选 SMILES(已解析的 ZINC ID、下载的分块文件或自行枚举的结果),然后使用 RDKit 在本地筛选。
import pandas as pd
from rdkit import Chem
from rdkit.Chem import Descriptors, Lipinski
# Candidate SMILES (here: a small hand list; in practice load a tranche .smi or your own set)
smiles_list = [
"CC(=O)Nc1ccc(O)cc1", # acetaminophen (MW 151 - too small, filtered out)
"CC(C)NCC(O)COc1cccc2ccccc12", # propranolol (MW 259, logP 2.6 - lead-like)
"CN(C)CCOC(c1ccccc1)c1ccccc1", # diphenhydramine
"c1ccc(NC(=O)c2ccccc2)cc1", # benzanilide (MW 197 - too small, filtered out)
]
def lead_like(smi):
m = Chem.MolFromSmiles(smi)
if m is None:
return None
mw, logp = Descriptors.MolWt(m), Descriptors.MolLogP(m)
hbd, hba = Lipinski.NumHDonors(m), Lipinski.NumHAcceptors(m)
ok = 250 <= mw <= 350 and 1 <= logp <= 3 and hbd <= 3 and hba <= 7
return {"smiles": Chem.MolToSmiles(m), "mw": round(mw, 1), "logp": round(logp, 2),
"hbd": hbd, "hba": hba} if ok else None
rows = [r for r in (lead_like(s) for s in smiles_list) if r]
df = pd.DataFrame(rows).drop_duplicates(subset=["smiles"])
print(f"Lead-like compounds after RDKit filter: {len(df)}")
print(df.head())
类似物/相似性搜索(功能有限——使用前请阅读)
提交端点接受 dist(拓扑图编辑距离,0–3)和 adist(“anon”距离,即骨架跳数),用于近邻类似物搜索:
# Near-neighbor analogs. Keep dist small (1) and the timeout generous; expect FEW results.
try:
analogs = smiles_lookup("c1ccc(NC(=O)c2ccccc2)cc1", dist=1, adist=0, timeout=240)
print(f"Analogs: {len(analogs)}")
for h in analogs[:10]:
print(" ", h["zinc_id"], h["smiles"])
except TimeoutError as e:
print("Analog search timed out:", e)
根据对在线公共服务的验证,注意事项如下:
dist=0(精确搜索)速度快且可靠。dist >= 1会在共享集群上跨数十亿个分子运行,经常超时(例如,dist=3在约 270 秒内未返回结果);即使搜索成功,通常也只会返回查询对象和少量近乎相同的结构。- 对于广泛的整分子相似性搜索(基于图编辑距离计算 Tanimoto),CartBlanche 在网站上提供单独的 Smallworld 搜索(
cartblanche22.docking.org→ Similarity → Smallworld,/similarity/sw)。这是交互式流程,此处未提供简单的 GET/POST JSON 端点——大型相似性搜索请使用网页界面。 - 不要构建假设类似物搜索会返回丰富结果集的自动化流水线。应基于精确查询和本地 RDKit/化学信息学扩展来设计工作流。
关键概念
ZINC22 ID
ZINC22 引入了 ZINCbq0000001gLA 这类短字母数字 ID(前缀字母表示子数据库/分块)。ZINC000000029632 这类旧版 12 位 ID(来自 ZINC15/ZINC20)仍可通过 /substance/{id}.json 解析。请始终按原样传入 ID。
异步搜索模型
SMILES 搜索会作为任务排队:
POST /smiles.json(multipart 表单:smiles、dist、adist、database)→{"task": "<uuid>"}GET /search/result/{task}→ 运行期间返回{"status": "PROGRESS"},完成后返回{"status": "SUCCESS", "result": {...}}- 成功时,如果有命中结果,
result是一个以子数据库(zinc22,有时也包括zinc20)为键的字典;如果没有结果,则为空列表。上面的辅助函数会规范化这两种形式。
分块
ZINC 根据重原子数/MW(H 代码,例如 H11)和 logP(字母分箱),将分子组织成二维的“分块”网格。CartBlanche 的 Tranches 浏览器(cartblanche22.docking.org 上的“3D”/“2D”分块页面)支持选择属性区域,并批量下载用于对接的 SMILES/SDF。分块批量下载是交互式的(且数据量较大);如需以编程方式筛选属性,请优先采用查询 4 中的 RDKit 方法,或从 files.docking.org 获取分块文件并在本地筛选。
结果字段
每条搜索/物质记录包含:zinc_id、smiles、mol_formula、rings、hetero_atoms、db、catalogs[] 和 tranche_details(mwt、logp、heavy_atoms、inchi、inchikey)。SMILES 搜索结果行还包含 sub_id、matched_smiles 和 tranche 代码。
常见工作流
工作流 1:检查命中列表的可购买性
目标:给定对接命中的 SMILES,解析其 ZINC22 ID,并报告哪些可以购买。
import pandas as pd
hit_smiles = [
"CC(=O)Nc1ccc(O)cc1", # acetaminophen
"c1ccc(NC(=O)c2ccccc2)cc1", # benzanilide
]
rows = []
for smi in hit_smiles:
matches = smiles_lookup(smi) # exact
if not matches:
rows.append({"query_smiles": smi, "zinc_id": None, "purchasable": False})
continue
for m in matches:
cats = m.get("catalogs", [])
rows.append({
"query_smiles": smi,
"zinc_id": m["zinc_id"],
"mw": m["tranche_details"]["mwt"],
"n_catalogs": len(cats),
"purchasable": any(c.get("purchase") for c in cats) or bool(cats),
})
df = pd.DataFrame(rows)
print(df)
df.to_csv("hit_purchasability.csv", index=False)
print("Saved: hit_purchasability.csv")
工作流 2:解析并筛选用于对接的候选集合
目标:获取一组候选 SMILES(自行枚举的结果或分块文件),保留 ZINC22 中存在/可购买的分子,筛选出符合先导化合物特征的范围,并导出用于对接的 SMILES。
import pandas as pd
from rdkit import Chem
from rdkit.Chem import Descriptors
candidates = [
"CC(=O)Nc1ccc(O)cc1",
"O=C([C@H]1CCCN1C(=O)Cc1c[nH]c2ccccc12)N1CCc2ccccc2C1",
"c1ccc(NC(=O)c2ccccc2)cc1",
]
rows = []
for smi in candidates:
m = Chem.MolFromSmiles(smi)
if m is None:
continue
mw, logp = Descriptors.MolWt(m), Descriptors.MolLogP(m)
if not (200 <= mw <= 500 and -1 <= logp <= 5): # lead-like local filter
continue
matches = smiles_lookup(smi) # confirm it exists in ZINC22
if not matches:
continue
m0 = matches[0]
rows.append({"zinc_id": m0["zinc_id"], "smiles": Chem.MolToSmiles(m),
"mw": round(mw, 1), "logp": round(logp, 2),
"purchasable": bool(m0.get("catalogs"))})
df = pd.DataFrame(rows).drop_duplicates(subset=["smiles"])
print(f"ZINC22-confirmed lead-like compounds: {len(df)}")
df[["smiles", "zinc_id"]].to_csv("docking_library.smi", sep=" ", index=False, header=False)
print("Saved: docking_library.smi")
print(df)
关键参数
| 参数 | 端点 | 默认值 | 范围/选项 | 作用 |
|---|---|---|---|---|
| (路径) | GET /substance/{zinc_id}.json | — | ZINC ID | 同步查询物质 |
smiles | POST /smiles.json | — | 有效的 SMILES | 查询分子 |
dist | POST /smiles.json | 0 | 0–3 | 拓扑距离;0 = 精确匹配(可靠),>=1 = 类似物搜索(速度慢,可能超时) |
adist | POST /smiles.json | 0 | 0–3 | 匿名图距离(骨架跳跃);0 = 关闭 |
database | POST /smiles.json | zinc22 | "zinc22"、"zinc20" | 要搜索的 ZINC 版本 |
task | GET /search/result/{task} | — | 提交时返回的 UUID | 用于轮询异步结果的句柄 |
最佳实践
-
使用 ZINC22(CartBlanche),不要使用 ZINC15。
zinc15.docking.orgREST API 有 CAPTCHA 验证,无法通过脚本使用。此处所有端点均指向cartblanche22.docking.org。 -
优先精确查询;不要依赖类似物搜索。
dist=0是可靠的基础操作。对于类似物/相似度搜索,可在本地扩展搜索范围(RDKit、rdkit-cheminformatics),或使用 Smallworld 网页界面——类似物 API 在范围较广的查询中会超时。 -
使用 RDKit 在本地筛选属性。 ZINC22 不支持在服务器端按 MW/logP/HBD 查询。先检索候选项,再用 RDKit 筛选——也可在此步骤应用 PAINS/Brenk 警示。
-
礼貌地轮询。 SMILES 查询是异步的;每隔几秒轮询一次
/search/result/{task},设置合理的超时时间,并处理PROGRESS→SUCCESS状态转换(以及TimeoutError)。 -
按规范 SMILES 去重。 同一结构可能对应多个 ZINC ID/目录。对结构进行对接前,使用 RDKit 规范化(
Chem.MolToSmiles(Chem.MolFromSmiles(smi)))。 -
通过
catalogs验证是否可购买。 将purchase: 1的catalogs条目视为可订购;记录supplier_code和catalog_name以便采购。 -
缓存结果。 ZINC 数据会定期更新;使用带日期的文件名缓存物质/搜索 JSON,并避免在同一项目中重复查询。
常见用法
配方:通过 SMILES 查询 ZINC22 ID 和是否可购买
matches = smiles_lookup("CC(=O)Nc1ccc(O)cc1") # acetaminophen
for m in matches:
cats = m.get("catalogs", [])
print(f"{m['zinc_id']} | MW {m['tranche_details']['mwt']:.1f} | "
f"catalogs {len(cats)} | purchasable {bool(cats)}")
配方:批量解析 ZINC ID 列表
zinc_ids = ["ZINC000000029632", "ZINCbq0000001gLA"]
for zid in zinc_ids:
try:
c = get_substance(zid)
print(f"{zid}: {c['smiles']} MW {c['tranche_details']['mwt']:.1f}")
except requests.HTTPError as e:
print(f"{zid}: not found ({e.response.status_code})")
配方:化合物库的属性分布
import pandas as pd
from rdkit import Chem
from rdkit.Chem import Descriptors
df = pd.read_csv("docking_library.smi", sep=" ", names=["smiles", "zinc_id"])
df["mw"] = df["smiles"].map(lambda s: Descriptors.MolWt(Chem.MolFromSmiles(s)))
df["logp"] = df["smiles"].map(lambda s: Descriptors.MolLogP(Chem.MolFromSmiles(s)))
print(f"Library size: {len(df)}")
print(df[["mw", "logp"]].describe())
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
请求 zinc15.docking.org 会重定向到 /captcha/ 或返回 403 | 旧版 ZINC15 REST API 受 CAPTCHA 限制,无法通过脚本使用 | 按此处所示,将所有调用切换到 cartblanche22.docking.org(ZINC22 / CartBlanche) |
GET /substances/{id}.json(复数)返回 HTML | 路径错误——该路径会匹配 SPA 的兜底路由 | 使用单数 /substance/{id}.json |
端点返回 200,且 content-type: application/json,但响应正文是 HTML | CartBlanche 会为未知路由提供 SPA 外壳(其 content-type 具有误导性) | 检查响应正文能否实际解析为 JSON;只有文档中列出的端点会返回数据 |
POST /smiles.json 返回 {"task": ...},但你预期会得到结果 | SMILES 搜索是异步的 | 轮询 GET /search/result/{task},直到 status == "SUCCESS" |
400 No Valid SMILES, please try again | SMILES 未通过验证(或请求格式错误) | 使用 RDKit 验证 SMILES;通过 multipart 表单字段 smiles/dist/adist/database 提交 |
成功的任务出现 KeyError: 'data' / 'zinc22' | 有匹配项时,result 是字典;没有匹配项时则是空列表 | 处理这两种数据结构(参见 smiles_lookup 辅助函数) |
类似物搜索(dist >= 1)超时或只返回完全匹配项 | 共享集群上的搜索空间非常大 | 使用 dist=0(精确匹配);如需相似度搜索,请使用 Smallworld 网页界面;使用 RDKit 在本地扩展类似物 |
/substance/random.json 返回 400;/substance/random/{n}.json 返回 {"status":"PENDING"} | 随机端点是异步的,且并非简单请求 | 脚本无需使用此端点;改用已知的 ZINC ID |
化合物 ID 返回 HTTP 404 | ID 拼写错误,或此版本中不存在该 ID | 验证 ZINC ID;对于旧版 ID,尝试使用 database="zinc20" |
相关技能
rdkit-cheminformatics— 计算检索到的 ZINC 化合物的属性,并应用 PAINS/Brenk 过滤器(此处进行属性过滤时必需)autodock-vina-docking— 使用导出的 ZINC SMILES/SDF 文件进行分子对接筛选chembl-database-bioactivity— 获取 ZINC 虚拟筛选中发现的化合物的生物活性数据drugbank-database-access— 已获批药物的结构和注释
参考资料
- CartBlanche / ZINC22 网页应用 — 物质查询、SMILES 搜索、tranche 浏览器
- ZINC22 论文 — Tingle 等人,《ZINC-22,一种用于配体发现的免费数十亿级可购化合物数据库》,J. Chem. Inf. Model. 2023
- docking.org wiki — CartBlanche 使用说明和 ZINC22 下载文档
- files.docking.org — ZINC22 批量 tranche 文件下载