pubchem-compound-search について
PubChem Compound Search は、公開 PUG-REST JSON API を通じて PubChem を検索する Python 向けスキルです。化学・生物医学のワークフローを対象とし、API キーなしで requests から識別子、物性、構造、同義語、アッセイ概要を取得します。
主な機能
- 01名称と化学識別子の解決
- 02化合物物性の一括取得
- 03類似性・部分構造検索
- 04同義語と説明の取得
- 05アッセイ概要の取得と構造のダウンロード
活用シーン
- 01化合物パネルの物性比較
- 02リード化合物の類似体検索
- 03特定の構造モチーフのスクリーニング
- 04後続解析用の構造ダウンロード
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jaechang-hits/ sciagent-skills --skill pubchem-compound-searchインストール後、Claude Code で/pubchem-compound-searchを入力して読み込まれたか確認します。
詳細
PubChem 化合物検索
概要
PubChem(NCBI)は、無料で利用できる最大の化学データベースです。化合物は 1 億 1,000 万件以上、物質は 2 億 8,000 万件以上、バイオアッセイ記録は数百万件あります。PUG-REST JSON API が標準的なプログラムインターフェースで、ここに示す例はすべて、通常の requests を使って直接呼び出します。Python の pubchempy ラッパーは必須ではありません。PUG-REST の URL 文法は十分に簡潔なため、直接呼び出すほうが動作がわかりやすく、リトライやキャッシュも容易で、サンドボックスの依存関係に関する問題も避けられます(このライブラリは TOOL_STATUS.md に含まれていません)。
URL の形式は固定されており、予測しやすいものです。
https://pubchem.ncbi.nlm.nih.gov/rest/pug/<input>/<operation>/<output>
<input>=compound/{name,cid,smiles,inchikey,formula}/<value><operation>=cids、property/<list>、synonyms、description、assaysummary、JSON(完全なレコード)、SDF、PNG<output>=JSON、CSV、TXT、SDF、PNG
類似性検索、部分構造検索、分子式検索などの時間がかかる操作では、API は HTTP 202 と {"Waiting": {"ListKey": "..."}} を返します。IdentifierList が返されるまで compound/listkey/{key}/cids/JSON をポーリングしてください。このスキルでは、モジュール 4 でこのパターンを処理します。
使用する場面
- 化合物名、SMILES、InChIKey、分子式で化合物を検索し、PubChem CID を取得する
- 1 回のリクエストで、1 つまたは複数の CID の分子特性(分子量、XLogP、TPSA、水素結合ドナー/アクセプター数、回転可能結合数、分子式、IUPAC 名)を取得する
- Tanimoto 類似度を使って構造が類似した化合物を検索する(非同期の ListKey ポーリング)
- 特定の部分構造/ファーマコフォアモチーフを含む化合物を検索する
- ある CID の同義語/商品名/CAS 番号をすべて取得する
- 化合物のアッセイサマリーテーブル(スクリーニング結果の活性/不活性)を取得する
- 1 回の API 呼び出しで識別子の形式を相互変換する(名前 ↔ CID ↔ SMILES ↔ InChI ↔ InChIKey)
- 図や後続の RDKit 処理用に、2D SDF または PNG の構造データをダウンロードする
- ローカルのケモインフォマティクス処理(フィンガープリント、記述子、3D コンフォーマー、骨格抽出)には
rdkitを使用する - より詳細な生物活性/標的結合データ(特定の標的に対する IC50、Ki)には
chembl-database-bioactivityを使用する
前提条件
- Python パッケージ:
requests、pandas— どちらも標準的な環境にすでに含まれています - API キーは不要 — PubChem は完全に公開されています
- レート制限:IP あたり毎秒最大 5 リクエスト、毎分 400 リクエストです。ループ内では
time.sleep(0.25)で間隔を空けてください。戻りコード 503 は、制限に達したことを示します。
requests と pandas がすでに利用できる pixi/conda 環境内にいる場合は、インストールを省略し、pixi run python ... でスクリプトを実行してください。
pip install requests pandas
クイックスタート
import requests
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
# name → CID
cid = requests.get(f"{BASE}/compound/name/aspirin/cids/JSON").json()["IdentifierList"]["CID"][0]
# CID → properties (single call, many fields)
r = requests.get(
f"{BASE}/compound/cid/{cid}/property/"
"MolecularWeight,XLogP,TPSA,HBondDonorCount,HBondAcceptorCount,SMILES,IUPACName/JSON")
p = r.json()["PropertyTable"]["Properties"][0]
print(f"CID {cid} — {p['IUPACName']}")
print(f" MW={p['MolecularWeight']} XLogP={p['XLogP']} TPSA={p['TPSA']}")
print(f" HBD={p['HBondDonorCount']} HBA={p['HBondAcceptorCount']}")
print(f" SMILES={p['SMILES']}")
コア API
モジュール 1:識別子の検索
/compound/{namespace}/{value}/cids/JSON を使って、外部識別子を PubChem CID に解決します。名前空間は name、cid、smiles、inchikey、inchi、formula です。
import requests
from urllib.parse import quote
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
# By name (returns all matching CIDs as a list)
cids = requests.get(f"{BASE}/compound/name/caffeine/cids/JSON").json()["IdentifierList"]["CID"]
print(f"caffeine CIDs: {cids}")
# By canonical SMILES (URL-encode!)
smi = quote("CC(=O)OC1=CC=CC=C1C(=O)O", safe="")
cid = requests.get(f"{BASE}/compound/smiles/{smi}/cids/JSON").json()["IdentifierList"]["CID"][0]
print(f"aspirin SMILES → CID {cid}")
# By InChIKey (exact match, fastest if you already have one)
ikey = "BSYNRYMUTXBXSQ-UHFFFAOYSA-N"
cid = requests.get(f"{BASE}/compound/inchikey/{ikey}/cids/JSON").json()["IdentifierList"]["CID"][0]
print(f"InChIKey → CID {cid}")
モジュール 2:特性の取得
/compound/cid/{cid_or_csv}/property/<csv-list>/JSON は、指定したすべての特性を 1 回のリクエストで返します。CID と特性名はどちらも CSV 形式で連結できます。一度に最大約 200 個の CID と複数の特性をまとめて取得できます。
import requests
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
# Full property set for a single compound (ibuprofen CID 3672)
url = (f"{BASE}/compound/cid/3672/property/"
"MolecularWeight,XLogP,TPSA,HBondDonorCount,HBondAcceptorCount,"
"RotatableBondCount,SMILES,InChIKey,IUPACName,MolecularFormula/JSON")
p = requests.get(url).json()["PropertyTable"]["Properties"][0]
print(f"{p['IUPACName']} formula={p['MolecularFormula']}")
print(f" MW={p['MolecularWeight']} XLogP={p['XLogP']} TPSA={p['TPSA']}")
print(f" HBD={p['HBondDonorCount']} HBA={p['HBondAcceptorCount']} RotB={p['RotatableBondCount']}")
import requests, pandas as pd
# Batch: 4 CIDs, 3 properties — one request, one round trip
cids = "2244,3672,2157,2662" # aspirin, ibuprofen, naproxen, celecoxib
r = requests.get(
f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{cids}/property/"
"MolecularWeight,XLogP,TPSA/JSON")
df = pd.DataFrame(r.json()["PropertyTable"]["Properties"])
print(df.to_string(index=False))
モジュール 3:同義語と説明
同義語(商品名、CAS 番号、別表記)と編集済みの説明は、/compound/{ns}/{value}/{synonyms|description}/JSON にあります。
import requests
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
# Full synonym list (aspirin has ~700)
info = requests.get(f"{BASE}/compound/cid/2244/synonyms/JSON").json()["InformationList"]["Information"][0]
print(f"aspirin synonyms: {len(info['Synonym'])}")
for s in info["Synonym"][:8]:
print(f" {s}")
import requests
# Curated descriptions (NCBI MeSH, CAMEO, etc.)
r = requests.get("https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/aspirin/description/JSON")
for item in r.json()["InformationList"]["Information"]:
if "Description" in item:
print(f"[{item.get('DescriptionSourceName','?')}]")
print(f" {item['Description'][:200]}…")
print()
モジュール 4:類似性・部分構造検索(非同期 ListKey パターン)
構造検索では HTTP 202 と {"Waiting": {"ListKey": "..."}} が返されます。IdentifierList が返されるまで、約 2 秒ごとに /compound/listkey/{key}/cids/JSON をポーリングしてください。頻繁に使うため、ヘルパーでこの処理をまとめます。
import requests, time
from urllib.parse import quote
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
def poll_listkey(listkey, max_polls=10, interval=2.0):
"""Block until PubChem finishes async search; return CID list."""
for _ in range(max_polls):
time.sleep(interval)
j = requests.get(f"{BASE}/compound/listkey/{listkey}/cids/JSON", timeout=20).json()
if "IdentifierList" in j:
return j["IdentifierList"]["CID"]
raise TimeoutError(f"ListKey {listkey} did not complete")
# Tanimoto similarity (90% threshold, max 20 hits) — starting from aspirin SMILES
smi = quote("CC(=O)OC1=CC=CC=C1C(=O)O", safe="")
init = requests.get(
f"{BASE}/compound/similarity/smiles/{smi}/JSON?Threshold=90&MaxRecords=20").json()
cids = poll_listkey(init["Waiting"]["ListKey"]) if "Waiting" in init \
else init["IdentifierList"]["CID"]
print(f"aspirin @90% similarity: {len(cids)} hits, sample={cids[:5]}")
import requests
from urllib.parse import quote
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
# Substructure search — all compounds containing a sulfonamide group
smi = quote("S(=O)(=O)N", safe="")
init = requests.get(
f"{BASE}/compound/substructure/smiles/{smi}/JSON?MaxRecords=20").json()
cids = poll_listkey(init["Waiting"]["ListKey"]) if "Waiting" in init \
else init["IdentifierList"]["CID"]
print(f"sulfonamide-containing CIDs: {len(cids)}, sample={cids[:5]}")
モジュール 5:アッセイサマリー(生物活性データ)
/compound/cid/{cid}/assaysummary/JSON は、その化合物が登場する PubChem BioAssay をすべて含む Table を返します(アッセイ AID、標的、結果、利用可能な場合はマイクロモル単位の活性)。
import requests, pandas as pd
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
r = requests.get(f"{BASE}/compound/cid/2244/assaysummary/JSON", timeout=30)
rows = r.json().get("Table", {}).get("Row", [])
cols = r.json().get("Table", {}).get("Columns", {}).get("Column", [])
print(f"aspirin appears in {len(rows)} bioassays")
# First few columns + rows as a DataFrame
df = pd.DataFrame([row["Cell"] for row in rows[:5]], columns=cols)
print(df.iloc[:, :6].to_string(index=False))
モジュール 6:構造ファイルのダウンロード(SDF/PNG)
/compound/cid/{cid}/SDF は 2D MOL/SDF を返し、/compound/cid/{cid}/PNG は構造画像を返します(高解像度にするには ?image_size=large を使用します)。
import requests
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
cid = 2519 # caffeine
# 2D SDF for downstream RDKit / OpenBabel
sdf = requests.get(f"{BASE}/compound/cid/{cid}/SDF", timeout=15).text
with open("caffeine.sdf", "w") as f:
f.write(sdf)
print(f"caffeine.sdf: {len(sdf)} chars, ends with M END={'M END' in sdf}")
# PNG structure image
png = requests.get(f"{BASE}/compound/cid/{cid}/PNG?image_size=large", timeout=15).content
with open("caffeine.png", "wb") as f:
f.write(png)
print(f"caffeine.png: {len(png)} bytes")
主な概念
非同期 ListKey パターン
類似性、部分構造、分子式の検索は非同期です。API はバックグラウンドジョブを開始し、{"Waiting": {"ListKey": "<id>"}} を伴う HTTP 202 を返します。IdentifierList を含むレスポンスが返るまで、約 2 秒ごとに /compound/listkey/{id}/cids/JSON をポーリングしてください。ほとんどの検索は 5~15 秒で完了します。小規模な検索ではポーリング間隔を短くし、非常に大規模な検索では長くしてください。すべての箇所で、Module 4 の poll_listkey ヘルパーを使用してください。
高速な検索のごく一部では、最初の呼び出しで Waiting フィールドなしに IdentifierList が直接返されます。どちらの場合にも対応できるよう確認してください。
プロパティ名のリファレンス
| API 名 | 意味 |
|---|---|
MolecularWeight | 分子量(g/mol、文字列) |
MolecularFormula | ヒル方式の分子式 |
SMILES | 立体化学情報を含む異性体 SMILES(2025 年以降の名称。以前は IsomericSMILES) |
ConnectivitySMILES | 立体化学情報を含まない、結合情報のみの SMILES(2025 年以降の名称。以前は CanonicalSMILES) |
IUPACName | 精選された IUPAC 名 |
InChI / InChIKey | IUPAC InChI / InChIKey |
XLogP | 計算値の logP(オクタノール/水) |
TPSA | トポロジカル極性表面積(Ų) |
HBondDonorCount | 水素結合ドナーの数 |
HBondAcceptorCount | 水素結合アクセプターの数 |
RotatableBondCount | 回転可能結合の数 |
HeavyAtomCount | 水素以外の原子数 |
Charge | 形式電荷 |
任意の項目を選んで CSV 形式で結合し、単一の /property/<csv>/JSON URL で取得できます。MolecularWeight は文字列として返されるため、計算の前に float に変換してください。
レスポンスのエンベロープ形式
cids/JSON→{"IdentifierList": {"CID": [int, ...]}}property/.../JSON→{"PropertyTable": {"Properties": [{...}, ...]}}(入力順に、CID ごとに 1 つの辞書)synonyms/JSON→{"InformationList": {"Information": [{"CID": int, "Synonym": [str, ...]}]}}description/JSON→{"InformationList": {"Information": [{"CID": int, "Description": str, "DescriptionSourceName": str, ...}, ...]}}assaysummary/JSON→{"Table": {"Columns": {"Column": [...]}, "Row": [{"Cell": [...]}, ...]}}- 非同期開始(類似性、部分構造、分子式) →
{"Waiting": {"ListKey": "..."}}を伴う 202 - ListKey のポーリング → 完了時に
{"IdentifierList": {"CID": [...]}}
一般的なワークフロー
ワークフロー 1:医薬品パネル内でのプロパティ比較
目標:少数の小分子セットの物理化学的性質を並べて比較します。
import requests, pandas as pd, time
from urllib.parse import quote
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
drugs = ["aspirin", "ibuprofen", "naproxen", "celecoxib"]
# Resolve names → CIDs in a loop (rate-limited)
cids = []
for d in drugs:
cid = requests.get(f"{BASE}/compound/name/{quote(d)}/cids/JSON",
timeout=15).json()["IdentifierList"]["CID"][0]
cids.append(cid)
time.sleep(0.25)
# Single batched property pull
cid_csv = ",".join(str(c) for c in cids)
r = requests.get(
f"{BASE}/compound/cid/{cid_csv}/property/"
"MolecularWeight,XLogP,TPSA,HBondDonorCount,HBondAcceptorCount/JSON")
df = pd.DataFrame(r.json()["PropertyTable"]["Properties"])
df["Name"] = drugs
df = df[["Name", "CID", "MolecularWeight", "XLogP", "TPSA",
"HBondDonorCount", "HBondAcceptorCount"]]
print(df.to_string(index=False))
ワークフロー 2:リード化合物 → 類似アナログ → プロパティ
目標:キナーゼ阻害剤(ゲフィチニブ)を起点に、85%類似するアナログを見つけ、そのプロパティを取得します。
import requests, time
from urllib.parse import quote
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
def poll_listkey(listkey, max_polls=10, interval=2.0):
for _ in range(max_polls):
time.sleep(interval)
j = requests.get(f"{BASE}/compound/listkey/{listkey}/cids/JSON",
timeout=20).json()
if "IdentifierList" in j:
return j["IdentifierList"]["CID"]
raise TimeoutError("listkey timeout")
# 1. lead → CID → canonical SMILES
ref_cid = requests.get(
f"{BASE}/compound/name/gefitinib/cids/JSON").json()["IdentifierList"]["CID"][0]
ref_smi = requests.get(
f"{BASE}/compound/cid/{ref_cid}/property/SMILES/JSON"
).json()["PropertyTable"]["Properties"][0]["SMILES"]
print(f"gefitinib CID={ref_cid} SMILES={ref_smi}")
# 2. similarity search
smi_q = quote(ref_smi, safe="")
init = requests.get(
f"{BASE}/compound/similarity/smiles/{smi_q}/JSON?Threshold=85&MaxRecords=15"
).json()
sim_cids = poll_listkey(init["Waiting"]["ListKey"]) if "Waiting" in init \
else init["IdentifierList"]["CID"]
print(f" {len(sim_cids)} analogs @85% Tanimoto")
# 3. batch-pull properties for top 5 analogs
cid_csv = ",".join(str(c) for c in sim_cids[:5])
r = requests.get(
f"{BASE}/compound/cid/{cid_csv}/property/"
"MolecularWeight,XLogP,TPSA,RotatableBondCount/JSON")
for row in r.json()["PropertyTable"]["Properties"]:
print(f" CID {row['CID']}: MW={row['MolecularWeight']} XLogP={row['XLogP']}")
ワークフロー 3:部分構造によるファーマコフォアスクリーニング → 生物活性の確認
目標:スルホンアミドモチーフを持つ化合物を見つけ、生物活性の記録があるものを確認します。
import requests, time
from urllib.parse import quote
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
def poll_listkey(listkey, max_polls=10, interval=2.0):
for _ in range(max_polls):
time.sleep(interval)
j = requests.get(f"{BASE}/compound/listkey/{listkey}/cids/JSON",
timeout=20).json()
if "IdentifierList" in j:
return j["IdentifierList"]["CID"]
raise TimeoutError("listkey timeout")
smi = quote("S(=O)(=O)N", safe="")
init = requests.get(
f"{BASE}/compound/substructure/smiles/{smi}/JSON?MaxRecords=10").json()
cids = poll_listkey(init["Waiting"]["ListKey"]) if "Waiting" in init \
else init["IdentifierList"]["CID"]
print(f"sulfonamide CIDs: {cids}")
# Bioactivity row counts for the first few hits
for cid in cids[:3]:
rows = requests.get(f"{BASE}/compound/cid/{cid}/assaysummary/JSON",
timeout=30).json().get("Table", {}).get("Row", [])
print(f" CID {cid}: {len(rows)} assay rows")
time.sleep(0.3)
主要なパラメーター
| パラメーター | エンドポイント/モジュール | デフォルト | 範囲/オプション | 効果 |
|---|---|---|---|---|
<namespace> | /compound/{ns}/<value>/... | — | name、cid、smiles、inchikey、inchi、formula | 入力識別子の種類 |
<property csv> | /.../property/<csv>/JSON | — | 任意のプロパティ名(表を参照)の組み合わせ | 返すプロパティ(リクエストごとに DB 呼び出し 1 回) |
Threshold | similarity(M4) | 90 | 0~100 | Tanimoto カットオフ(パーセント) |
MaxRecords | similarity/substructure/formula | (サーバー側のデフォルト) | 1~10000 | 非同期結果リストの上限 |
image_size | /compound/cid/{cid}/PNG | medium | small、large、WxH(例:500x500) | PNG 出力の解像度 |
record_type | /compound/cid/{cid}/SDF | 2d | 2d、3d | SDF の次元(?record_type=3d) |
MaxAssayResults | /compound/cid/{cid}/assaysummary/JSON | — | 整数 | 化合物に数千件のレコードがある場合のアッセイ行数の上限 |
ベストプラクティス
-
名前または外部識別子から開始する場合は、必ず最初に
cids/JSONを使います。 名前から CID への解決と、CID からプロパティへの検索は別々の呼び出しです。name → propertyで一度に行うこともできますが、後続のクエリで必要となる正規の CID リストが失われます。 -
プロパティはまとめて取得し、個別にループしないでください。
compound/cid/2244,3672,2157,.../property/MolecularWeight,XLogP,.../JSONは、最大約 200 個の CID と任意の数のプロパティを受け付けるため、N 回ではなく 1 回の往復で済みます。名前ごとにget_compoundsをループして呼び出すのは、レート制限にかかる最もよくある原因です。 -
すべての SMILES を URL エンコードしてください。
urllib.parse.quote(smiles, safe="")を使用します。=,#,(,),[,]を含むエンコード前の SMILES は動作することもありますが、+、/、\、またはクエリ文字列のような部分文字列が含まれると、予測不能な形で失敗します。 -
類似性/部分構造/分子式の検索は非同期として扱ってください。
"Waiting" in responseを条件に分岐し、検索を再実行するのではなくlistkeyをポーリングします。検索を再実行すると新しい ListKey が作成され、サーバーのジョブ枠を無駄にします。 -
リクエストは毎秒 5 件以下、毎分 400 件以下に制限してください。 連続ループには
time.sleep(0.25)を挿入します。HTTP 503 は制限に達したことを意味します。10 秒待って、同時実行数を減らしてください。 -
MolecularWeightはfloatにキャストしてください。 小数の精度を完全に保つため、文字列("180.16")として返されます。文字列を数値のしきい値と比較すると、気付かないままバグになります。 -
CID が 100 件以上の場合は POST を使用してください。 約 2000 文字を超える GET URL は、一部の HTTP プロキシによって切り詰められます。PubChem は、フォーム本文に
cidを含めたPOSTも受け付けます:requests.post(f"{BASE}/compound/cid/property/MolecularWeight/JSON", data={"cid": cid_csv})。 -
2025 年の SMILES プロパティ名の変更。 PubChem は、2025 年の PUG-REST スキーマで 2 つの SMILES プロパティの名前を変更しました。旧
IsomericSMILES(立体情報を含む)はSMILESに、旧CanonicalSMILES(立体情報を含まない結合情報のみ)はConnectivitySMILESになりました。URL パスでは引き続き旧名を入力として受け付けます(例:/property/CanonicalSMILES/JSONは 200 を返します)が、レスポンス JSON のキーには新しい名前が使われます。そのため、リクエストは成功しても、パース処理だけがKeyErrorで失敗します。新しいコードではSMILES/ConnectivitySMILESを使用し、それらのキーを読み取ってください。
よく使うレシピ
レシピ 1 — リピンスキーの Rule of Five チェック
import requests
from urllib.parse import quote
BASE = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
def check_lipinski(name):
cid = requests.get(f"{BASE}/compound/name/{quote(name)}/cids/JSON"
).json()["IdentifierList"]["CID"][0]
p = requests.get(
f"{BASE}/compound/cid/{cid}/property/"
"MolecularWeight,XLogP,HBondDonorCount,HBondAcceptorCount/JSON"
).json()["PropertyTable"]["Properties"][0]
mw, xlogp = float(p["MolecularWeight"]), p.get("XLogP", 0) or 0
hbd, hba = p["HBondDonorCount"], p["HBondAcceptorCount"]
rules = {"MW ≤ 500": mw <= 500, "XLogP ≤ 5": xlogp <= 5,
"HBD ≤ 5": hbd <= 5, "HBA ≤ 10": hba <= 10}
v = sum(1 for ok in rules.values() if not ok)
return rules, v
rules, v = check_lipinski("metformin")
print(f"violations: {v}/4 ({'PASS' if v <= 1 else 'FAIL'})")
for r, ok in rules.items(): print(f" {'✓' if ok else '✗'} {r}")
レシピ 2 — すべての同義語/商品名を取得
import requests
r = requests.get("https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/aspirin/synonyms/JSON")
syns = r.json()["InformationList"]["Information"][0]["Synonym"]
print(f"{len(syns)} synonyms")
for s in syns[:10]:
print(f" {s}")
レシピ 3 — 構造 PNG のダウンロード
import requests
cid = 2519 # caffeine
png = requests.get(
f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{cid}/PNG?image_size=large"
).content
with open("caffeine.png", "wb") as f:
f.write(png)
print(f"wrote caffeine.png ({len(png)} bytes)")
レシピ 4 — 再試行/429 ハンドリングを含む堅牢なセッション
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
s = requests.Session()
s.headers.update({"Accept": "application/json"})
s.mount("https://", HTTPAdapter(max_retries=Retry(
total=4, backoff_factor=1.0,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"])))
r = s.get(
"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/2244/property/MolecularWeight/JSON",
timeout=15)
r.raise_for_status()
print(r.json()["PropertyTable"]["Properties"][0]["MolecularWeight"])
期待される出力
- CID 検索(
/.../cids/JSON):{"IdentifierList": {"CID": [...]}}— 整数 CID のリストです。名前検索では関連度順に並びます。 - プロパティ(
/.../property/.../JSON):{"PropertyTable": {"Properties": [{"CID": ..., "MolecularWeight": "180.16", ...}, ...]}}。入力順の CID ごとに 1 行です。MolecularWeightは文字列です。 - 同義語:
{"InformationList": {"Information": [{"CID": 2244, "Synonym": ["aspirin", "ACETYLSALICYLIC ACID", "50-78-2", ...]}]}}。 - 非同期検索の開始:HTTP 202 と
{"Waiting": {"ListKey": "12345..."}}が返されます。IdentifierListが返されるまで/compound/listkey/{key}/cids/JSONをポーリングします。 - アッセイ概要:
{"Table": {"Columns": {"Column": [...col names...]}, "Row": [{"Cell": [...]}, ...]}}。 - SDF:CID ヘッダー行で始まるプレーンテキストです。
M ENDの後に SDF プロパティブロックが続きます。 - PNG:バイナリの
image/pngです。デフォルトサイズでは約 2~4 KB、image_size=largeでは約 10~20 KB です。
トラブルシューティング
| 問題 | 原因 | 解決方法 |
|---|---|---|
HTTP 404 PUGREST.NotFound | 名前/SMILES/分子式に一致するレコードがありません | CAS 番号または InChIKey を試してください。PubChem のウェブ UI でスペルを確認してください。RDKit の正準 SMILES は、入力 SMILES では解決できない場合でも解決できることがよくあります |
類似性/部分構造検索の呼び出しで HTTP 202 が {"Waiting":...} のまま止まる | 非同期ジョブがまだ実行中です | /compound/listkey/{key}/cids/JSON を 2 秒ごとに最大約 30 秒間ポーリングしてください。完了しない場合は MaxRecords を減らしてください |
HTTP 503 PUGREST.ServerBusy | 1 秒あたり 5 リクエストまたは 1 分あたり 400 リクエストのレート制限に達しました | ループに time.sleep(0.25) を挿入してください。レシピ 4 の Retry セッションを使用し、同時実行数を減らしてください |
| SMILES の URL で HTTP 400 | SMILES が URL エンコードされていません | urllib.parse.quote(smi, safe="") でラップしてください。#、+、/、\ はすべてパスの解析を妨げます |
KeyError: 'CanonicalSMILES'/KeyError: 'IsomericSMILES' | 古い名前をリクエストしています。URL は 200 を返しますが、2025 年の JSON では ConnectivitySMILES/SMILES がキーになっています | p["ConnectivitySMILES"](立体情報なしの結合情報)または p["SMILES"](立体情報あり)を読み取ってください。プロパティ CSV を新しい名前に更新してください |
TypeError: '>' not supported between instances of 'str' and 'int' | MolecularWeight が文字列です | 算術比較を行う前に float(p["MolecularWeight"]) を実行してください |
バッチ cid/2244,3672,... が一部の行しか返しません | URL がサーバーの制限を超えています | requests.post(url, data={"cid": "2244,3672,..."}) に切り替えてください。値を除いた同じ URL を使い、本文に CSV を含めます |
assaysummary テーブルが空です | CID にバイオアッセイのレコードがありません | すべての化合物がアッセイされているわけではありません。PubChem のウェブページで確認してください |
有効な CID で XLogP が None です | その化合物のプロパティが計算されていません | 算術演算の前に p.get("XLogP", 0) or 0 で処理してください |
関連スキル
chembl-database-bioactivity— IC50/Ki/Kd の標的結合データ。PubChem のアッセイ概要より詳細ですrdkit-cheminformatics— SMILES/MOL のローカル操作、フィンガープリント、記述子、骨格の抽出pdb-database— PubChem CID で見つかった低分子と共結晶化したタンパク質構造
参考資料
- PubChem PUG-REST ドキュメント — URL の文法を網羅したリファレンス
- 実例付き PUG-REST チュートリアル — API の手順を追った解説
- PubChem プロパティテーブル —
/property/<name>の値とその意味を示す信頼できる一覧 - PubChem メインサイト — スポットチェック用のウェブインターフェイス
- Kim et al.(2023)Nucleic Acids Res — PubChem 2023 年アップデート