mcpField
E
emdb-database

emdb-database

github.com/jaechang-hits数据与数据库38470 次安装6 天前更新
代码仓库

关于 emdb-database

emdb-database 是用于查询欧洲生物信息学研究所 EMDB 电子显微镜密度图档案的技能,适合结构生物学研究与数据整理。它结合关键词检索和单条目 JSON 数据,提供图谱元数据、拟合的 PDB 模型及引文信息。

主要功能

  1. 01按关键词检索 EMDB 条目
  2. 02获取分辨率、方法和样本信息
  3. 03读取密度图文件及地图头信息
  4. 04查询拟合的 PDB 原子模型
  5. 05提取条目引文和出版信息

适用场景

  1. 01查找特定蛋白或复合物的冷冻电镜图谱
  2. 02筛选特定生物或分辨率的结构条目
  3. 03获取图谱文件信息以便下载
  4. 04整理结构研究的元数据与引文

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jaechang-hits/sciagent-skills --skill emdb-database

安装后在 Claude Code 中输入/emdb-database确认已加载。

详细介绍

EMDB 数据库

概览

EBI 的电子显微镜数据银行(EMDB)存档大分子复合物的三维电子显微镜密度图——主要是冷冻电镜(cryo-EM)和冷冻电子断层扫描(cryo-ET)数据(30,000 多个条目:核糖体、膜蛋白、病毒和大型复合物)。数据可通过两个服务访问:

  • EMDB 条目 API(https://www.ebi.ac.uk/emdb/api/entry/{EMD-XXXXX})——每个条目的规范 JSON,包含元数据、密度图头信息、拟合的 PDB 列表和引文。/map、/fitted、/publications 等子端点不存在——这些数据都包含在同一条目响应中。
  • EBI Search WS(https://www.ebi.ac.uk/ebisearch/ws/rest/emdb)——真正的关键词搜索后端(基础端点 https://www.ebi.ac.uk/emdb/api/search/ 会忽略查询,仅返回最近的条目)。

无需身份验证或 API 密钥。

适用场景

  • 按关键词查找冷冻电镜密度图(例如,“刺突蛋白”“70S 核糖体”)
  • 获取 .map.gz 密度文件的下载 URL,以便在 ChimeraX / PyMOL 中使用
  • 查找拟合到某个 EMDB 密度图中的 PDB 原子模型(以及反向查找)
  • 获取条目元数据——分辨率、重构方法、物种、样本
  • 按物种或分辨率阈值筛选并列出冷冻电镜结构
  • 获取 EMDB 条目的主要引文(期刊、DOI、PubMed ID)
  • 如果需要实验测定的原子坐标,请改用 pdb-database
  • 如果需要 AI 预测的结构,请使用 alphafold-database-access;EMDB 仅收录实验电子显微镜密度图

先决条件

  • Python 包:requests、pandas、matplotlib
  • 数据要求:EMDB 条目 ID(EMD-XXXXX)、关键词搜索字符串或用于交叉引用的 PDB ID
  • 环境:需要互联网连接;无需 API 密钥
  • 速率限制:未公布官方限制;在批量循环的请求之间添加 time.sleep(0.2),以礼貌地访问服务
pip install requests pandas matplotlib

快速开始

import requests

EMDB_API   = "https://www.ebi.ac.uk/emdb/api"
EBI_SEARCH = "https://www.ebi.ac.uk/ebisearch/ws/rest/emdb"

# Keyword search via EBI Search WS (NOT /emdb/api/search/, which ignores q=)
r = requests.get(EBI_SEARCH,
                 params={"query": "sars-cov-2 spike", "size": 5, "format": "json",
                         "fields": "id,name,resolution,em_method,organism"},
                 timeout=30)
r.raise_for_status()
res = r.json()
print(f"Total hits: {res['hitCount']}")
for e in res["entries"]:
    f = e["fields"]
    name = (f.get("name") or [""])[0][:60]
    resol = (f.get("resolution") or ["?"])[0]
    print(f"  {e['id']}: {name}  ({resol} Å)")

核心 API

查询 1:关键词搜索(EBI Search WS)

返回按 EMDB ID 编号的分页命中列表,每个条目包含所请求的 fields。

import requests, pandas as pd

EBI_SEARCH = "https://www.ebi.ac.uk/ebisearch/ws/rest/emdb"

def emdb_search(query, size=20, start=0,
                fields="id,name,resolution,em_method,organism"):
    r = requests.get(EBI_SEARCH,
                     params={"query": query, "size": size, "start": start,
                             "format": "json", "fields": fields},
                     timeout=30)
    r.raise_for_status()
    return r.json()

data = emdb_search("ribosome 70S", size=10)
rows = []
for e in data["entries"]:
    f = e["fields"]
    rows.append({
        "emdb_id": e["id"],
        "name": (f.get("name") or [""])[0],
        "resolution_A": float((f.get("resolution") or [0])[0] or 0) or None,
        "em_method": (f.get("em_method") or [""])[0],
        "organism": (f.get("organism") or [""])[0],
    })
df = pd.DataFrame(rows)
print(f"hitCount={data['hitCount']}; first {len(df)} rows:")
print(df.to_string(index=False))
# Paged retrieval — iterate `start` until exhausting hitCount
def emdb_search_all(query, size=100, max_pages=5,
                    fields="id,name,resolution,em_method"):
    out = []
    for page in range(max_pages):
        d = emdb_search(query, size=size, start=page * size, fields=fields)
        if not d["entries"]:
            break
        out.extend(d["entries"])
        if len(out) >= d["hitCount"]:
            break
    return out

hits = emdb_search_all("ferritin", size=50, max_pages=2)
print(f"Pulled {len(hits)} ferritin entries")
查询 2:条目元数据

单个条目端点会在一个文档中返回所有元数据、密度图头信息、拟合的 PDB 列表和引文。请仔细读取字段路径——大多数字段都嵌套在其他字段中。

import requests

EMDB_API = "https://www.ebi.ac.uk/emdb/api"

def emdb_entry(emdb_id):
    r = requests.get(f"{EMDB_API}/entry/{emdb_id}", timeout=30)
    r.raise_for_status()
    return r.json()

e = emdb_entry("EMD-30210")  # nsp12-nsp7-nsp8 + Remdesivir (RdRp)
print(f"Title       : {e['admin']['title']}")
print(f"Status      : {e['admin']['current_status']}")
print(f"Key dates   : {e['admin'].get('key_dates')}")

sd = e["structure_determination_list"]["structure_determination"][0]
ip = sd["image_processing"][0]
res = ip["final_reconstruction"]["resolution"]
print(f"Method      : {sd['method']}")
print(f"Resolution  : {res['valueOf_']} {res['units']}  (type: {res['res_type']})")
查询 3:密度图头信息 / 下载信息

entry["map"] 包含文件名、格式、体素网格、轴顺序、晶胞、等值面阈值和建议显示阈值。

import requests

EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()

m = e["map"]
print(f"Map file     : {m.get('file')}")          # e.g. emd_30210.map.gz
print(f"Format       : {m.get('format')}")        # 'CCP4'
print(f"Dimensions   : {m.get('dimensions')}")    # {'col': ..., 'row': ..., 'sec': ...}
print(f"Axis order   : {m.get('axis_order')}")    # {'fast': 'X', 'medium': 'Y', 'slow': 'Z'}
print(f"Contour list : {m.get('contour_list')}")  # recommended threshold(s)

# Conventional FTP/HTTPS download URL pattern (mirror at EBI):
EMDB_FTP = "https://ftp.ebi.ac.uk/pub/databases/emdb/structures"
emdb_num = "EMD-30210"
print(f"Download URL : {EMDB_FTP}/{emdb_num}/map/{m['file']}")
查询 4:拟合的 PDB 原子模型

EMDB 会交叉引用拟合到密度图中的 PDB 条目。每个项目都包含 PDB ID 和关系标签(例如,FULLOVERLAP)。

import requests

EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()

pdblist = e.get("crossreferences", {}).get("pdb_list", {})
pdb_refs = pdblist.get("pdb_reference", []) if isinstance(pdblist, dict) else []
print(f"Fitted PDB entries: {len(pdb_refs)}")
for ref in pdb_refs:
    in_frame = (ref.get("relationship") or {}).get("in_frame")
    print(f"  {ref['pdb_id'].upper():6s}  relationship={in_frame}")
# 7BV2  relationship=FULLOVERLAP
查询 5:引文和出版物

主要引文位于 entry["crossreferences"]["citation_list"]。其结构因引文类型而异(期刊论文或预印本)。

import requests

EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()

citations = e.get("crossreferences", {}).get("citation_list", {})
primary = citations.get("primary_citation", {})
ct = primary.get("citation_type") or primary

title   = (ct.get("title") or "").strip()
journal = ct.get("journal") or ct.get("book_title")
year    = ct.get("year")
authors = [a.get("name") or a.get("name_str")
           for a in (ct.get("author") or ct.get("author_order") or [])
           if a]
xrefs = ct.get("external_references", []) or ct.get("xref", [])

doi = next((x.get("valueOf_") for x in xrefs if x.get("type") == "DOI"), None)
pmid = next((x.get("valueOf_") for x in xrefs if x.get("type") == "PUBMED"), None)

print(f"Title   : {title[:80]}")
print(f"Journal : {journal} ({year})")
print(f"Authors : {', '.join(a for a in authors[:5] if a)}{'...' if len(authors) > 5 else ''}")
print(f"DOI     : {doi}")
print(f"PMID    : {pmid}")
查询 6:样本和物种
import requests

EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()

sample = e.get("sample", {})
sm_list = (sample.get("supramolecule_list") or {}).get("supramolecule", [])
for sm in sm_list:
    ns = sm.get("natural_source") or sm.get("natural_source_list", {}).get("natural_source") or []
    if isinstance(ns, dict):
        ns = [ns]
    for n in ns:
        org = (n.get("organism") or {}).get("valueOf_")
        print(f"  Supramolecule '{sm.get('name', {}).get('valueOf_', '?')}': {org}")

核心概念

字段路径映射(/api/entry/{id} 文档)
内容路径
标题admin.title
发布日期admin.key_dates
作者admin.authors_list.author[*].name/name_str
电镜方法structure_determination_list.structure_determination[0].method(例如,singleParticle、tomography)
分辨率structure_determination_list.structure_determination[0].image_processing[0].final_reconstruction.resolution.valueOf_(字符串,单位为 Å;转换为浮点数)
密度图文件名map.file
密度图格式/尺寸map.format / map.dimensions
等值面阈值map.contour_list
拟合的 PDBcrossreferences.pdb_list.pdb_reference[*].pdb_id
引文crossreferences.citation_list.primary_citation.citation_type(期刊/年份/标题/作者/外部参考文献)
DOI/PubMed…citation_type.external_references[],其中 type 为 {DOI, PUBMED}
物种sample.supramolecule_list.supramolecule[*].natural_source[*].organism.valueOf_
搜索与条目
  • 关键词搜索 → EBI Search WS (/ebisearch/ws/rest/emdb)。使用此接口选择 query=、size=、start= 和 fields=。
  • /emdb/api/search/ 不可靠——它会悄悄忽略 q=,并返回最新发布的条目。不要依赖它。
  • 条目详情 → /emdb/api/entry/{EMD-XXXXX}。没有 /map、/fitted 或 /publications 子端点——这些内容都在条目文档中。

常见工作流

工作流 1:筛选主题相关的分辨率

目标:查找高分辨率的 SARS-CoV-2 刺突蛋白条目,并报告其拟合的 PDB 模型。

import requests, time, pandas as pd

EBI_SEARCH = "https://www.ebi.ac.uk/ebisearch/ws/rest/emdb"
EMDB_API   = "https://www.ebi.ac.uk/emdb/api"

# Step 1: keyword search via EBI Search
hits = []
for start in (0, 50):
    r = requests.get(EBI_SEARCH,
        params={"query": "sars-cov-2 spike", "size": 50, "start": start,
                "format": "json", "fields": "id,name,resolution"},
        timeout=30)
    r.raise_for_status()
    hits.extend(r.json()["entries"])
    time.sleep(0.2)

# Step 2: filter to resolution ≤ 3.0 Å (skip entries with missing field)
rows = []
for h in hits:
    f = h["fields"]
    name = (f.get("name") or [""])[0]
    try:
        resol = float((f.get("resolution") or [None])[0])
    except (TypeError, ValueError):
        continue
    if resol <= 3.0:
        rows.append({"emdb_id": h["id"], "resolution_A": resol, "name": name[:60]})

df = pd.DataFrame(rows).sort_values("resolution_A")
print(f"Spike entries ≤ 3.0 Å: {len(df)}")
print(df.head(10).to_string(index=False))

# Step 3: pull fitted PDB ids for the top 5
for emdb_id in df["emdb_id"].head(5):
    e = requests.get(f"{EMDB_API}/entry/{emdb_id}", timeout=30).json()
    pdblist = e.get("crossreferences", {}).get("pdb_list", {}) or {}
    pdbs = [p["pdb_id"].upper() for p in pdblist.get("pdb_reference", [])]
    print(f"  {emdb_id} -> PDB: {', '.join(pdbs) if pdbs else '(none fitted)'}")
    time.sleep(0.2)
工作流 2:构建队列元数据表

目标:针对一个查询,返回包含方法、分辨率、生物体和地图下载 URL 的 DataFrame——适用于综述论文。

import requests, time, pandas as pd

EBI_SEARCH = "https://www.ebi.ac.uk/ebisearch/ws/rest/emdb"
EMDB_API   = "https://www.ebi.ac.uk/emdb/api"
EMDB_FTP   = "https://ftp.ebi.ac.uk/pub/databases/emdb/structures"

def cohort_table(query, size=10):
    r = requests.get(EBI_SEARCH,
        params={"query": query, "size": size, "format": "json", "fields": "id"},
        timeout=30)
    r.raise_for_status()
    rows = []
    for h in r.json()["entries"]:
        emdb_id = h["id"]
        e = requests.get(f"{EMDB_API}/entry/{emdb_id}", timeout=30).json()
        sd = e["structure_determination_list"]["structure_determination"][0]
        ip = sd["image_processing"][0]
        try:
            resol = float(ip["final_reconstruction"]["resolution"]["valueOf_"])
        except (KeyError, ValueError, TypeError):
            resol = None
        m = e["map"]
        rows.append({
            "emdb_id": emdb_id,
            "title": e["admin"]["title"][:60],
            "method": sd.get("method"),
            "resolution_A": resol,
            "map_url": f"{EMDB_FTP}/{emdb_id}/map/{m['file']}" if m.get("file") else None,
        })
        time.sleep(0.2)
    return pd.DataFrame(rows)

df = cohort_table("ribosome 70S bacterial", size=6)
print(df.to_string(index=False))
df.to_csv("emdb_cohort.csv", index=False)

关键参数

参数端点默认值范围 / 选项效果
queryEBI Search /emdb必填文本搜索字符串要匹配的关键词
sizeEBI Search /emdb151–100每页命中数
startEBI Search /emdb0非负整数分页偏移量
fieldsEBI Search /emdb(子集)以逗号分隔的 EBI Search 字段要返回的每条命中记录的字段(id,name,resolution,em_method,organism 等)
formatEBI Search /emdbjsonjson、xml响应格式
(路径){EMD-XXXXX}/api/entry/{id}必填EMDB 登录号单条目详情

最佳实践

  1. 关键词搜索始终使用 EBI Search WS。 https://www.ebi.ac.uk/emdb/api/search/ 会忽略 q=,只返回最新发布的条目——依赖它会导致队列结果悄然出错。
  2. 没有子端点。 不要调用 /api/entry/{id}/map、/fitted、/publications 或 /api/statistics/——这些端点都会返回 404(/statistics/ 会返回 HTML)。所有内容都从单个条目文档中读取。
  3. 显式将 resolution.valueOf_ 转换为浮点数。 该字段是类似 "2.5" 的字符串;数值筛选需要显式转换(并使用 try/except 处理缺少值的条目)。
  4. EBI Search 字段值以列表形式返回。 即使是 name 这样的单值字段,也会以 {"name": ["..."]} 的形式返回——始终索引 [0] 或进行拼接。
  5. 在逐条处理条目的循环中加入 time.sleep(0.2)。 虽然没有公布速率限制,但 API 托管在共享服务上;适当间隔可避免暂时出现 502 错误。
  6. 地图下载 URL 的格式为 https://ftp.ebi.ac.uk/pub/databases/emdb/structures/{EMDB_ID}/map/{file}——根据 entry["map"]["file"] 推导,不要硬编码。

常见用法

用法:PDB → EMDB 交叉引用
import requests

# Given an EMDB ID, get its fitted PDB; given a PDB ID, you'd query
# RCSB PDB /rest/v2/entry/{pdb_id} and read `rcsb_external_references.emdb_id`.
EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()
pdblist = e.get("crossreferences", {}).get("pdb_list") or {}
print({"emdb": e["emdb_id"],
       "pdbs": [p["pdb_id"].upper() for p in pdblist.get("pdb_reference", [])]})
用法:某生物体的最高分辨率条目
import requests, time, pandas as pd

EBI_SEARCH = "https://www.ebi.ac.uk/ebisearch/ws/rest/emdb"

def best_by_organism(organism, size=50):
    r = requests.get(EBI_SEARCH,
        params={"query": f'organism:"{organism}"', "size": size,
                "format": "json", "fields": "id,name,resolution"},
        timeout=30)
    r.raise_for_status()
    rows = []
    for h in r.json()["entries"]:
        f = h["fields"]
        try:
            resol = float((f.get("resolution") or [None])[0])
        except (TypeError, ValueError):
            continue
        rows.append({"emdb_id": h["id"], "resolution_A": resol,
                     "name": (f.get("name") or [""])[0][:60]})
    return pd.DataFrame(rows).sort_values("resolution_A").reset_index(drop=True)

df = best_by_organism("Saccharomyces cerevisiae", size=20)
print(df.head(8).to_string(index=False))
用法:引文导出(适用于 BibTeX)
import requests

EMDB_API = "https://www.ebi.ac.uk/emdb/api"
e = requests.get(f"{EMDB_API}/entry/EMD-30210", timeout=30).json()
ct = (e.get("crossreferences", {})
        .get("citation_list", {})
        .get("primary_citation", {})
        .get("citation_type")) or {}
authors = [a.get("name") or a.get("name_str")
           for a in (ct.get("author") or ct.get("author_order") or [])]
xrefs = ct.get("external_references", []) or ct.get("xref", [])
doi = next((x.get("valueOf_") for x in xrefs if x.get("type") == "DOI"), "")
print({"title": (ct.get("title") or "").strip(),
       "journal": ct.get("journal"),
       "year": ct.get("year"),
       "authors_n": len(authors),
       "doi": doi})

故障排查

问题原因解决方案
搜索 EMDB 时出现 KeyError: 'results' 或 'numFound'/api/search/ 返回原始 JSON 数组,并忽略 q=使用 EBI Search WS(/ebisearch/ws/rest/emdb)——包装对象为 {hitCount, entries, facets}
/api/entry/{id}/map(或 /fitted、/publications)返回 HTTP 404这些子端点不存在从单条条目响应中读取 entry["map"]、entry["crossreferences"]["pdb_list"]、entry["crossreferences"]["citation_list"]
分辨率以字符串形式返回EMDB 将数值字段存储为字符串使用 float(entry[...]['resolution']['valueOf_'])——并用 try/except 包裹
EBI Search 的字段看起来像单元素列表EBI Search 会将多值字段作为列表返回读取 f["name"][0](或使用 (... or [""])[0] 作为后备)
EBI Search 返回空的 entries字段限定符错误或有拼写错误移除字段限定符,使用纯文本搜索;访问 https://www.ebi.ac.uk/ebisearch/ 检查
/api/statistics/ 返回 HTML 响应/statistics/ 返回的是 HTML,而不是 JSON此端点供 Web UI 使用;不要以编程方式调用
某条目的 pdb_list 为 {}该图谱没有拟合的原子模型这是正常情况——许多断层图 / 亚断层平均图都没有拟合的 PDB

相关技能

  • pdb-database — RCSB PDB,提供 EMDB 交叉引用中的原子模型信息
  • alphafold-database-access — AI 预测的结构(可补充实验性 EMDB 图谱)
  • uniprot-protein-database — 确定 EMDB 样本的生物体 / 序列背景
  • cellxgene-census — 可补充结构调查的组织 / 细胞类型表达数据

参考资料

评论

支持 Markdown