Biological Databases
When to Use
- Deciding which database (NCBI, UniProt, PDB, Ensembl, GEO, SRA) holds the data you actually need
- Writing
Bio.Entrez esearch/efetch/elink code to pull sequences, GenBank annotations, or PubMed abstracts
- Resolving accession types (
NM_/NP_/XM_/GSE/SRR) and curated-vs-predicted status
- Fetching protein records from UniProt or 3D structures from PDB by accession/ID
- Chaining a lookup across databases: gene name -> RefSeq mRNA -> protein -> structure -> orthologs
Version Compatibility
- biopython >= 1.81, Python >= 3.10
- UniProt REST API (
rest.uniprot.org), current JSON schema (2024+)
- RCSB PDB Search API v2 and Data API v1 (
search.rcsb.org, data.rcsb.org)
- Ensembl REST API (
rest.ensembl.org), Ensembl release 110+
Prerequisites
pip install biopython
- stdlib only otherwise:
urllib.request, json, urllib.parse
- Set
Entrez.email (required by NCBI); get a free NCBI API key to raise rate limits
- Helpful follow-on skills:
bio-sequence-manipulation-seq-objects, bio-structural-biology-structure-io
Database Selector
| Need |
Database |
Preferred subset |
| Gene info + orthologs |
NCBI Gene / Ensembl |
Ensembl Compara for orthologs |
| mRNA/protein sequence |
NCBI Nucleotide / Protein |
RefSeq (NM_, NP_, NC_) |
| Protein function/structure refs |
UniProt |
SwissProt (reviewed:true) |
| 3D structure |
PDB (RCSB) |
check resolution + method |
| Raw sequencing reads |
SRA |
SRR accession |
| Processed expression data |
GEO |
GSE (series), GSM (sample) |
| Literature |
PubMed |
— |
NCBI Accession Prefixes
| Prefix |
Type |
Curated? |
NM_ |
curated mRNA |
Yes (RefSeq) |
NP_ |
curated protein |
Yes (RefSeq) |
NC_ |
chromosome/complete genome |
Yes (RefSeq) |
XM_/XP_ |
predicted/model mRNA/protein |
No |
NR_ |
non-coding RNA |
Yes (RefSeq) |
GEO accessions: GSE (series/experiment), GSM (sample), GPL (platform), GDS (curated dataset).
SRA hierarchy: Study (SRP) > Sample (SRS) > Experiment (SRX) > Run (SRR).
Goal: search, fetch, and cross-link NCBI records without exhausting rate limits.
Approach: use Entrez.esearch/efetch/elink; always batch IDs and close handles.
from Bio import Entrez, SeqIO
Entrez.email = "your.email@example.com" # required by NCBI
# Entrez.api_key = "..." # optional, raises limit 3 -> 10 req/s
def fetch_refseq_mrna(gene, organism="Homo sapiens"):
"""Find and fetch the top RefSeq mRNA GenBank record for a gene symbol."""
handle = Entrez.esearch(
db="nucleotide",
term=f"{gene}[Gene] AND {organism}[Organism] AND RefSeq[Filter] AND mRNA[Filter]",
retmax=1,
)
ids = Entrez.read(handle)["IdList"]
handle.close()
if not ids:
return None
handle = Entrez.efetch(db="nucleotide", id=ids[0], rettype="gb", retmode="text")
record = SeqIO.read(handle, "genbank")
handle.close()
return record
record = fetch_refseq_mrna("insulin") # NM_000207.3
# Extract CDS and translate directly from GenBank features
for feat in record.features:
if feat.type == "CDS":
cds_seq = feat.location.extract(record.seq)
protein = cds_seq.translate(to_stop=True)
annotated = feat.qualifiers.get("translation", [""])[0]
# Cross-database link: nucleotide -> protein
handle = Entrez.elink(dbfrom="nucleotide", db="protein", id="NM_000207.3")
link_results = Entrez.read(handle)
handle.close()
# Batch fetch (comma-separated IDs — much faster than looping efetch calls)
handle = Entrez.efetch(db="nucleotide", id="NM_000207.3,NM_001301717.2",
rettype="fasta", retmode="text")
Entrez search syntax: insulin[Gene], Homo sapiens[Organism], mRNA[Filter] AND RefSeq[Filter],
2020:2024[PDAT] (date range), CRISPR AND (cancer OR tumor).
Goal: pull a protein record and its structural cross-references from UniProt.
Approach: hit the REST API directly with urllib — no extra dependency needed.
import json
import urllib.parse
import urllib.request
def fetch_uniprot(accession, fmt="json"):
"""Fetch one UniProt entry by accession (e.g. 'P01308' = human insulin)."""
url = f"https://rest.uniprot.org/uniprotkb/{accession}.{fmt}"
with urllib.request.urlopen(url) as r:
return json.loads(r.read()) if fmt == "json" else r.read().decode()
def search_uniprot(query, limit=5):
"""Search UniProt; e.g. query='hemoglobin AND organism_id:9606 AND reviewed:true'."""
encoded = urllib.parse.quote(query)
url = f"https://rest.uniprot.org/uniprotkb/search?query={encoded}&size={limit}&format=json"
with urllib.request.urlopen(url) as r:
return json.loads(r.read())
insulin = fetch_uniprot("P01308")
name = insulin["proteinDescription"]["recommendedName"]["fullName"]["value"]
seq = insulin["sequence"]["value"]
# Cross-references to PDB structures embedded in the UniProt record
pdb_refs = [r for r in insulin.get("uniProtKBCrossReferences", []) if r["database"] == "PDB"]
Goal: fetch and parse a 3D structure once you have a PDB ID (e.g. from the UniProt cross-refs above).
Approach: RCSB search API for discovery, files.rcsb.org for the actual coordinate file.
def download_pdb(pdb_id, fmt="pdb"):
"""Download a structure file from RCSB PDB ('pdb' or 'cif' format)."""
ext = "pdb" if fmt == "pdb" else "cif"
url = f"https://files.rcsb.org/download/{pdb_id}.{ext}"
filename = f"{pdb_id}.{ext}"
urllib.request.urlretrieve(url, filename)
return filename
path = download_pdb("4INS") # classic insulin hexamer crystal structure
Pitfalls
- GenBank vs RefSeq: GenBank has all submitted sequences (redundant, unreviewed); RefSeq is curated. Prefer RefSeq (
NM_/NP_) for analysis
- SwissProt vs TrEMBL: SwissProt (
570K entries, manually curated) vs TrEMBL (250M, auto-annotated); filter with reviewed:true for reliable annotations
- Rate limits: NCBI allows 3 req/s without an API key, 10/s with one (
Entrez.api_key = "..."); batch with comma-separated IDs, never loop individual efetch calls
- GI numbers deprecated: use accession.version (
NM_000207.3), not numeric GI IDs
efetch handle must be closed: always handle.close() (or use with) — unclosed handles exhaust NCBI connections
id as a list breaks efetch: pass a comma-separated string, not a Python list — a common LLM-generated bug
- PDB resolution matters: structures worse than ~3.5 Å only give reliable overall fold, not side-chain/atomic detail
See Also
bio-database-access-entrez-search, bio-database-access-entrez-fetch, bio-database-access-entrez-link
bio-database-access-uniprot-access
bio-structural-biology-structure-io
bio-database-access-geo-data, bio-database-access-sra-data
1---2name: bio-core-biological-databases3description: Fetch sequences via NCBI Entrez (esearch/efetch/elink), UniProt REST, and RCSB PDB APIs with BioPython/urllib. Use when picking a database, decoding accession prefixes (NM_/XM_/GSE/SRR), or cross-linking a gene NCBI-UniProt-PDB.4---56# Biological Databases78## When to Use9- Deciding which database (NCBI, UniProt, PDB, Ensembl, GEO, SRA) holds the data you actually need10- Writing `Bio.Entrez` esearch/efetch/elink code to pull sequences, GenBank annotations, or PubMed abstracts11- Resolving accession types (`NM_`/`NP_`/`XM_`/`GSE`/`SRR`) and curated-vs-predicted status12- Fetching protein records from UniProt or 3D structures from PDB by accession/ID13- Chaining a lookup across databases: gene name -> RefSeq mRNA -> protein -> structure -> orthologs1415## Version Compatibility16- biopython >= 1.81, Python >= 3.1017- UniProt REST API (`rest.uniprot.org`), current JSON schema (2024+)18- RCSB PDB Search API v2 and Data API v1 (`search.rcsb.org`, `data.rcsb.org`)19- Ensembl REST API (`rest.ensembl.org`), Ensembl release 110+2021## Prerequisites22- `pip install biopython`23- stdlib only otherwise: `urllib.request`, `json`, `urllib.parse`24- Set `Entrez.email` (required by NCBI); get a free NCBI API key to raise rate limits25- Helpful follow-on skills: `bio-sequence-manipulation-seq-objects`, `bio-structural-biology-structure-io`2627## Database Selector2829| Need | Database | Preferred subset |30|------|----------|-------------------|31| Gene info + orthologs | NCBI Gene / Ensembl | Ensembl Compara for orthologs |32| mRNA/protein sequence | NCBI Nucleotide / Protein | RefSeq (`NM_`, `NP_`, `NC_`) |33| Protein function/structure refs | UniProt | SwissProt (`reviewed:true`) |34| 3D structure | PDB (RCSB) | check resolution + method |35| Raw sequencing reads | SRA | SRR accession |36| Processed expression data | GEO | GSE (series), GSM (sample) |37| Literature | PubMed | — |3839## NCBI Accession Prefixes4041| Prefix | Type | Curated? |42|--------|------|----------|43| `NM_` | curated mRNA | Yes (RefSeq) |44| `NP_` | curated protein | Yes (RefSeq) |45| `NC_` | chromosome/complete genome | Yes (RefSeq) |46| `XM_`/`XP_` | predicted/model mRNA/protein | No |47| `NR_` | non-coding RNA | Yes (RefSeq) |4849GEO accessions: `GSE` (series/experiment), `GSM` (sample), `GPL` (platform), `GDS` (curated dataset).50SRA hierarchy: Study (`SRP`) > Sample (`SRS`) > Experiment (`SRX`) > Run (`SRR`).5152**Goal:** search, fetch, and cross-link NCBI records without exhausting rate limits.53**Approach:** use `Entrez.esearch`/`efetch`/`elink`; always batch IDs and close handles.5455```python56from Bio import Entrez, SeqIO5758Entrez.email = "your.email@example.com" # required by NCBI59# Entrez.api_key = "..." # optional, raises limit 3 -> 10 req/s6061def fetch_refseq_mrna(gene, organism="Homo sapiens"):62 """Find and fetch the top RefSeq mRNA GenBank record for a gene symbol."""63 handle = Entrez.esearch(64 db="nucleotide",65 term=f"{gene}[Gene] AND {organism}[Organism] AND RefSeq[Filter] AND mRNA[Filter]",66 retmax=1,67 )68 ids = Entrez.read(handle)["IdList"]69 handle.close()70 if not ids:71 return None72 handle = Entrez.efetch(db="nucleotide", id=ids[0], rettype="gb", retmode="text")73 record = SeqIO.read(handle, "genbank")74 handle.close()75 return record7677record = fetch_refseq_mrna("insulin") # NM_000207.37879# Extract CDS and translate directly from GenBank features80for feat in record.features:81 if feat.type == "CDS":82 cds_seq = feat.location.extract(record.seq)83 protein = cds_seq.translate(to_stop=True)84 annotated = feat.qualifiers.get("translation", [""])[0]8586# Cross-database link: nucleotide -> protein87handle = Entrez.elink(dbfrom="nucleotide", db="protein", id="NM_000207.3")88link_results = Entrez.read(handle)89handle.close()9091# Batch fetch (comma-separated IDs — much faster than looping efetch calls)92handle = Entrez.efetch(db="nucleotide", id="NM_000207.3,NM_001301717.2",93 rettype="fasta", retmode="text")94```9596Entrez search syntax: `insulin[Gene]`, `Homo sapiens[Organism]`, `mRNA[Filter] AND RefSeq[Filter]`,97`2020:2024[PDAT]` (date range), `CRISPR AND (cancer OR tumor)`.9899**Goal:** pull a protein record and its structural cross-references from UniProt.100**Approach:** hit the REST API directly with `urllib` — no extra dependency needed.101102```python103import json104import urllib.parse105import urllib.request106107108def fetch_uniprot(accession, fmt="json"):109 """Fetch one UniProt entry by accession (e.g. 'P01308' = human insulin)."""110 url = f"https://rest.uniprot.org/uniprotkb/{accession}.{fmt}"111 with urllib.request.urlopen(url) as r:112 return json.loads(r.read()) if fmt == "json" else r.read().decode()113114115def search_uniprot(query, limit=5):116 """Search UniProt; e.g. query='hemoglobin AND organism_id:9606 AND reviewed:true'."""117 encoded = urllib.parse.quote(query)118 url = f"https://rest.uniprot.org/uniprotkb/search?query={encoded}&size={limit}&format=json"119 with urllib.request.urlopen(url) as r:120 return json.loads(r.read())121122123insulin = fetch_uniprot("P01308")124name = insulin["proteinDescription"]["recommendedName"]["fullName"]["value"]125seq = insulin["sequence"]["value"]126127# Cross-references to PDB structures embedded in the UniProt record128pdb_refs = [r for r in insulin.get("uniProtKBCrossReferences", []) if r["database"] == "PDB"]129```130131**Goal:** fetch and parse a 3D structure once you have a PDB ID (e.g. from the UniProt cross-refs above).132**Approach:** RCSB search API for discovery, `files.rcsb.org` for the actual coordinate file.133134```python135def download_pdb(pdb_id, fmt="pdb"):136 """Download a structure file from RCSB PDB ('pdb' or 'cif' format)."""137 ext = "pdb" if fmt == "pdb" else "cif"138 url = f"https://files.rcsb.org/download/{pdb_id}.{ext}"139 filename = f"{pdb_id}.{ext}"140 urllib.request.urlretrieve(url, filename)141 return filename142143path = download_pdb("4INS") # classic insulin hexamer crystal structure144```145146## Pitfalls147148- **GenBank vs RefSeq**: GenBank has all submitted sequences (redundant, unreviewed); RefSeq is curated. Prefer RefSeq (`NM_`/`NP_`) for analysis149- **SwissProt vs TrEMBL**: SwissProt (~570K entries, manually curated) vs TrEMBL (~250M, auto-annotated); filter with `reviewed:true` for reliable annotations150- **Rate limits**: NCBI allows 3 req/s without an API key, 10/s with one (`Entrez.api_key = "..."`); batch with comma-separated IDs, never loop individual `efetch` calls151- **GI numbers deprecated**: use accession.version (`NM_000207.3`), not numeric GI IDs152- **`efetch` handle must be closed**: always `handle.close()` (or use `with`) — unclosed handles exhaust NCBI connections153- **`id` as a list breaks `efetch`**: pass a comma-separated string, not a Python list — a common LLM-generated bug154- **PDB resolution matters**: structures worse than ~3.5 Å only give reliable overall fold, not side-chain/atomic detail155156## See Also157- `bio-database-access-entrez-search`, `bio-database-access-entrez-fetch`, `bio-database-access-entrez-link`158- `bio-database-access-uniprot-access`159- `bio-structural-biology-structure-io`160- `bio-database-access-geo-data`, `bio-database-access-sra-data`