Zum Inhalt springen
Zurück zum Blog

Web Scraping mit R: Ein Praxisleitfaden zu rvest und httr2

Raluca PenciucZuletzt aktualisiert am 20 min read
Web Scraping mit R: Ein Praxisleitfaden zu rvest und httr2
Kurz gesagt: Für Web Scraping mit R parsen Sie HTML mit rvest über CSS-Selektoren oder XPath und laden Seiten mit httr2, inklusive Wiederholungen und Ratenbegrenzung. Für eine einzelne Seite reicht rvest allein; sobald Sie crawlen, laden Sie mit httr2 und parsen mit rvest, und wenn JavaScript die Seite aufbaut, greifen Sie zu read_html_live(). Dieser Leitfaden behandelt Tabellen, Paginierung, Daten in Script-Tags, Scraper-APIs und Offline-Tests.

Die Daten, die Sie brauchen, stehen auf einer Webseite, und Sie möchten sie in einem Tibble haben, ohne R zu verlassen. Web Scraping mit R bringt Sie mit zwei Paketen und dem Tidyverse, das Sie schon kennen, ans Ziel. Neu im Thema? Beginnen Sie mit der Frage, was Web Scraping ist.

Als durchgehendes Beispiel dient Goodreads: die Leserliste „Best Books Ever“ und die Seite zu George Orwells 1984. Sie parsen eine Seite und eine Tabelle mit rvest, crawlen eine paginierte Liste rücksichtsvoll mit httr2, holen Daten, die JavaScript aufbaut, übergeben blockierte Seiten an eine Scraper-API, ohne Ihren Parser zu ändern, testen Ihren Code offline und fassen das Ergebnis zusammen.

Schnellstart

  • Installieren: install.packages(c("rvest", "httr2"))
  • Laden mit read_html(), auswählen mit html_elements(), extrahieren mit html_text2() oder html_attr()
  • read_html_live() für Seiten verwenden, die JavaScript aufbaut
  • R 4.1 oder neuer für die native Pipe |> verwenden

Was brauchen Sie für Web Scraping mit R?

Sie brauchen R 4.1 oder neuer und zwei Pakete: rvest, um HTML zu parsen, und httr2, um Seiten mit Wiederholungen und Ratenbegrenzung abzurufen. Nehmen Sie chromote dazu, wenn JavaScript die Seiten aufbaut, und eine Scraper-API, wenn Websites Sie blockieren. Alles andere, von dplyr bis readr, ist das Tidyverse, das Sie ohnehin nutzen.

R eignet sich gut fürs Scraping, weil die gescrapten Zeilen direkt in einem Tibble landen. Bereinigung und Analyse passieren so in derselben Sitzung.

rvest parst: Es wählt Elemente mit CSS-Selektoren oder XPath aus und liest Text, Attribute und Tabellen aus. httr2 ruft ab: Es setzt Header, wartet zwischen Anfragen, wiederholt vorübergehende Fehler und führt Anfragen parallel aus. rvest kann eine URL weiterhin selbst abrufen, und für eine einzelne Seite genügt das. Sobald Sie crawlen, rufen Sie mit httr2 ab und parsen mit rvest.

Paket

Aufgabe

Einsetzen, wenn

CRAN-Status (Okt. 2026)

rvest

HTML parsen, Auswahl per CSS oder XPath, Tabellen, read_html_live()

Immer

1.0.5

xml2

HTML/XML-Engine unter rvest; url_absolute()

Links auflösen

1.6.0

httr2

Abruf mit Headern, Wiederholungen, Drosselung, parallelen Anfragen

Mehr als eine Handvoll Seiten oder APIs

1.3.0

jsonlite

JSON aus Endpunkten oder Script-Tags parsen

Daten kommen als JSON

2.0.0

chromote

Headless Chrome (von read_html_live() genutzt)

Von JavaScript aufgebaute Seiten

0.5.1

RSelenium

Selenium WebDriver aus R

Vorhandenes Selenium-Setup oder Firefox nötig

1.7.10 (braucht Java und einen Treiber)

testthat

Offline-Tests für Parser

Jeder Scraper, den Sie erneut ausführen

3.3.2

polite, Rcrawler

Ältere Helfer für Höflichkeitsregeln und Crawling

Keinen neuen Code damit beginnen

Seit Oktober 2026 auf CRAN archiviert

Rcrawler und polite wurden beide auf CRAN archiviert (Rcrawler im April 2025, polite im September 2026). Ältere Tutorials, die auf ihnen aufbauen, lassen sich deshalb nicht mehr sauber installieren. Sie brauchen sie nicht: req_throttle() aus httr2 übernimmt das Tempo der Anfragen, und die robots.txt-Prüfung weiter unten in diesem Leitfaden deckt den Rest dessen ab, was polite erledigt hat.

Wie richten Sie R fürs Scraping ein?

Laden Sie R von CRAN herunter, falls Sie es noch nicht haben. Die aktuelle Version ist R 4.6.2, und jedes Snippet hier läuft mit R 4.1 oder neuer. Jeder Editor funktioniert: RStudio, Positron oder VS Code. Installieren Sie dann die Pakete aus diesem Leitfaden in einer Zeile:

install.packages(c("rvest", "httr2", "jsonlite", "dplyr", "readr", "chromote", "testthat"))

read_html_live() braucht außerdem Google Chrome auf Ihrem Rechner. chromote findet es automatisch.

Legen Sie einen Projektordner mit einem Unterordner fixtures/ für gespeicherte Seiten an. Speichern Sie die drei Goodreads-Seiten, die dieser Leitfaden offline parst:

download.file("https://www.goodreads.com/list/show/1.Best_Books_Ever", "fixtures/list-page1.html")
download.file("https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2", "fixtures/list-page2.html")
download.file("https://www.goodreads.com/book/show/5470.1984", "fixtures/book-1984.html")

Wie scrapen Sie eine Webseite mit rvest?

Laden Sie die Seite mit read_html(), wählen Sie die wiederkehrenden Elemente mit html_elements() und einem CSS-Selektor aus und lesen Sie dann aus jedem Element die Felder mit html_element() plus html_text2() oder html_attr() aus. Bauen Sie aus diesen Vektoren ein Tibble. Da die rvest-Funktionen vektorisiert sind, brauchen Sie selten eine Schleife, und fehlende Felder liefern NA, statt etwas kaputtzumachen.

Die Goodreads-Liste „Best Books Ever“ zeigt 100 Bücher pro Seite. Jedes Buch ist eine Zeile einer Tabelle mit der Klasse tableList, und jede Zeile enthält einen Rang, einen Titel-Link, einen Autoren-Link und eine Zeile wie „4.36 avg rating — 10,338,532 ratings“. Diese Funktion macht aus einer Listenseite ein Tibble:

# parse_books.R: turn one Goodreads list page into a tibble
library(rvest)
library(tibble)

