Fix wikipedia_url population in INaturalist requester
- Fixed wikipedia_url being incorrectly looked up in the
ancestors dict (which maps rank→name) — now reads directly
from the taxon object
- Updated enrich_with_ancestors to capture and store
wikipedia_url per taxon alongside ancestor data
- Merges wikipedia_url back into species during enrichment
pass, with fallback to value captured in the first pass
This commit is contained in:
1 parent
f18c7859d0
commit
7a91a9bed4
1 file changed
+175
@@ -0,0 +1,175 @@
|
||||
import requests
|
||||
from pathlib import Path
|
||||
import csv
|
||||
import time
|
||||
|
||||
# Portland, OR place ID on iNaturalist
|
||||
PLACE_ID = 2143
|
||||
OUTPUT_FILE = Path(__file__).parent.parent / "wildlife" / "portland_wildlife.csv"
|
||||
|
||||
TAXON_GROUPS = ["Aves", "Mammalia", "Reptilia", "Amphibia", "Insecta"]
|
||||
|
||||
FIELDNAMES = [
|
||||
"taxon_id",
|
||||
"scientific_name",
|
||||
"common_name",
|
||||
"kingdom",
|
||||
"phylum",
|
||||
"class",
|
||||
"order",
|
||||
"family",
|
||||
"genus",
|
||||
"iconic_taxon",
|
||||
"observation_count",
|
||||
"photo_url_thumb",
|
||||
"photo_url_med",
|
||||
"wikipedia_url"
|
||||
]
|
||||
|
||||
def fetch_species_for_group(iconic_taxon: str) -> list[dict]:
|
||||
"""Fetch unique species list for one iconic taxon group using species_counts."""
|
||||
results = []
|
||||
page = 1
|
||||
|
||||
print(f"Fetching {iconic_taxon}...")
|
||||
|
||||
while True:
|
||||
response = requests.get(
|
||||
"https://api.inaturalist.org/v1/observations/species_counts",
|
||||
params={
|
||||
"iconic_taxa": iconic_taxon,
|
||||
"quality_grade": "research",
|
||||
"per_page": 500,
|
||||
"page": page,
|
||||
"place_id" : PLACE_ID,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
results_page = data.get("results", [])
|
||||
if not results_page:
|
||||
break
|
||||
|
||||
for entry in results_page:
|
||||
taxon = entry.get("taxon", {})
|
||||
|
||||
# Skip anything that isn't a species (genus-level matches etc.)
|
||||
if taxon.get("rank") != "species":
|
||||
continue
|
||||
|
||||
ancestors = {
|
||||
a["rank"]: a["name"]
|
||||
for a in taxon.get("ancestors", [])
|
||||
}
|
||||
|
||||
photo = taxon.get("default_photo") or {}
|
||||
|
||||
results.append({
|
||||
"taxon_id": taxon.get("id"),
|
||||
"scientific_name": taxon.get("name"),
|
||||
"common_name": taxon.get("preferred_common_name", ""),
|
||||
"kingdom": ancestors.get("kingdom", ""),
|
||||
"phylum": ancestors.get("phylum", ""),
|
||||
"class": ancestors.get("class", ""),
|
||||
"order": ancestors.get("order", ""),
|
||||
"family": ancestors.get("family", ""),
|
||||
"genus": ancestors.get("genus", ""),
|
||||
"iconic_taxon": taxon.get("iconic_taxon_name", ""),
|
||||
"observation_count": entry.get("count", 0),
|
||||
"photo_url_thumb": photo.get("square_url", ""),
|
||||
"photo_url_med": photo.get("medium_url", ""),
|
||||
"wikipedia_url": taxon.get("wikipedia_url", "")
|
||||
})
|
||||
|
||||
total = data.get("total_results", 0)
|
||||
fetched_so_far = (page - 1) * 500 + len(results_page)
|
||||
print(f" Page {page} — {fetched_so_far}/{total} species")
|
||||
|
||||
if fetched_so_far >= total:
|
||||
break
|
||||
|
||||
page += 1
|
||||
time.sleep(1) # stay within API rate limits (~1 req/sec)
|
||||
|
||||
print(f" Done — {len(results)} species found for {iconic_taxon}\n")
|
||||
return results
|
||||
|
||||
def enrich_with_ancestors(species_list: list[dict]) -> list[dict]:
|
||||
"""
|
||||
Batch-fetch full taxon details from /v1/taxa to fill in ancestor fields.
|
||||
The species_counts endpoint omits ancestors, so we fetch them separately.
|
||||
iNaturalist supports up to 30 comma-separated IDs per request.
|
||||
"""
|
||||
BATCH_SIZE = 30
|
||||
ids = [str(s["taxon_id"]) for s in species_list]
|
||||
taxon_details = {}
|
||||
|
||||
print(f"Enriching {len(ids)} species with ancestor data...")
|
||||
|
||||
for i in range(0, len(ids), BATCH_SIZE):
|
||||
batch = ids[i : i + BATCH_SIZE]
|
||||
response = requests.get(
|
||||
f"https://api.inaturalist.org/v1/taxa/{','.join(batch)}",
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
for taxon in data.get("results", []):
|
||||
ancestors = {
|
||||
a["rank"]: a["name"]
|
||||
for a in taxon.get("ancestors", [])
|
||||
}
|
||||
taxon_details[taxon["id"]] = {
|
||||
"ancestors": ancestors,
|
||||
"wikipedia_url": taxon.get("wikipedia_url", ""),
|
||||
}
|
||||
|
||||
print(f" Fetched batch {i // BATCH_SIZE + 1}/{-(-len(ids) // BATCH_SIZE)}")
|
||||
time.sleep(1)
|
||||
|
||||
# Merge ancestor data back into species list
|
||||
for s in species_list:
|
||||
detail = taxon_details.get(s["taxon_id"], {})
|
||||
ancestors = detail.get("ancestors", {})
|
||||
s["kingdom"] = ancestors.get("kingdom", "")
|
||||
s["phylum"] = ancestors.get("phylum", "")
|
||||
s["class"] = ancestors.get("class", "")
|
||||
s["order"] = ancestors.get("order", "")
|
||||
s["family"] = ancestors.get("family", "")
|
||||
s["genus"] = ancestors.get("genus", "")
|
||||
s["wikipedia_url"] = detail.get("wikipedia_url", "") or s.get("wikipedia_url", "")
|
||||
|
||||
print("Enrichment complete.\n")
|
||||
return species_list
|
||||
|
||||
if __name__ == "__main__":
|
||||
all_species = []
|
||||
|
||||
for group in TAXON_GROUPS:
|
||||
species = fetch_species_for_group(group)
|
||||
all_species.extend(species)
|
||||
|
||||
# Deduplicate by taxon_id just in case
|
||||
seen = set()
|
||||
unique_species = []
|
||||
for s in all_species:
|
||||
if s["taxon_id"] not in seen:
|
||||
seen.add(s["taxon_id"])
|
||||
unique_species.append(s)
|
||||
|
||||
# Sort by observation count descending — most-seen species first
|
||||
# This makes it easy to cap at top ~200 for the POC
|
||||
unique_species.sort(key=lambda x: x["observation_count"], reverse=True)
|
||||
|
||||
# Second pass: fill in ancestor taxonomy from /v1/taxa
|
||||
unique_species = enrich_with_ancestors(unique_species)
|
||||
|
||||
print(f"Total unique species: {len(unique_species)}")
|
||||
|
||||
with open(OUTPUT_FILE, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
|
||||
writer.writeheader()
|
||||
writer.writerows(unique_species)
|
||||
|
||||
print(f"Written to {OUTPUT_FILE}")
|
||||
Reference in new issue
Block a user