When I downloaded my list of plants and animals from GBIF, they came in with scientific names and no pictures. I couldn’t tell you what most of those species were, even if you told me their common name. I have to learn them all.
I want to display temporary photos so I know what I’m looking for as I dig through my own photos. How do I know what photo is which species? I ask iNaturalist.
Correction. This is a business that needs to come out of the red. Time is money I don’t have. I want 500 pictures in five minutes. I asked ChatGPT how I can ask iNaturalist’s API. After about 20 failed attempts, I finally got a script that gave me a URL of a photo for every species on my list. The script is a bit complicated because it achieves three goals for all 500 species.
I won’t bore you with the frustration of failed attempts. Here is what FINALLY worked using Google Apps Script to run it:
function fetchINatPhotosAndAttributions() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const rows = sheet.getDataRange().getValues();
for (let i = 1; i < rows.length; i++) { // Skip header
const taxonUrl = rows[i][2];
if (!taxonUrl || !taxonUrl.includes("/taxa/")) continue;
const taxonId = taxonUrl.split("/taxa/")[1];
const apiUrl = `https://api.inaturalist.org/v1/taxa/${taxonId}`;
try {
const response = UrlFetchApp.fetch(apiUrl);
const json = JSON.parse(response.getContentText());
const photo = json.results[0].default_photo;
if (!photo) continue;
const photoUrl = photo.medium_url || "";
let rawAttribution = photo.attribution || "Photo © Unknown";
const licenseCode = photo.license_code || "";
const licenseUrl = getLicenseUrl(licenseCode);
const licenseLink = licenseUrl
? `<a href="${licenseUrl}">${licenseCode.toUpperCase()}</a>`
: licenseCode;
// 🔹 Remove "uploaded by ..." if present
rawAttribution = rawAttribution.replace(/,?\s*uploaded by .+$/i, "");
// 🔹 Remove existing license mention (e.g., "(CC BY-NC)" or "CC BY-NC")
rawAttribution = rawAttribution.replace(/\(CC [^)]+\)/i, "").replace(/CC [A-Z\-]+/i, "").trim();
// 🔹 Remove trailing commas or extra spaces
rawAttribution = rawAttribution.replace(/,\s*$/, "").trim();
const cleanAttribution = `${rawAttribution}${licenseLink ? " (" + licenseLink + ")" : ""}`;
// Write photo URL to column D and attribution to column E
sheet.getRange(i + 1, 4).setValue(photoUrl);
sheet.getRange(i + 1, 5).setValue(cleanAttribution);
} catch (e) {
Logger.log(`Error at row ${i + 1}: ${e.message}`);
}
Utilities.sleep(200); // avoid throttling
}
}
function getLicenseUrl(code) {
const base = "https://creativecommons.org/licenses/";
const map = {
"cc0": "zero/1.0/",
"cc-by": "by/4.0/",
"cc-by-sa": "by-sa/4.0/",
"cc-by-nd": "by-nd/4.0/",
"cc-by-nc": "by-nc/4.0/",
"cc-by-nc-sa": "by-nc-sa/4.0/",
"cc-by-nc-nd": "by-nc-nd/4.0/"
};
return code.toLowerCase() in map ? base + map[code.toLowerCase()] : "";
}
This code gave me two new columns on my spreadsheet. I now have the iNaturalist photo URL and photographer for every species on my list.