parse_books <- function(page, page_url) {
  rows  <- html_elements(page, "table.tableList tr")
  links <- html_element(rows, "a.bookTitle")
  stats <- html_text2(html_element(rows, "span.minirating"))   # "4.36 avg rating — 10,338,532 ratings"
  tibble(
    rank       = as.integer(html_text2(html_element(rows, "td.number"))),
    title      = html_text2(links),
    author     = html_text2(html_element(rows, "a.authorName")),
    avg_rating = readr::parse_number(stats),
    ratings    = readr::parse_number(sub(".*avg rating", "", stats)),
    url        = xml2::url_absolute(html_attr(links, "href"), page_url)
  )
}

Zeile für Zeile:

  • html_elements(page, "table.tableList tr") gibt jede Zeile zurück: ein Knoten pro Buch.
  • html_element(rows, "a.bookTitle"), im Singular, liefert genau ein Ergebnis pro Zeile. Genau das hält die Spalten in Reih und Glied. Fehlte in einer Zeile der Autor, bekämen Sie an dieser Stelle NA und keinen kürzeren Vektor, der alle folgenden Werte verschiebt.
  • html_text2() gibt Text so zurück, wie ein Browser ihn anzeigt, und fasst Zeilenumbrüche und mehrfache Leerzeichen zusammen. Das ältere html_text() liefert den Rohtext samt Einrückung.
  • readr::parse_number() liest die erste Zahl in einem String und macht aus der Bewertungszeile so 4.36. Wenn Sie vorher alles bis „avg rating“ entfernen, bleibt „— 10,338,532 ratings“ übrig, was zu 10338532 wird. Beide Spalten sind Zahlen, die Sie sortieren und mitteln können, keine Strings.
  • xml2::url_absolute() macht aus jedem relativen href, etwa /book/show/2767052-the-hunger-games, eine vollständige URL. Lösen Sie Links relativ zu der Seite auf, von der sie stammen, nicht zu einer von Hand eingetippten Stammadresse. Ein Link kann relativ zum Ordner der Seite sein, und die falsche Basis erzeugt URLs, die mit 404 enden. Die Referenz zu url_absolute() erklärt die Regeln.

Führen Sie sie auf der gespeicherten Seite aus:

# 01_static.R: parse a saved page (offline); the same code works on a live URL
source("parse_books.R")
page  <- read_html("fixtures/list-page1.html")
books <- parse_books(page, "https://www.goodreads.com/list/show/1.Best_Books_Ever")
print(books, n = 5, width = 100)
cat("rows:", nrow(books), "\n")

Ausgabe:

# A tibble: 100 × 6
   rank title                                                       
  <int> <chr>                                                       
