Created
February 17, 2026 19:40
-
-
Save duarteocarmo/ee007986e70e8e3b31c966c199a7fea9 to your computer and use it in GitHub Desktop.
Lookup a GND person record via lobid.org – returns VIAF ID, geocoordinates, Wikidata ID, ISNI, professions, etc. Usage: uv run lookup_gnd.py <gnd_id>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # /// script | |
| # requires-python = ">=3.11" | |
| # dependencies = ["httpx"] | |
| # /// | |
| """Lookup a GND person record via lobid.org and return enriched info (VIAF, coordinates, etc). | |
| Usage: | |
| uv run scripts/lookup_gnd.py <gnd_id> | |
| uv run scripts/lookup_gnd.py 110952928 | |
| Example output includes VIAF ID, ISNI, Wikidata ID, birth/death info, | |
| geocoordinates for places, professions, and more. | |
| """ | |
| import sys | |
| import json | |
| import httpx | |
| def extract_viaf_id(same_as: list[dict]) -> str | None: | |
| for entry in same_as: | |
| if entry.get("collection", {}).get("abbr") == "VIAF": | |
| return entry["id"].rsplit("/", 1)[-1] | |
| return None | |
| def extract_identifier(same_as: list[dict], abbr: str) -> str | None: | |
| for entry in same_as: | |
| if entry.get("collection", {}).get("abbr") == abbr: | |
| return entry["id"].rsplit("/", 1)[-1] | |
| return None | |
| def resolve_place(place_ref: dict, client: httpx.Client) -> dict: | |
| """Resolve a place GND reference to get label and coordinates.""" | |
| place_id = place_ref.get("id", "") | |
| label = place_ref.get("label", "") | |
| result = { | |
| "label": label, | |
| "lat": None, | |
| "lng": None, | |
| "gnd_id": place_id.rsplit("/", 1)[-1], | |
| } | |
| if not place_id or not place_id.startswith("https://d-nb.info/gnd/"): | |
| return result | |
| gnd_id = place_id.rsplit("/", 1)[-1] | |
| try: | |
| resp = client.get(f"https://lobid.org/gnd/{gnd_id}.json", timeout=10) | |
| resp.raise_for_status() | |
| place_data = resp.json() | |
| for geom in place_data.get("hasGeometry", []): | |
| wkt = geom.get("asWKT", [None])[0] | |
| if wkt and "Point" in wkt: | |
| # Format: "Point ( +013.416669 +052.500000 )" | |
| coords = wkt.replace("Point", "").strip(" ()") | |
| parts = coords.split() | |
| if len(parts) == 2: | |
| result["lng"] = float(parts[0]) | |
| result["lat"] = float(parts[1]) | |
| except (httpx.HTTPError, ValueError, KeyError): | |
| pass | |
| return result | |
| def lookup_gnd(gnd_id: str) -> dict: | |
| with httpx.Client() as client: | |
| resp = client.get(f"https://lobid.org/gnd/{gnd_id}.json", timeout=10) | |
| resp.raise_for_status() | |
| data = resp.json() | |
| same_as = data.get("sameAs", []) | |
| name_entity = data.get("preferredNameEntityForThePerson", {}) | |
| # Resolve places | |
| birthplace = None | |
| if data.get("placeOfBirth"): | |
| birthplace = resolve_place(data["placeOfBirth"][0], client=client) | |
| deathplace = None | |
| if data.get("placeOfDeath"): | |
| deathplace = resolve_place(data["placeOfDeath"][0], client=client) | |
| activity_places = [] | |
| for place_ref in data.get("placeOfActivity", []): | |
| activity_places.append(resolve_place(place_ref, client=client)) | |
| professions = [ | |
| p.get("label", "") for p in data.get("professionOrOccupation", []) | |
| ] | |
| countries = [c.get("label", "") for c in data.get("geographicAreaCode", [])] | |
| gender_list = data.get("gender", []) | |
| gender = gender_list[0].get("label", "") if gender_list else None | |
| variant_names = data.get("variantName", []) | |
| bio_info = data.get("biographicalOrHistoricalInformation", []) | |
| return { | |
| "gnd_id": gnd_id, | |
| "viaf_id": extract_viaf_id(same_as), | |
| "isni_id": extract_identifier(same_as, "ISNI"), | |
| "wikidata_id": extract_identifier(same_as, "WIKIDATA"), | |
| "preferred_name": data.get("preferredName"), | |
| "forename": (name_entity.get("forename") or [None])[0], | |
| "surname": (name_entity.get("surname") or [None])[0], | |
| "variant_names": variant_names or None, | |
| "date_of_birth": (data.get("dateOfBirth") or [None])[0], | |
| "date_of_death": (data.get("dateOfDeath") or [None])[0], | |
| "birthplace": birthplace, | |
| "deathplace": deathplace, | |
| "activity_places": activity_places or None, | |
| "associated_countries": countries or None, | |
| "professions": professions or None, | |
| "gender": gender, | |
| "biographical_info": bio_info[0] if bio_info else None, | |
| } | |
| def main(): | |
| if len(sys.argv) != 2: | |
| print("Usage: uv run scripts/lookup_gnd.py <gnd_id>") | |
| print("Example: uv run scripts/lookup_gnd.py 110952928") | |
| sys.exit(1) | |
| gnd_id = sys.argv[1] | |
| try: | |
| result = lookup_gnd(gnd_id=gnd_id) | |
| print(json.dumps(result, indent=2, ensure_ascii=False)) | |
| except httpx.HTTPStatusError as e: | |
| print(f"Error: HTTP {e.response.status_code} for GND {gnd_id}", file=sys.stderr) | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment