Tony Wang9 Min. LesezeitWebsite zu Google Sheets scrapen — IMPORTXML bis API
So scrapst du eine Website nach Google Sheets: IMPORTXML mit echtem XPath, IMPORTHTML, IMPORTDATA, ein Apps-Script-Refresh und eine Python-+-API-Pipeline.
Eine Website in Google Sheets zu scrapen hat drei Stufen, und jede existiert, weil die darunterliegende an eine Grenze gestoßen ist. Stufe eins sind die eingebauten Import-Formeln — IMPORTHTML, IMPORTXML, IMPORTDATA — kostenlos, sofort, und auf statische Seiten begrenzt. Stufe zwei ist Apps Script mit UrlFetchApp und einem zeitgesteuerten Trigger, was dir echtes Scheduling und JSON-Handling verschafft. Stufe drei ist ein kurzes Python-Skript, das eine Scraping API aufruft und Zeilen mit gspread schreibt — dazu greifst du, wenn die Website sich wehrt. Dieser Guide geht bei allen drei in die Tiefe, mit funktionierenden Formeln, kompletten Skripten und einer ehrlichen Darstellung, wo jede an ihre Grenzen kommt. (Brauchst du die Daten lieber in Excel? Der Schwester-Guide scrape a website to Excel deckt Power Query und die Arbeitsmappen-Seite derselben Pipeline ab.)
Stufe 1: Die eingebauten Import-Formeln — richtig gemacht
Die meisten Tutorials zeigen ein einziges IMPORTXML-Beispiel und ziehen weiter. Die Bordmittel haben Besseres verdient — innerhalb ihrer Grenzen eingesetzt sind sie das schnellste Scraping-Werkzeug, das es gibt, weil es nichts zu installieren und nichts zu authentifizieren gibt.
IMPORTHTML — Tabellen und Listen nach Position
IMPORTHTML(url, query, index) greift das N-te table- oder list-Element einer Seite ab. Für jede Seite, deren Daten bereits in einer HTML-Tabelle stecken — Wikipedia, Behörden-Statistiken, Sport-Tabellen —, ist das die ganze Arbeit:
=IMPORTHTML("https://en.wikipedia.org/wiki/List_of_countries_by_population_(United_Nations)", "table", 1)
Liefert der erste Index die falsche Tabelle, rate nicht — zähl sie durch. Setz diesen Helfer in eine Notizzelle, und er beschriftet jede Tabelle der Seite, sodass du den richtigen Index einmal findest:
=INDEX(IMPORTHTML("https://example.com/stats", "table", 2), 1, 1)
Erhöh die abschließende 2 schrittweise, bis die Vorschau die gewünschte Kopfzeile zeigt, und richte dann deine eigentliche Formel auf diesen Index. Verwende "list" als zweites Argument für ul- und ol-Elemente auf dieselbe Weise.
IMPORTXML — XPath für alles andere
IMPORTXML(url, xpath_query) ist das Präzisionsinstrument: Es ruft die Seite ab und führt eine XPath-Query gegen das Markup aus, sodass du genau ein Feld statt einer ganzen Tabelle ziehen kannst. Echte Queries, die du wirklich brauchst:
=IMPORTXML("https://example.com/product", "//h1")
=IMPORTXML("https://example.com/product", "//span[@class='price']")
=IMPORTXML("https://example.com/blog", "//a/@href")
=IMPORTXML("https://example.com/blog", "//article//h2[contains(@class, 'title')]")
=IMPORTXML("https://example.com", "//meta[@name='description']/@content")
Die erste holt den Seitentitel, die zweite ein Preis-Span nach Klasse, die dritte jede Link-URL der Seite, die vierte Überschriften, deren Klasse einen Wert lediglich enthält (praktisch, wenn Websites gehashte Klassennamen mit stabilen Präfixen nutzen), und die fünfte greift Metadaten ab, die überhaupt nie sichtbar gerendert werden. XPath-Attribute kommen als reiner Text zurück, und eine Query, die mehrere Knoten trifft, läuft automatisch die Spalte hinunter. Ein optionales drittes Argument legt das Parsing-Locale fest, falls die Zahlenformate der Quellseite von deinem Sheet abweichen.
Liefert eine Query nichts, teste sie zuerst im Browser: Öffne die DevTools auf der Zielseite und führ $x("//span[@class='price']") in der Konsole aus. Findet DevTools den Knoten, Sheets aber nicht, wird das Element per JavaScript nach dem Laden erzeugt — und du hast die harte Grenze der Bordmittel gefunden (mehr dazu unten). XPath gegen reales Markup zu schreiben ist hier die Kernkompetenz; unser Glossareintrag zu HTML parsing erklärt, warum die Seitenstruktur immer ein bewegliches Ziel ist.
IMPORTDATA — das unterschätzte
IMPORTDATA(url) liest jede URL ein, die CSV oder TSV zurückgibt — kein XPath, keine Indizes, einfach strukturierte Daten direkt in die Zellen:
=IMPORTDATA("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip?format=csv")
=IMPORTDATA("https://data.cityofnewyork.us/api/views/erm2-nwe9/rows.csv?$limit=500")
Behörden-Open-Data-Portale, Finanz-Referenz-Feeds und viele SaaS-Export-Endpoints veröffentlichen schlichte CSV-URLs, und IMPORTDATA behandelt sie wie eine native Datenquelle. Bevor du irgendein XPath schreibst, prüf, ob die Website still und leise einen CSV-Endpoint anbietet — das ist der Unterschied zwischen dem Parsen einer Seite und dem bloßen Ablesen der Antwort. (Genau diese Unterscheidung — gerenderte Seiten parsen versus strukturierte Endpoints konsumieren — ist das ganze Thema von web scraping vs API.)
Wo alle drei an ihre Grenzen kommen
- Kein JavaScript-Rendering. Die Formeln lesen das rohe HTML, das der Server bei der ersten Anfrage sendet. Websites, die ihre Inhalte clientseitig nachladen — Amazon, Google-Ergebnisse, die meisten E-Commerce-Seiten und Dashboards —, reichen Sheets eine leere Hülle, also liefert die Formel
#N/A, »Imported content is empty« oder gar nichts. - Der Refresh ist oberflächlich. Laut Googles Dokumentation prüfen
IMPORTDATA,IMPORTHTMLundIMPORTXMLgrob jede Stunde auf Updates, und nur solange das Dokument geöffnet ist. Ein geschlossenes Sheet aktualisiert nie, und es bloß wieder zu öffnen erzwingt keinen Neuabruf. - Traffic-Limits und die hängende »Loading...«-Zelle. Google erzwingt Nutzungslimits für Import-Funktionen, die über alle geöffneten Dokumente einer Person hinweg berechnet werden. Die häufig genannte praktische Obergrenze liegt bei rund 50 Import-Formeln pro Tabelle, bevor es abbaut, und intensive Nutzung erzeugt die berüchtigte Zelle, die ewig »Loading...« anzeigt — Googles eigener Rat für den Fehler »Loading data may take a while because of the large number of requests« lautet schlicht, weniger Import-Funktionen zu verwenden. Es gibt keine einsehbare Warteschlange und keinen Retry-Button; der Fehlermodus ist Stille. Import-Formeln weigern sich außerdem, volatile Funktionen wie
NOW()oderRANDBETWEEN()zu referenzieren (mitTODAY()als einziger Ausnahme), was den klassischen Cache-Busting-Trick zunichtemacht. - Googles Fetcher wird blockiert. Jede Import-Formel ruft über Googles geteilte Infrastruktur ab — dieselben IP-Bereiche für jeden Sheets-Nutzer weltweit. Websites mit irgendeiner rate limiting- oder Anti-Bot-Schicht haben diese IPs milliardenfach gesehen und sperren sie pauschal, weshalb eine Seite, die in deinem Browser problemlos lädt, in Sheets trotzdem
#N/Aliefern kann, selbst wenn sie vollständig statisch ist.
Fazit: unschlagbar für statische Tabellen, CSV-Feeds und Metadaten auf kooperativen Websites. Sobald du einen Zeitplan, eine JavaScript-gerenderte Seite oder eine geschützte Website brauchst, geh eine Stufe höher.
Stufe 2: Apps Script — UrlFetchApp plus zeitgesteuerter Trigger
Apps Script ist die Eskalation, die die meisten Tabellen-Nutzer nie angehen, und sie behebt zwei der vier Wände gratis: echtes Scheduling und echtes Error-Handling. UrlFetchApp sendet HTTP-Requests mit eigenen Headern, und ein zeitgesteuerter Trigger führt deine Funktion nach der Uhr erneut aus — sogar bei geschlossenem Sheet, was keine Import-Formel kann.
Öffne dein Sheet, geh auf Extensions → Apps Script und füge dieses komplette Skript ein. Es ruft eine JSON-API auf (hier Crawloras Amazon-Search-Endpoint — jeder JSON-Endpoint funktioniert gleich), prüft den Statuscode und schreibt einen Tab namens results neu:
function refreshSheet() {
const API_KEY = PropertiesService.getScriptProperties()
.getProperty("CRAWLORA_API_KEY");
const url =
"https://api.crawlora.net/api/v1/amazon/search?k=" +
encodeURIComponent("mechanical keyboard");
const resp = UrlFetchApp.fetch(url, {
headers: { "x-api-key": API_KEY },
muteHttpExceptions: true, // return the response instead of throwing
});
if (resp.getResponseCode() !== 200) {
console.error("Fetch failed: HTTP " + resp.getResponseCode());
return;
}
const rows = JSON.parse(resp.getContentText()).data;
const header = ["asin", "title", "price", "rating", "review_count", "link"];
const values = rows.map((r) =>
header.map((h) => (r[h] == null ? "" : r[h]))
);
const sheet =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("results");
sheet.clearContents();
sheet.getRange(1, 1, 1, header.length).setValues([header]);
if (values.length) {
sheet.getRange(2, 1, values.length, header.length).setValues(values);
}
}
function installTrigger() {
ScriptApp.newTrigger("refreshSheet").timeBased().everyHours(6).create();
}
Drei Hinweise zum Setup. Speichere den API-Key unter Project Settings → Script properties als CRAWLORA_API_KEY — füge Secrets niemals in Zellen oder Code ein. Führ installTrigger() einmal aus dem Editor aus (oder richte dasselbe im Triggers-Panel ein), und Google ruft refreshSheet() etwa alle sechs Stunden auf; zeitgesteuerte Trigger verhalten sich wie cron, wobei Google die genaue Minute im Zeitfenster zufällig wählt, um Last zu verteilen. Und lass muteHttpExceptions: true stehen — es verwandelt einen fehlgeschlagenen Abruf in einen Statuscode, den du loggen und behandeln kannst, statt in eine rote Exception um 3 Uhr nachts.
Die Grenzen, ehrlich: Apps-Script-Requests laufen nach sechs Minuten in einen Timeout, URLs sind auf 2,082 Zeichen begrenzt, Consumer-Accounts bekommen begrenzte tägliche UrlFetch- und Trigger-Quotas, und — der große Punkt — die Requests stammen weiterhin aus Googles IP-Bereichen. Gegen eine geschützte Website wird Apps Script genau wie IMPORTXML blockiert, denn für das Ziel klopft derselbe Crawler an. Apps Script löst Scheduling und JSON, nicht Zugang. Genau deshalb ruft das Skript oben eine API auf statt einer rohen Seite.
Stufe 3: Python + Scraping API + gspread
Wenn die Zielseite mit JavaScript rendert, ihr Markup rotiert oder Crawler aktiv blockiert, hör auf, Seiten abzurufen, und fang an, Antworten abzurufen. Eine scraping API übernimmt Rendering, Proxy-Rotation und Retries auf ihrer Seite und liefert dasselbe normalisierte JSON, selbst wenn die Website neu gestaltet wird — und gspread macht aus diesem JSON in drei Zeilen Sheet-Zeilen. Das ist das Setup, das den Kontakt mit der Produktion übersteht. (Den Scraper stattdessen selbst zu bauen ist ein legitimer Weg — der Pillar-Artikel web scraping with Python deckt das komplette DIY-Toolkit ab —, aber für eine Tabellen-Pipeline nimmt dir der API-Weg die Wartung ab.)
Einmaliges Google-Setup, fünf Minuten: Erstell in der Google Cloud Console ein Projekt, aktiviere die Google Sheets API und die Google Drive API, erstell einen Service-Account, lade dessen JSON-Key herunter und teile deine Tabelle mit der E-Mail-Adresse des Service-Accounts (sie endet auf iam.gserviceaccount.com), genau wie du sie mit einem Kollegen teilen würdest. Dann:
pip install requests gspread
import requests
import gspread
from datetime import datetime, timezone
API_KEY = "YOUR_API_KEY" # free tier: 2,000 credits/month, no card
resp = requests.get(
"https://api.crawlora.net/api/v1/amazon/search",
params={"k": "mechanical keyboard"},
headers={"x-api-key": API_KEY},
timeout=60,
)
resp.raise_for_status()
items = resp.json()["data"] # normalized result cards
gc = gspread.service_account(filename="service-account.json")
ws = gc.open("Keyboard price tracker").worksheet("log")
stamp = datetime.now(timezone.utc).isoformat(timespec="seconds")
rows = [
[stamp, it["asin"], it["title"], it["price"], it["rating"], it["link"]]
for it in items
]
ws.append_rows(rows, value_input_option="USER_ENTERED")
print(f"Appended {len(rows)} rows at {stamp}")
Beachte den Design-Unterschied zur Apps-Script-Version: Diese hier hängt mit Zeitstempel versehene Zeilen an, statt zu überschreiben, sodass das Sheet zu einer Preishistorie wird, die du charten und pivotieren kannst, nicht nur zu einem Schnappschuss. Tausch das k-Keyword aus oder wechsle gleich den ganzen Endpoint — der Plattform-Katalog listet jede unterstützte Website, die Docs spezifizieren die Felder jedes Endpoints, und im Playground kannst du einen Test-Request im Browser abfeuern, bevor du eine Zeile Code schreibst. Weil pricing pay-on-success abrechnet — berechnet werden nur erfolgreiche 2xx-Antworten —, kostet ein blockierter oder fehlgeschlagener Abruf einen Retry, keine Credits. Plane es mit cron oder einem GitHub-Actions-Workflow; der Excel-Schwester-Guide hat ein komplettes Copy-Paste-Actions-YAML, das hier unverändert funktioniert. Und egal, welcher Zeitplan: halt ihn höflich — ein täglicher Abruf ist crawling, verantwortungsvoll gemacht; eine hämmernde Schleife ist der Weg, wie IPs auf Blocklisten landen.
Welche Stufe solltest du nutzen?
| Import-Formeln | Apps Script + Trigger | Python + Scraping API | |
|---|---|---|---|
| Setup-Zeit | Unter einer Minute | 15–30 Minuten | Etwa 15 Minuten |
| JavaScript-lastige Seiten | Nein | Nur über eine API | Ja |
| Bot-geschützte Seiten | Nein (geteilte Google-IPs) | Nein (dieselben Google-IPs) | Ja (übernimmt die API) |
| Refresh bei geschlossenem Sheet | Nie | Ja — zeitgesteuerter Trigger | Ja — cron / Actions |
| Fehler-Sichtbarkeit | Hängendes »Loading...« / #N/A | Statuscodes, Logs | Voll: Exceptions, Retries, Alerts |
| Übersteht Redesigns | Nein (XPath bricht) | Ja mit API-JSON | Ja (normalisiertes JSON) |
| Kosten | Kostenlos | Kostenlos (quotengebunden) | Kostenlose Stufe 2,000 Credits/Monat; pay-on-success |
Nach Persona:
- Analyst mit statischer Tabelle und Deadline:
IMPORTHTML, in sechzig Sekunden erledigt. Prüf zuerst auf einen CSV-Endpoint und nimmIMPORTDATA, falls es einen gibt. - Ops-Person, die eine Seite täglich in einem geteilten Sheet aktualisiert braucht: Apps Script mit zeitgesteuertem Trigger. Es ist die günstigste wirklich geplante Option, und das Skript oben ist alles.
- Alle, die dynamische, geschützte oder viele Websites scrapen: Python + API + gspread. Es ist die einzige Stufe, auf der das Problem blockierter Websites die Aufgabe von jemand anderem ist.
- Team, das ohnehin in Excel lebt: gleiche Pipeline, andere letzte Meile — der Excel-Guide deckt Power Query und
to_excel()ab.
- Stecken die Daten im initialen Server-HTML? Probier zuerst IMPORTHTML oder IMPORTXML — teste dein XPath in den DevTools mit $x().
- Gibt es einen CSV-Endpoint? IMPORTDATA schlägt XPath jedes Mal, wenn einer existiert.
- Refresh bei geschlossenem Sheet nötig? Import-Formeln können das nicht — nimm einen zeitgesteuerten Apps-Script-Trigger.
- Ist die Seite JavaScript-gerendert oder blockiert sie Googles Fetcher? Leite über eine Scraping API und schreib Zeilen mit gspread.
- Bewahre API-Keys in Script properties oder Umgebungs-Secrets auf, niemals in Zellen, und halte den Abruf-Takt höflich.
Verwandle jede unterstützte Website in ein lebendiges Google Sheet
Ein GET-Request liefert normalisiertes JSON — Apps Script oder gspread erledigt den Rest. 2,000 kostenlose Credits pro Monat, ohne Karte, und pay-on-success: berechnet werden nur erfolgreiche 2xx-Antworten.
Weiterlesen: der Excel-Schwester-Guide für Power Query und die Arbeitsmappen-Seite dieser Pipeline, der Pillar-Artikel web scraping with Python für das komplette DIY-Toolkit und web scraping vs API dazu, wann welcher Ansatz gewinnt.
Häufig gestellte Fragen
Wie scrape ich eine Website nach Google Sheets?
Fang mit den eingebauten Formeln an: IMPORTHTML holt die N-te Tabelle oder Liste einer Seite, IMPORTXML extrahiert bestimmte Elemente mit einer XPath-Query, und IMPORTDATA liest jede öffentliche CSV- oder TSV-URL ein. Ist die Seite JavaScript-gerendert, braucht einen Zeitplan oder blockiert Googles Fetcher, eskaliere zu Apps Script mit UrlFetchApp oder einem Python-Skript, das eine Scraping API aufruft und Zeilen mit gspread schreibt.
Warum zeigt IMPORTXML Loading... an oder liefert #N/A?
Eine Zelle, die auf Loading... hängen bleibt, bedeutet meist, dass du Googles Traffic-Limits für Import-Funktionen erreicht hast — die Lösung sind weniger Import-Formeln über deine geöffneten Tabellen hinweg. #N/A oder ein leeres Ergebnis bedeutet meist, dass das Element per JavaScript nach dem Laden erzeugt wird (Sheets liest nur das initiale Server-HTML) oder die Website Googles geteilte Fetcher-IPs blockiert.
Kann Google Sheets JavaScript-gerenderte Seiten scrapen?
Nein. IMPORTHTML, IMPORTXML und IMPORTDATA lesen nur das rohe HTML, das der Server bei der ersten Anfrage sendet, ohne Browser-Rendering-Schritt. Websites, die Inhalte clientseitig nachladen — Amazon, Google-Ergebnisse, die meisten modernen Web-Apps —, liefern leere Ergebnisse. Der Workaround ist, das normalisierte JSON einer Scraping API stattdessen über Apps Script UrlFetchApp oder ein Python-Skript abzurufen.
Wie viele IMPORT-Funktionen kann ein Google Sheet haben?
Google dokumentiert strenge traffic-basierte Limits, die über alle geöffneten Dokumente einer Person hinweg berechnet werden, statt einer festen Zahl, aber die häufig genannte praktische Obergrenze liegt bei rund 50 Import-Formeln pro Tabelle, bevor Zellen auf Loading... hängen bleiben. Danach solltest du Formeln konsolidieren oder das Abrufen in Apps Script oder ein externes Skript verlagern.
Wie aktualisiere ich gescrapte Daten in Google Sheets automatisch?
Import-Formeln prüfen grob stündlich neu, aber nur solange das Sheet geöffnet ist — ein geschlossenes Sheet aktualisiert nie. Für einen echten Zeitplan nimm einen zeitgesteuerten Apps-Script-Trigger (zum Beispiel ScriptApp.newTrigger().timeBased().everyHours(6)), um einen UrlFetchApp-Abruf erneut auszuführen, oder lass ein Python-+-gspread-Skript per cron oder GitHub Actions laufen; beide funktionieren bei geschlossenem Sheet.
Was ist der Unterschied zwischen IMPORTHTML, IMPORTXML und IMPORTDATA?
IMPORTHTML greift eine ganze Tabelle oder Liste nach ihrer Position auf der Seite ab. IMPORTXML führt eine XPath-Query aus, kann also ein bestimmtes Element, Attribut oder eine Menge von Knoten extrahieren. IMPORTDATA ruft eine URL ab, die CSV oder TSV zurückgibt, und lädt sie direkt in die Zellen. Prüf zuerst auf einen CSV-Endpoint — IMPORTDATA ist von den dreien das robusteste, wenn einer existiert.
Brauche ich ein kostenpflichtiges Tool, um nach Google Sheets zu scrapen?
Für statische Seiten nicht — die Import-Formeln und Apps Script sind kostenlos. Für JavaScript-lastige oder bot-geschützte Websites brauchst du ein Scraping-Backend, aber auch das kann kostenlos starten: Crawloras kostenlose Stufe umfasst 2,000 Credits/Monat ohne Karte, und die Abrechnung ist pay-on-success, du zahlst also nur für erfolgreiche 2xx-Antworten.