1     1 The Hunger Games (The Hunger Games, #1)                     
2     2 Pride and Prejudice                                         
3     3 To Kill a Mockingbird                                       
4     4 Harry Potter and the Order of the Phoenix (Harry Potter, #5)
5     5 The Book Thief                                              
  author          avg_rating  ratings url                                       
  <chr>                <dbl>    <dbl> <chr>                                     
1 Suzanne Collins       4.36 10338532 https://www.goodreads.com/book/show/27670…
2 Jane Austen           4.3   5042082 https://www.goodreads.com/book/show/1885.…
3 Harper Lee            4.26  7157438 https://www.goodreads.com/book/show/2657.…
4 J.K. Rowling          4.5   3954056 https://www.goodreads.com/book/show/2.Har…
5 Markus Zusak          4.39  2994493 https://www.goodreads.com/book/show/19063…
# ℹ 95 more rows
rows: 100

Mit einer gespeicherten Kopie können Sie iterieren, ohne bei jedem Durchlauf eine Anfrage zu senden. read_html() nimmt auch eine URL entgegen, read_html("https://www.goodreads.com/list/show/1.Best_Books_Ever") parst also die Live-Liste.

Wie finden Sie die richtigen CSS-Selektoren?

Öffnen Sie die Seite in Chrome oder Firefox, klicken Sie mit der rechten Maustaste auf die gewünschten Daten und wählen Sie „Untersuchen“. Suchen Sie eine Klasse oder ein Tag, das sich einmal pro Datensatz wiederholt, testen Sie es in der DevTools-Konsole mit document.querySelectorAll() und fügen Sie denselben Selektor dann in html_elements() ein. Stabile Klassennamen sind langen, aus den DevTools kopierten Pfaden vorzuziehen.

Wenn Sie in der Goodreads-Liste einen Buchtitel untersuchen, sehen Sie ein <a class="bookTitle"> innerhalb eines <tr> von table.tableList. document.querySelectorAll("table.tableList tr").length in der Konsole liefert 100, eins pro Buch. Damit ist bestätigt, dass es das richtige wiederkehrende Element ist.

In diesem Leitfaden kommen sechs Selektormuster vor, und sie decken den Großteil der Scraping-Arbeit ab:

  • Tag.Klasse: a.bookTitle trifft <a>-Elemente mit der Klasse bookTitle.
  • Nachfahre: table.tableList tr trifft Zeilen irgendwo innerhalb dieser Tabelle.
  • Nur Klasse: .minirating trifft jedes Element mit dieser Klasse (wir schreiben zur Verdeutlichung span.minirating).
  • ID: script#__NEXT_DATA__ trifft das eine Element mit dieser id.
  • Attributwert: script[type="application/ld+json"] trifft Scripts eines bestimmten Typs.
  • Attribut eines Bedienelements: button[aria-label="Book details and editions"] findet einen Button über seinen barrierefreien Namen, der Designänderungen meist überdauert.

Meiden Sie die langen Pfade, die „Copy selector“ in den DevTools erzeugt, etwa #all_votes > table > tbody > tr:nth-child(1) > td:nth-child(3). Sie brechen, sobald sich das Layout verschiebt. Generierte Klassennamen mit zufälligen Suffixen sind aus demselben Grund fragil.

Wer lieber zeigt und klickt: Die Browser-Erweiterung SelectorGadget schlägt einen Selektor vor, wenn Sie die gewünschten Elemente anklicken. Prüfen Sie den Vorschlag zuerst in der Konsole.

rvest akzeptiert auch XPath: html_elements(page, xpath = "//a[@class='bookTitle']") erledigt dasselbe wie die CSS-Variante. In welchen Fällen XPath das bessere Werkzeug ist, lesen Sie unter wann XPath CSS schlägt. Halten Sie außerdem die vollständige Selektor-Referenz bei der Arbeit offen.

Tipp: Wenn ein Selektor in den DevTools funktioniert, in R aber null Elemente liefert, stehen die Daten womöglich nicht im HTML, das der Server sendet. Die DevTools zeigen die Seite, nachdem JavaScript gelaufen ist. read_html() sieht sie davor.

Wie scrapen Sie eine HTML-Tabelle mit R?

Wählen Sie die Tabelle mit html_element() aus und übergeben Sie sie an html_table(). Das liefert ein Tibble mit einer Spalte pro Tabellenspalte. Setzen Sie header = FALSE, wenn die Tabelle keine Kopfzeile hat, und benennen Sie die Spalten anschließend um. Bereinigen Sie Zahlen danach mit readr::parse_number(), denn html_table() übernimmt Währungssymbole und Text genau so, wie sie angezeigt werden.

Die Goodreads-Liste ist eine echte HTML-Tabelle, daher liest html_table() sie mit einem einzigen Aufruf:

# 03_table.R: read the same list with html_table()
library(rvest)
page <- read_html("fixtures/list-page1.html")
raw  <- page |> html_element("table.tableList") |> html_table(header = FALSE)
dim(raw)
raw$X3 <- trimws(gsub("\\s+", " ", raw$X3))   # collapse the cell's line breaks
print(raw[, c("X1", "X3")], n = 3, width = 100)

Ausgabe:

[1] 100   4
# A tibble: 100 × 2
     X1
  <int>
1     1
2     2
3     3
  X3                                                                            
  <chr>                                                                         
1 The Hunger Games (The Hunger Games, #1) by Suzanne Collins 4.36 avg rating — …
2 Pride and Prejudice by Jane Austen 4.30 avg rating — 5,042,082 ratings score:…
3 To Kill a Mockingbird by Harper Lee 4.26 avg rating — 7,157,438 ratings score…

Sie erhalten 100 Zeilen und vier Spalten. Die erste Spalte ist der Rang, bereits als Integer typisiert. Die dritte Spalte zeigt die Grenze: Goodreads packt Titel, Autor, Bewertung und Abstimmungspunkte in eine einzige Zelle, also liefert html_table() all das als einen String.

html_table() ist die schnellste Option, wenn jede Zelle genau einen Wert enthält, etwa in einer Preisliste oder einer Statistiktabelle. Packt eine Zelle mehrere Felder zusammen, wählen Sie stattdessen innerhalb jeder Zeile aus, wie parse_books() es tut, und erhalten so saubere, typisierte Spalten.

Rufen Sie html_table() auf einer ganzen Seite auf, erhalten Sie eine Liste aller Tabellen. Wählen Sie also zuerst die gewünschte aus.

Wie scrapen Sie mehrere Seiten mit R?

Scrapen Sie die erste Seite, lesen Sie den Link zur nächsten Seite, lösen Sie ihn mit xml2::url_absolute() auf und wiederholen Sie das, bis der Link verschwindet oder Sie ein Seitenlimit erreichen. Rufen Sie über httr2 ab: req_throttle() begrenzt Ihre Anfragerate, und req_retry() wiederholt Antworten mit 429 und 503 und beachtet dabei auch den Retry-After-Header.

Für eine einzelne Seite reicht read_html(url). Für einen Crawl rufen Sie stattdessen mit httr2 ab, denn so steuern Sie User-Agent, Timeout, Anfragerate und Wiederholungen in einer Pipeline:

# 02_paginate.R: polite live crawl with httr2 retries, throttling, and next-page links
library(httr2)
source("parse_books.R")

fetch_html <- function(url) {
  request(url) |>
    req_user_agent("r-scraping-tutorial/1.0 (you@example.com)") |>
    req_throttle(capacity = 1, fill_time_s = 2) |>   # at most ~1 request every 2 seconds
    req_retry(max_tries = 3) |>                      # retries 429 and 503, honors Retry-After
    req_timeout(30) |>
    req_perform() |>
    resp_body_html()
}

scrape_list <- function(start_url, max_pages = 3) {
  url <- start_url
  out <- list()
  for (i in seq_len(max_pages)) {
    page     <- fetch_html(url)
    out[[i]] <- parse_books(page, url)
    next_href <- html_attr(html_element(page, "a.next_page"), "href")
    if (is.na(next_href)) break
    url <- xml2::url_absolute(next_href, url)
  }
  dplyr::bind_rows(out, .id = "page")
}

books <- scrape_list("https://www.goodreads.com/list/show/1.Best_Books_Ever", max_pages = 3)
print(dplyr::count(books, page))
print(range(books$rank))

Ausgabe:

# A tibble: 3 × 2
  page      n
  <chr> <int>
1 1       100
2 2       100
3 3       100
[1]   1 300

Was jeder Teil von fetch_html() macht:

  • req_user_agent() identifiziert Ihren Scraper und gibt der Website eine Möglichkeit, Sie zu kontaktieren. Tragen Sie dort eine echte Adresse ein.
  • req_throttle(capacity = 1, fill_time_s = 2) ist ein Token-Bucket, der eine Anfrage fasst und sich alle zwei Sekunden wieder füllt. Der Crawl schickt also höchstens etwa eine Anfrage alle zwei Sekunden an einen Host, egal wie schnell die Schleife läuft.
  • req_retry() wiederholt vorübergehende Fehler. Standardmäßig sind das HTTP 429 Too Many Requests und 503 Service Unavailable, und wenn der Server einen Retry-After-Header mitschickt, wartet httr2 so lange, bevor es die Anfrage wiederholt.
  • req_timeout(30) verhindert, dass eine hängende Anfrage den ganzen Crawl lahmlegt.

scrape_list() folgt dem „Next“-Link (a.next_page), statt Seitenzahlen zu raten, löst ihn relativ zur aktuellen URL auf und hört auf, wenn der Link verschwindet oder max_pages erreicht ist. bind_rows(.id = "page") hält für jede Zeile die Quellseite fest, und der rank-Bereich von 1 bis 300 bestätigt, dass keine Seite übersprungen oder doppelt geladen wurde.

Halten Sie max_pages während der Entwicklung klein. Drei Seiten zeigen Ihnen so viel wie hundert.

Wie scrapen Sie Seiten parallel?

req_perform_parallel() aus httr2 nimmt eine Liste von Anfragen und führt mehrere davon parallel aus. max_active begrenzt, wie viele gleichzeitig laufen (Standard ist 10). Bauen Sie die Liste mit lapply() über Ihre URLs und ergänzen Sie jede Anfrage um req_throttle(), damit die Gesamtrate gegenüber einem einzelnen Host rücksichtsvoll bleibt. Starten Sie nicht einen Worker pro CPU-Kern gegen dieselbe Website. Das vervielfacht Ihren Traffic und führt schnell zur Sperre. Bei Tausenden URLs ist das Netzwerk der Engpass, nicht R: Eine Seite zu parsen dauert Millisekunden. Verteilen Sie parallele Arbeit auf verschiedene Hosts, nicht auf einen.

Woran erkennen Sie, ob eine Seite JavaScript braucht?

Rufen Sie das rohe HTML mit read_html() ab und prüfen Sie, ob Ihr Selektor etwas findet. Liefert er null Elemente, obwohl der Browser die Daten anzeigt, baut JavaScript diesen Inhalt auf. Bevor Sie zum Browser greifen, schauen Sie in die Script-Tags: Viele Websites liefern die Datensätze als JSON mit, das jsonlite direkt parsen kann.

Die Seite zu 1984 auf Goodreads zeigt einen Button „Book details and editions“. Klicken Sie im Browser darauf, öffnet sich ein Bereich mit Format, Verlag und Sprache der Ausgabe. Im rohen HTML existiert dieser Bereich noch nicht. Die Daten dahinter aber schon, und zwar in zwei Script-Tags:

# 04_js_check.R: is the data in the HTML, or built by JavaScript?
library(rvest)
page <- read_html("fixtures/book-1984.html")
cat("edition details in raw HTML:", length(html_elements(page, "div.EditionDetails")), "\n")

# 1. JSON-LD: the structured summary many sites publish for search engines
ld <- page |> html_element('script[type="application/ld+json"]') |> html_text2() |> jsonlite::fromJSON()
book <- tibble::tibble(
  title      = ld$name,
  author     = ld$author$name[1],
  pages      = ld$numberOfPages,
  avg_rating = ld$aggregateRating$ratingValue,
  ratings    = ld$aggregateRating$ratingCount
)
print(book)

# 2. __NEXT_DATA__: the app state the page's JavaScript renders from
next_data <- page |> html_element("script#__NEXT_DATA__") |> html_text2() |>
  jsonlite::fromJSON(simplifyVector = FALSE)
state     <- next_data$props$pageProps$apolloState
this_book <- Filter(\(x) identical(x$`__typename`, "Book") && identical(x$legacyId, 5470L), state)[[1]]
cat("publisher:", this_book$details$publisher, "| language:", this_book$details$language$name, "\n")

Ausgabe:

edition details in raw HTML: 0 
# A tibble: 1 × 5
  title author        pages avg_rating ratings
  <chr> <chr>         <int>      <dbl>   <int>
1 1984  George Orwell   328       4.21 5812589
publisher: New American Library | language: English 

Der Selektor findet nichts, also baut JavaScript den Bereich auf. Trotzdem enthält die Seite die Daten.

JSON-LD (script[type="application/ld+json"]) ist eine strukturierte Zusammenfassung, die viele Websites für Suchmaschinen veröffentlichen. Sie ist klein, durch schema.org dokumentiert und ändert sich seltener als das Layout einer Seite. Prüfen Sie sie deshalb zuerst auf Produkt-, Buch-, Rezept- und Veranstaltungsseiten.

__NEXT_DATA__ ist der Zustand, aus dem das JavaScript-Framework der Seite rendert. Goodreads speichert Datensätze in einer flachen, nach Typ geschlüsselten Liste, daher behält der Code den Book-Eintrag, dessen ID zu diesem Buch passt. Er enthält Felder, die im sichtbaren HTML fehlen, etwa den Verlag. Seine Struktur ist aber intern und kann sich ohne Ankündigung ändern. Sichern Sie sie deshalb mit Tests ab.

Eine dritte Quelle sind die eigenen JSON-Endpunkte der Website: Öffnen Sie in den DevTools den Tab Network, filtern Sie nach Fetch/XHR und laden Sie neu. httr2 kann einen dort gefundenen Endpunkt aufrufen, und resp_body_json() parst die Antwort, sofern robots.txt den Pfad erlaubt.

Alle drei sind meist schneller und stabiler, als die Seite in einem Browser zu rendern.

Wie scrapen Sie mit JavaScript gerenderte Seiten in R?

Verwenden Sie read_html_live() aus rvest. Die Funktion lädt die Seite über chromote in einem Headless Chrome und lässt Sie die üblichen rvest-Funktionen auf das gerenderte DOM anwenden. Sie kann auch klicken, tippen und scrollen. Installieren Sie zuerst Google Chrome. Für größere Jobs oder blockierende Websites rendern Sie besser remote mit einer Scraper-API, statt Chrome selbst zu betreiben.

read_html_live() öffnet die Seite zu 1984, klickt auf den Details-Button und liest den Bereich, der dann erscheint:

# 05_live.R: render the page in headless Chrome and click a button (rvest >= 1.0.4, chromote + Chrome)
library(rvest)
options(chromote.timeout = 30)   # heavy pages can take longer than the 10-second default
page <- read_html_live("https://www.goodreads.com/book/show/5470.1984")
Sys.sleep(2)   # simple wait; see the note on waiting for a selector
page$click('button[aria-label="Book details and editions"]')
Sys.sleep(1)
items <- page$html_elements("div.EditionDetails div.DescListItem")
details <- tibble::tibble(
  field = items |> html_element("dt") |> html_text2(),
  value = items |> html_element("dd") |> html_text2()
)
print(details)
invisible(page$session$close())

Ausgabe:

# A tibble: 3 × 2
  field     value                               
  <chr>     <chr>                               
1 Format    328 pages, Mass Market Paperback    
2 Published July 1, 1950 by New American Library
3 Language  English                             

read_html_live() kam mit rvest 1.0.4 und gibt ein Live-Seitenobjekt zurück. Dessen Methode $html_elements() funktioniert wie die Funktion, die Sie schon kennen, und das Ergebnis geht direkt an html_element() und html_text2(). Dasselbe Objekt kann per $click() einen Selektor anklicken, per $type() in ein Feld tippen und per $scroll_to() an eine Position scrollen. Damit sind „Mehr laden“-Buttons und Infinite Scroll abgedeckt. Die Referenz zu read_html_live() listet alle Methoden auf.

Sys.sleep() ist die einfachste Art zu warten. Bei langsamen Seiten nutzen Sie eine kurze Schleife, die jede halbe Sekunde length(page$html_elements("div.EditionDetails")) prüft, bis der Wert über null liegt oder ein Zeitlimit abläuft. options(chromote.timeout = 30) gibt schweren Seiten mehr Zeit zum Laden. Schließen Sie die Sitzung, wenn Sie fertig sind, denn jede hält einen Chrome-Prozess am Laufen.

Neu bei Browsern ohne Fenster? Lesen Sie, wie Headless-Browser funktionieren.

RSelenium ist der ältere Weg. Es ist weiterhin auf CRAN und nützlich, wenn Ihr Team bereits ein Selenium Grid betreibt oder Firefox braucht. Der Preis ist die Einrichtung: Java, ein Selenium-Server oder -Treiber und Browser- und Treiberversionen, die zueinander passen müssen.

Lokales Chrome funktioniert gut für eine Handvoll Seiten. Wenn Sie verwaltete Browsersitzungen mit Wartezeiten und Aktionen in der Cloud brauchen, nutzen Sie stattdessen einen gehosteten automatisierten Browser.

Wie scrapen Sie blockierte Seiten aus R mit einer Scraper-API?

Schicken Sie die Ziel-URL aus httr2 an eine Scraper-API und parsen Sie die Antwort wie gewohnt mit rvest. Mit der Scraper API von WebScrapingAPI rufen Sie einen einzigen Endpunkt mit Ihrem api_key und der URL auf. Ergänzen Sie render_js=1 für JavaScript-Seiten. Bewahren Sie den Schlüssel in .Renviron auf, niemals im Code. Ihre Parsing-Funktionen bleiben unverändert.

Wiederholungen und Drosselung lösen die meisten Probleme auf kooperativen Websites. Diese Symptome zeigen, dass das lokale Abrufen der Engpass ist:

  • 403- oder 429-Antworten, die auch nach rücksichtsvollen Wiederholungen immer wiederkommen
  • Challenge- oder Einwilligungsseiten, wo eigentlich der Inhalt stehen sollte
  • Inhalte, die je nach Land variieren, wenn Sie die Version eines bestimmten Standorts brauchen
  • Mehr JavaScript-Seiten zum Rendern, als lokales Chrome bewältigen kann

Bevor Sie Ihre Abrufschicht austauschen, probieren Sie die Grundlagen. Es gibt weitere Wege, Sperren zu vermeiden, und vier sind am wichtigsten:

  • Langsamer werden: Erhöhen Sie fill_time_s in req_throttle().
  • Einen ehrlichen User-Agent mit Kontaktdaten senden.
  • Bereits abgerufene Seiten cachen, statt sie erneut anzufordern.
  • Nichts scrapen, was Sie nicht brauchen, etwa Bilder oder Seiten außerhalb Ihrer Fragestellung.

Bleiben die Symptome, tauschen Sie die Abrufschicht aus. Die Scraper API von WebScrapingAPI nimmt eine URL entgegen und gibt die Seite zurück. Proxys, Wiederholungen und optionales JavaScript-Rendering übernimmt der Dienst selbst. Es gibt kein R-SDK, und Sie brauchen auch keins: Eine einzige httr2-Funktion ist die gesamte Integration.

# 06_scraper_api.R: call WebScrapingAPI's Scraper API from R with httr2
library(httr2)
library(rvest)

wsa_get <- function(url, render_js = FALSE, country = NULL) {
  key <- Sys.getenv("WSA_API_KEY")
  if (key == "") stop("Set WSA_API_KEY in your environment (for example in ~/.Renviron).")
  request("https://api.webscrapingapi.com/v2") |>
    req_url_query(
      api_key   = key,
      url       = url,                       # httr2 URL-encodes query values
      render_js = if (render_js) 1 else NULL,
      country   = country
    ) |>
    req_retry(max_tries = 3) |>
    req_timeout(120) |>
    req_perform()
}

# See exactly what you'll send, without sending it
req <- request("https://api.webscrapingapi.com/v2") |>
  req_url_query(api_key = "YOUR_KEY",
                url = "https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2",
                country = "us")
req$url

Ausgabe:

[1] "https://api.webscrapingapi.com/v2?api_key=YOUR_KEY&url=https%3A%2F%2Fwww.goodreads.com%2Flist%2Fshow%2F1.Best_Books_Ever%3Fpage%3D2&country=us"

Die ausgegebene URL zeigt, warum req_url_query() wichtig ist. Die Ziel-URL hat ein eigenes ?page=2. Unverändert in eine andere URL eingefügt, würden dieses ? und jedes & als Parameter der API gelesen, und die API würde die falsche Seite abrufen. httr2 kodiert sie als %3F und %26, sodass das Ziel unversehrt ankommt. NULL-Argumente werden weggelassen, also erscheinen render_js und country nur, wenn Sie sie setzen.

Halten Sie den Schlüssel aus Ihrem Code heraus. Fügen Sie diese Zeile der Datei .Renviron in Ihrem Home-Verzeichnis hinzu und starten Sie R neu:

WSA_API_KEY=your_api_key_here

Sys.getenv("WSA_API_KEY") liest ihn aus, und das R-Handbuch erklärt, wie R .Renviron beim Start einliest. Ist die Variable nicht gesetzt, bricht wsa_get() mit Error: Set WSA_API_KEY in your environment (for example in ~/.Renviron). ab. Ein ungültiger Schlüssel kommt als HTTP 401 mit dem Body {"error":"Invalid API key"} zurück, und httr2 macht daraus einen R-Fehler. Ein falscher Schlüssel geht also nie unbemerkt als leere Seite durch.

render_js = TRUE führt das JavaScript der Seite aus, bevor das HTML zurückkommt. country = "us" ruft die Seite so ab, wie ein Besucher aus diesem Land sie sehen würde. JavaScript-Rendering und Geotargeting kosten keine zusätzlichen Credits.

Die Antwort fließt in dasselbe parse_books(), das Sie am Anfang geschrieben haben:

source("parse_books.R")
source("wsa_get.R")   # the wsa_get() function from the previous snippet

target <- "https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2"
page   <- wsa_get(target, country = "us") |> httr2::resp_body_html()
books  <- parse_books(page, target)
print(books, n = 5, width = 100)
cat("rows:", nrow(books), "\n")

# Ask the API to run the page's JavaScript before it returns the HTML
book_page <- wsa_get("https://www.goodreads.com/book/show/5470.1984", render_js = TRUE) |>
  httr2::resp_body_html()
html_text2(html_element(book_page, 'h1[data-testid="bookTitle"]'))

Ausgabe:

# A tibble: 100 × 6
   rank title                                                       
  <int> <chr>                                                       
1   101 The Alchemist                                               
2   102 Divergent (Divergent, #1)                                   
3   103 Fahrenheit 451                                              
4   104 The Catcher in the Rye                                      
5   105 The Handmaid's Tale                                         
  author           avg_rating ratings url                                       
  <chr>                 <dbl>   <dbl> <chr>                                     
1 Paulo Coelho           3.91 3382145 https://www.goodreads.com/book/show/18144…
2 Veronica Roth          4.13 3905122 https://www.goodreads.com/book/show/13335…
3 Ray Bradbury           3.97 2488011 https://www.goodreads.com/book/show/13079…
4 J.D. Salinger          3.8  3600144 https://www.goodreads.com/book/show/5107.…
5 Margaret Atwood        4.13 2145887 https://www.goodreads.com/book/show/38447…
# ℹ 95 more rows
rows: 100
[1] "1984"

Der Parser hat sich nicht geändert, nur die Abrufzeile. Jeder Test, den Sie für parse_books() schreiben, deckt also auch diesen Weg ab.

Welche Scraping-Methode in R sollten Sie verwenden?

Verwenden Sie rvest allein für statische Seiten und Übungswebsites. Nehmen Sie httr2 dazu, wenn Sie viele Seiten crawlen und Ratenbegrenzung sowie Wiederholungen brauchen. Parsen Sie JSON aus Script-Tags oder den eigenen JSON-Endpunkt der Website, wenn JavaScript die Daten lädt. Nutzen Sie read_html_live() für einige interaktive Seiten und eine Scraper-API, wenn Sperren oder Volumen das lokale Abrufen unzuverlässig machen.

Situation

Einsetzen

Warum

Statisches HTML, wenige Seiten

read_html() + rvest

Die wenigsten beweglichen Teile

Viele Seiten oder ein Crawl

httr2-Abruf (req_throttle(), req_retry()) + rvest

Ratenbegrenzung, Wiederholungen, Timeouts

Daten als JSON in <script> oder an einem JSON-Endpunkt

httr2 + jsonlite

Schneller und stabiler als Rendern

Von JavaScript aufgebaute Seite, geringes Volumen

read_html_live()

Echtes Chrome, dieselben rvest-Funktionen

Klicks, Logins, mehrstufige Abläufe, die Sie automatisieren dürfen

Methoden von read_html_live() oder RSelenium

Unterstützung für Interaktionen

403/429, die Wiederholungen nicht beheben, länderspezifische Inhalte oder Rendering in großem Umfang

Scraper API aus httr2

Verwaltete Proxys, Wiederholungen und Rendering; Parser unverändert

Wenn Sie unsicher sind, gehen Sie diese Liste von oben nach unten durch und hören beim ersten Ja auf:

  1. Stehen die Daten im rohen HTML? Verwenden Sie rvest.
  2. Brauchen Sie mehr als ein paar Seiten? Rufen Sie mit httr2 ab und parsen Sie mit rvest.
  3. Stehen die Daten in einem Script-Tag oder an einem JSON-Endpunkt? Parsen Sie sie mit jsonlite.
  4. Braucht die Seite einen Browser, und haben Sie nur wenige Seiten? Verwenden Sie read_html_live().
  5. Werden Sie blockiert, brauchen Sie ein bestimmtes Land oder rendern Sie in großem Umfang? Verwenden Sie eine Scraper-API.

Wie testen Sie einen Scraper und machen aus den Ergebnissen einen Datensatz?

Speichern Sie von jedem Seitentyp eine Kopie als HTML-Datei und schreiben Sie testthat-Tests, die sie offline parsen. So lässt ein Redesign der Website einen Test scheitern, statt still NA zu liefern. Behandeln Sie das gescrapte Tibble danach wie alle anderen Daten: Fassen Sie es mit dplyr zusammen, schreiben Sie es mit readr::write_csv() und halten Sie fest, wann Sie es erhoben haben.

Scraper stürzen selten ab, wenn sich eine Website ändert. Sie liefern leere Spalten. Tests gegen gespeicherte Seiten fangen das ab. Die Fixtures haben Sie bereits mit download.file() gespeichert. Wenn Sie mit httr2 abrufen, erledigt writeLines(resp_body_string(resp), "fixtures/page.html") dasselbe. Committen Sie sie ins Projekt, damit die Tests auf jedem Rechner gleich laufen.

Diese testthat-Datei prüft, worauf es beim Listen-Parser ankommt:

# tests/test-parse.R: offline tests against saved HTML, run with testthat::test_file()
library(testthat)
source("../parse_books.R")

test_that("list page parses into 100 clean rows", {
  books <- parse_books(read_html("../fixtures/list-page1.html"),
                       "https://www.goodreads.com/list/show/1.Best_Books_Ever")
  expect_equal(nrow(books), 100)
  expect_false(anyNA(books$avg_rating))
  expect_true(all(books$avg_rating >= 1 & books$avg_rating <= 5))
  expect_true(all(books$ratings > 0))
  expect_match(books$url, "^https://www\\.goodreads\\.com/book/show/")
})

Führen Sie sie im Ordner tests/ mit testthat::test_file("test-parse.R") aus:

[ FAIL 0 | WARN 0 | SKIP 0 | PASS 5 ]

Ändert sich die Website, speichern Sie eine frische Kopie der Seite und führen den Test erneut aus. Ein Fehlschlag zeigt Ihnen, welches Feld kaputt ist, bevor fehlerhafte Daten Ihre Analyse erreichen. Wenden Sie dieselbe Regel „laut scheitern“ in der Produktion an: Liefert ein Selektor null Zeilen, brechen Sie den Lauf mit einem Fehler ab, statt eine leere Datei zu schreiben.

Werden Bücher mit mehr Bewertungen auch besser bewertet? Zwei gespeicherte Seiten liefern Ihnen 200 Bücher zum Anschauen:

# 07_analysis.R: from scraped rows to an answer
library(dplyr, warn.conflicts = FALSE)
source("parse_books.R")
list_url <- "https://www.goodreads.com/list/show/1.Best_Books_Ever"
books <- bind_rows(
  parse_books(read_html("fixtures/list-page1.html"), list_url),
  parse_books(read_html("fixtures/list-page2.html"), paste0(list_url, "?page=2"))
)
books |>
  mutate(popularity = cut(ratings, c(0, 1e6, 3e6, Inf),
                          labels = c("under 1M", "1M to 3M", "over 3M"))) |>
  group_by(popularity) |>
  summarize(books = n(), median_rating = median(avg_rating), .groups = "drop") |>
  print()
books |> count(author, sort = TRUE) |> head(3) |> print()
readr::write_csv(books, "books.csv")
cat("wrote books.csv with", nrow(books), "rows\n")

Ausgabe:

# A tibble: 3 × 3
  popularity books median_rating
  <fct>      <int>         <dbl>
1 under 1M      84          4.1 
2 1M to 3M      79          4.14
3 over 3M       37          4.3 
# A tibble: 3 × 2
  author           n
  <chr>        <int>
1 J.K. Rowling    10
2 Rick Riordan     5
3 Riley Sager      4
wrote books.csv with 200 rows

In dieser Stichprobe haben die 37 Bücher mit mehr als drei Millionen Bewertungen einen Median von 4,3, gegenüber 4,1 bei Büchern mit weniger als einer Million. Das ist ein Muster in einer einzigen, von Lesern gewählten Liste, kein Gesetz des Buchmarkts. Ab hier sind es ganz normale R-Daten: Ein ggplot2-Streudiagramm von ratings gegen avg_rating auf logarithmischer Skala ist das naheliegende nächste Diagramm.

Da sich die Liste täglich ändert, ergänzen Sie vor dem Speichern eine Spalte scraped_at = Sys.time(). Wenn jemand die Analyse nächsten Monat erneut ausführt und andere Zahlen erhält, erklärt der Zeitstempel, warum.

Ist R oder Python besser für Web Scraping?

Python hat mehr Scraping-Bibliotheken und Browser-Tools und eignet sich daher für große Crawler und Produktionsdienste. R passt besser, wenn die gescrapten Daten in Analysen, Berichte oder Modelle in R einfließen, denn rvest liefert Tibbles und httr2 deckt das rücksichtsvolle Abrufen ab. Für die meisten Analysten ist es besser, in einer Sprache zu bleiben, als für etwas schnelleres Scraping zu wechseln. Landen die Daten in einer R-Analyse, einem Quarto-Bericht oder einem Modell, spart Scraping in R eine Übergabe: kein Export und keine zweite Umgebung, die synchron gehalten werden muss.

Die Abrufschicht ist sprachunabhängig, was das Kombinieren einfach macht. Ein HTTP-Client oder eine Scraper-API liefert R und Python dasselbe HTML, sodass ein Team in der einen Sprache crawlen und in der anderen analysieren kann. Für die Python-Seite lesen Sie unseren Leitfaden zu Web Scraping mit Python.

Wie scrapen Sie verantwortungsvoll mit R?

Lesen Sie vor dem Crawlen die Nutzungsbedingungen und die robots.txt der Website, halten Sie die Anfragerate niedrig, identifizieren Sie Ihren Scraper mit einem User-Agent und erheben Sie nur die Felder, die Sie brauchen. Meiden Sie personenbezogene Daten ohne Rechtsgrundlage und scrapen Sie niemals ohne Erlaubnis hinter einem Login. Fragen Sie im Zweifel den Websitebetreiber oder nutzen Sie dessen API.

Mit robots.txt teilt eine Website Crawlern mit, welche Pfade sie nicht besuchen sollen. Der robots.txt-Standard (RFC 9309) legt die Regeln fest: Finden Sie die Gruppe für Ihren User-Agent, und die spezifischste passende Regel gewinnt. Diese Funktion wendet sie auf eine Liste von Pfaden an:

# 08_robots.R: check paths against a site's robots.txt before you crawl (RFC 9309)
library(httr2)

robots_allows <- function(site, paths, agent = "*") {
  resp <- request(site) |> req_url_path("/robots.txt") |>
    req_error(is_error = \(resp) FALSE) |> req_perform()
  if (resp_status(resp) == 404) return(rep(TRUE, length(paths)))   # no robots.txt, no rules
  lines <- trimws(sub("#.*", "", strsplit(resp_body_string(resp), "\n")[[1]]))
  lines <- lines[lines != ""]
  field <- tolower(trimws(sub(":.*", "", lines)))
  value <- trimws(sub("^[^:]*:", "", lines))

  # Keep the Allow/Disallow rules from groups whose User-agent line matches `agent`
  keep <- logical(length(lines)); in_group <- FALSE
  for (i in seq_along(lines)) {
    if (field[i] == "user-agent") {
      if (i == 1 || field[i - 1] != "user-agent") in_group <- FALSE
      in_group <- in_group || value[i] == agent
    } else {
      keep[i] <- in_group && field[i] %in% c("allow", "disallow") && value[i] != ""
    }
  }
  type <- field[keep]; rule <- value[keep]
  pattern <- paste0("^", gsub("*", ".*", gsub("([.?+^$()|{}\\[\\]\\\\])", "\\\\\\1", rule, perl = TRUE), fixed = TRUE))

  vapply(paths, function(path) {
    hits <- which(vapply(pattern, grepl, logical(1), x = path))
    if (length(hits) == 0) return(TRUE)
    best <- hits[nchar(rule[hits]) == max(nchar(rule[hits]))]   # longest match wins
    any(type[best] == "allow")                                  # a tie goes to Allow
  }, logical(1))
}

paths <- c("/list/show/1.Best_Books_Ever", "/book/show/5470.1984", "/book/reviews/5470",
           "/review/list/1", "/search?q=orwell", "/work/quotes/153313")
data.frame(path = paths, allowed = unname(robots_allows("https://www.goodreads.com", paths)))

Ausgabe:

                          path allowed
1 /list/show/1.Best_Books_Ever    TRUE
2         /book/show/5470.1984    TRUE
3           /book/reviews/5470   FALSE
4               /review/list/1   FALSE
5             /search?q=orwell   FALSE
6          /work/quotes/153313    TRUE

Die Listen- und Buchseiten aus diesem Leitfaden sind erlaubt. Rezensionslisten und die Suche sind es nicht. Deshalb arbeitet dieser Leitfaden mit Bewertungen auf Buchebene und erhebt keine einzelnen Rezensionen. Die letzte Zeile zeigt die Längste-Übereinstimmung-Regel in Aktion: Goodreads verbietet /work, erlaubt aber das spezifischere /work/quotes.

robots.txt ist eine Konvention für Crawler, keine rechtliche Erlaubnis, und nicht das einzige Dokument, das zählt. Die Nutzungsbedingungen einer Website können automatisierte Erhebung auch auf Pfaden einschränken, die robots.txt erlaubt. Die Nutzungsbedingungen von Goodreads etwa nehmen Data Mining und automatisierte Extraktionswerkzeuge von der gewährten Lizenz aus. Lesen Sie sie also, bevor Sie diese Beispiele ausführen, und bitten Sie Goodreads um Erlaubnis, bevor Sie seine Daten für etwas anderes als Lernzwecke erheben.

Rezensionen zeigen auch, warum personenbezogene Daten Sorgfalt erfordern: Jede trägt den Namen und die Meinung eines Lesers. Weitere gute Gewohnheiten finden Sie in diesen praktischen Best Practices fürs Scraping und in unseren Grundsätzen für verantwortungsvolle Webdaten.

FAQ

Wird rvest noch gepflegt?

Ja. rvest gehört zum Tidyverse, und Version 1.0.5 ist Stand Oktober 2026 auf CRAN. Version 1.0.4 hat read_html_live() für JavaScript-Seiten eingeführt. Fürs Abrufen pflegt dasselbe Team inzwischen httr2, das Wiederholungen, Ratenbegrenzung und parallele Anfragen übernimmt. Die meisten modernen R-Scraper kombinieren deshalb beide Pakete.

Was ist der Unterschied zwischen rvest und httr2?

rvest parst HTML: Es wählt Elemente per CSS oder XPath aus und extrahiert Text, Attribute und Tabellen. httr2 stellt HTTP-Anfragen: Es setzt Header, wiederholt fehlgeschlagene Anfragen, drosselt Ihre Rate und verarbeitet JSON-APIs. Einfache Seiten können Sie allein mit rvest scrapen, bei Crawls rufen Sie aber mit httr2 ab und parsen mit rvest.

Kann rvest JavaScript-Websites scrapen?

Das einfache read_html() kann es nicht, denn es sieht nur das HTML, das der Server sendet. read_html_live() kann es: Es führt die Seite über chromote in einem Headless Chrome aus und gibt Ihnen das gerenderte DOM, mit dem Sie die normalen rvest-Funktionen nutzen. Prüfen Sie zuerst, ob JSON in Script-Tags oder ein JSON-Endpunkt vorhanden ist; das ist meist schneller.

Lohnt sich RSelenium noch?

RSelenium ist weiterhin auf CRAN, braucht aber Java, einen Selenium-Server oder -Treiber und passende Browserversionen, was die Einrichtung fehleranfällig macht. Setzen Sie es ein, wenn Ihr Team bereits Selenium nutzt oder Firefox braucht. Für die meisten R-Nutzer deckt read_html_live() in rvest Rendern, Klicken und Tippen mit weit weniger Aufwand ab.

Wie scrape ich in R eine Tabelle von einer Website?

Laden Sie die Seite mit read_html(), wählen Sie die Tabelle mit html_element() und einem CSS-Selektor wie "table.data" aus und rufen Sie dann html_table() auf. Sie erhalten ein Tibble mit einer Spalte pro Tabellenspalte. Verwenden Sie header = FALSE für Tabellen ohne Kopfzeile und bereinigen Sie Zahlen anschließend mit readr::parse_number().

Wie vermeide ich, beim Scraping mit R blockiert zu werden?

Werden Sie mit req_throttle() aus httr2 langsamer, wiederholen Sie 429- und 503-Antworten mit req_retry(), setzen Sie einen ehrlichen User-Agent und cachen Sie Seiten, damit Sie sie nicht erneut abrufen. Liefert eine Website trotzdem 403-Fehler oder Challenge-Seiten, leiten Sie die Anfragen über eine Scraper-API, die Proxys und Rendering verwaltet, und lassen Ihren rvest-Parser unverändert.

Wie sollte ich einen API-Schlüssel in R speichern?

Legen Sie den Schlüssel in Ihrer benutzerweiten .Renviron-Datei als Zeile wie WSA_API_KEY=your_key ab, starten Sie R neu und lesen Sie ihn mit Sys.getenv("WSA_API_KEY") aus. Fügen Sie Schlüssel nie in Skripte, Notebooks oder URLs ein, die Sie teilen, und nehmen Sie jede projektbezogene .Renviron-Datei in .gitignore auf, damit der Schlüssel nie in der Versionskontrolle landet.

Kann ich mit R Seiten hinter einem Login scrapen?

Technisch ja: session() aus rvest kann Formulare absenden und Cookies behalten, und httr2 kann authentifizierte Anfragen senden. Tun Sie das nur mit Erlaubnis des Websitebetreibers und im Rahmen seiner Nutzungsbedingungen, denn eingeloggte Bereiche enthalten meist personenbezogene oder vertraglich geschützte Daten. Bietet die Website eine offizielle API an, nutzen Sie diese, statt die eingeloggten Seiten zu scrapen.

Wie scrape ich in R viele Seiten parallel?

Bauen Sie eine Liste von httr2-Anfragen und senden Sie sie mit req_perform_parallel(); max_active begrenzt, wie viele gleichzeitig laufen. Ergänzen Sie req_throttle(), damit ein Host nie mehr Traffic bekommt, als er verkraftet. Halten Sie die Parallelität für jede einzelne Website moderat, denn Geschwindigkeit ist selten so wichtig wie nicht blockiert zu werden.

Öffentlich zugängliche Daten zu scrapen ist oft rechtmäßig, hängt aber von den Nutzungsbedingungen der Website, der Art der Daten und dem örtlichen Recht ab, besonders bei personenbezogenen Daten und urheberrechtlich geschützten Inhalten. Die Programmiersprache ändert daran nichts. Beachten Sie robots.txt, meiden Sie personenbezogene Daten ohne Rechtsgrundlage und holen Sie bei kommerziellen Projekten rechtlichen Rat ein.

Behalten Sie Ihren R-Code, geben Sie die schwierigen Seiten ab

Ihre rvest-Parser müssen sich nicht ändern, wenn eine Website Sie zu blockieren beginnt oder JavaScript braucht. Richten Sie den Abruf auf die Scraper API von WebScrapingAPI und parsen Sie weiter in R. JavaScript-Rendering und Geotargeting kosten keine zusätzlichen Credits, und fehlgeschlagene Anfragen werden nicht berechnet. Vergleichen Sie die Scraper-API-Tarife, beginnend mit einer kostenlosen Testversion mit 5.000 Anfragen.

Über den Autor

Raluca Penciuc, Full-Stack-Entwickler @ WebScrapingAPI

Raluca Penciuc

Full-Stack-Entwickler

Raluca Penciuc ist Full-Stack-Entwicklerin bei WebScrapingAPI. Sie entwickelt Scraper, verbessert Umgehungsstrategien und findet zuverlässige Wege, um die Erkennung auf Zielwebsites zu verringern.

Web-Scraping mit AWS Lambda: Leitfaden für Python und Java 2026
Anleitungen

Web-Scraping mit AWS Lambda: Leitfaden für Python und Java 2026

TL;DR: Kurz gesagt: Web-Scraping mit AWS Lambda funktioniert am besten, wenn jeder Aufruf kurz, begrenzt und unabhängig wiederholbar ist. Beginnen Sie mit direktem HTTP, AWS SAM und S3 und fügen Sie erst dann SQS, Container, Browser-Rendering, Proxys oder eine verwaltete Abrufebene hinzu, wenn sich im Rahmen der Arbeitslast herausstellt, dass diese erforderlich sind.

Suciu Dan26 min read
Artikel lesen
So verwenden Sie GoSpider: Crawlen, URLs bereinigen und Daten extrahieren
Anleitungen

So verwenden Sie GoSpider: Crawlen, URLs bereinigen und Daten extrahieren

TL;DR: GoSpider ist ein Befehlszeilen-Crawler zum Auffinden von URLs, kein vollständiger Scraper für strukturierte Daten. Diese Anleitung zur Verwendung von GoSpider zeigt einen begrenzten Crawl, die saubere Verarbeitung der Ausgabe, die Übergabe von Colly an CSV sowie einen Diagnosepfad für 403-Antworten oder Seiten, die eine JavaScript-Darstellung erfordern.

Suciu Dan20 min read
Artikel lesen
Wie man Redfin scrappt: Python-Leitfaden für Immobiliendaten
Anleitungen

Wie man Redfin scrappt: Python-Leitfaden für Immobiliendaten

TL;DR: Redfin stellt versteckte API-Endpunkte zur Verfügung, die strukturiertes JSON für Immobilienangebote zurückgeben, wodurch das fragile HTML-Parsing vollständig übersprungen werden kann. Diese Anleitung führt Sie durch den Aufbau eines Python-Scrapers, der Miet- und Verkaufsdaten extrahiert, nach Standort sucht, neue Angebote über XML-Sitemaps überwacht und saubere Ergebnisse in CSV oder JSON exportiert.

Suciu Dan11 min read
Artikel lesen

Los geht’s

Sind Sie bereit, Ihre Datenerfassung zu erweitern?

Schließen Sie sich den über 2.000 Unternehmen an, die WebScrapingAPI nutzen, um Webdaten im Unternehmensmaßstab ohne zusätzlichen Infrastrukturaufwand zu extrahieren.