Der anfängerfreundliche Leitfaden für Web Scraping mit Rust
Mihai Maxim am 17. Oktober 2022
Eignet sich Rust gut für Web Scraping?
Rust ist eine Programmiersprache, die auf Geschwindigkeit und Effizienz ausgelegt ist. Im Gegensatz zu C oder C++ verfügt Rust über einen integrierten Paketmanager und ein Build-Tool. Außerdem verfügt es über eine ausgezeichnete Dokumentation und einen freundlichen Compiler mit hilfreichen Fehlermeldungen. Es dauert eine Weile, bis man sich an die Syntax gewöhnt hat. Aber wenn man sich daran gewöhnt hat, wird man feststellen, dass man komplexe Funktionen mit nur wenigen Zeilen Code schreiben kann. Web Scraping mit Rust ist eine beeindruckende Erfahrung. Sie erhalten Zugang zu leistungsstarken Scraping-Bibliotheken, die Ihnen den Großteil der Arbeit abnehmen. Das Ergebnis ist, dass Sie mehr Zeit für die Dinge haben, die Ihnen Spaß machen, wie das Entwerfen neuer Funktionen. In diesem Artikel führe ich Sie durch den Prozess der Erstellung eines Web-Scrapers mit Rust.
Wie wird Rust installiert?
Die Installation von Rust ist ein ziemlich unkomplizierter Prozess. Besuchen Sie Install Rust - Rust Programming Language (rust-lang.org) und folgen Sie der empfohlenen Anleitung für Ihr Betriebssystem. Die Seite zeigt je nach verwendetem Betriebssystem unterschiedliche Inhalte an. Am Ende der Installation sollten Sie ein neues Terminal öffnen und rustc --version ausführen. Wenn alles richtig gelaufen ist, sollten Sie die Versionsnummer des installierten Rust-Compilers sehen.
Since we will be building a web scraper, let’s create a Rust project with Cargo. Cargo is Rust’s build system and package manager. If you used the official installers provided by rust-lang.org, Cargo should be already installed. Check whether Cargo is installed by entering the following into your terminal: cargo --version. If you see a version number, you have it! If you see an error, such as command not found, look at the documentation for your method of installation to determine how to install Cargo separately. To create a project, navigate to the desired project location and run cargo new <project name>.
Dies ist die Standard-Projektstruktur:
- Sie schreiben Code in .rs-Dateien.
- Sie verwalten die Abhängigkeiten in der Datei Cargo.toml.
- Besuchen Sie crates.io: Rust Package Registry, um Pakete für Rust zu finden.
Bau eines Web Scrapers mit Rust
Schauen wir uns nun an, wie man mit Rust einen Scraper bauen kann. Der erste Schritt besteht darin, einen klaren Zweck zu definieren. Was möchte ich extrahieren? Der nächste Schritt ist die Entscheidung, wie Sie die gesammelten Daten speichern wollen. Die meisten Leute speichern sie als .json, aber Sie sollten generell das Format wählen, das Ihren individuellen Bedürfnissen am besten entspricht. Wenn diese beiden Anforderungen geklärt sind, können Sie getrost mit der Implementierung eines Scrapers fortfahren. Zur besseren Veranschaulichung dieses Prozesses schlage ich vor, ein kleines Tool zu entwickeln, das Covid-Daten von der Website COVID Live - Coronavirus Statistics - Worldometer (worldometers.info) extrahiert. Es sollte die Tabellen der gemeldeten Fälle analysieren und die Daten als .json speichern. Wir werden diesen Scraper in den folgenden Kapiteln gemeinsam erstellen.
Abruf von HTML mit HTTP-Anfragen
Um die Tabellen zu extrahieren, müssen Sie zunächst den HTML-Code der Webseite abrufen. Wir werden die "reqwest"-Kiste/Bibliothek verwenden, um rohes HTML von der Website zu holen.
Fügen Sie es zunächst als Abhängigkeit in der Datei Cargo.toml hinzu:
reqwest = { version = "0.11", features = ["blocking", "json"] }
Geben Sie dann Ihre Ziel-URL an und senden Sie Ihre Anfrage:
let url = "https://www.worldometers.info/coronavirus/";
let response = reqwest::blocking::get(url).expect("Could not load url.");
Die Funktion "Blockieren" sorgt dafür, dass die Anforderung synchron ist. Infolgedessen wartet das Programm, bis die Anfrage abgeschlossen ist, und fährt dann mit den anderen Anweisungen fort.
let raw_html_string = response.text().unwrap();
Verwendung von CSS-Selektoren zum Auffinden von Daten
Sie haben alle erforderlichen Rohdaten. Jetzt müssen Sie einen Weg finden, um die Tabellen mit den gemeldeten Fällen zu finden. Die beliebteste Rust-Bibliothek für diese Art von Aufgabe heißt "Scraper". Sie ermöglicht HTML-Parsing und Abfragen mit CSS-Selektoren.
Fügen Sie diese Abhängigkeit zu Ihrer Cargo.toml-Datei hinzu:
scraper = "0.13.0"
Fügen Sie diese Module zu Ihrer Datei main.rs hinzu.
use scraper::Selector;
use scraper::Html;
Verwenden Sie nun die rohe HTML-Zeichenkette, um ein HTML-Fragment zu erstellen:
let html_fragment = Html::parse_fragment(&raw_html_string);
Wir wählen die Tabellen aus, die die gemeldeten Fälle von heute, gestern und vor zwei Tagen anzeigen.

Öffnen Sie die Entwicklerkonsole und identifizieren Sie die Tabellen-IDs:

Zum Zeitpunkt der Erstellung dieses Artikels lautet die ID für den heutigen Tag: "main_table_countries_today".
Die beiden anderen Tabellen-IDs sind:
"main_table_countries_yesterday" und "main_table_countries_yesterday2".
Lassen Sie uns nun einige Selektoren definieren:
let table_selector_string = "#main_table_countries_today, #main_table_countries_yesterday, #main_table_countries_yesterday2";
let table_selector = Selector::parse(table_selector_string).unwrap();
let head_elements_selector = Selector::parse("thead>tr>th").unwrap();
let row_elements_selector = Selector::parse("tbody>tr").unwrap();
let row_element_data_selector = Selector::parse("td, th").unwrap();
Übergeben Sie den table_selector_string an die Methode html_fragment select, um die Verweise auf alle Tabellen zu erhalten:
let all_tables = html_fragment.select(&table_selector);
Erstellen Sie unter Verwendung der Tabellenreferenzen eine Schleife, die die Daten aus jeder Tabelle analysiert.
for table in all_tables{
let head_elements = table.select(&head_elements_selector);
for head_element in head_elements{
//parse the header elements
}
let head_elements = table.select(&head_elements_selector);
for row_element in row_elements{
for td_element in row_element.select(&row_element_data_selector){
//parse the individual row elements
}
}
}
Parsing der Daten
Das Format, in dem Sie die Daten speichern, bestimmt die Art und Weise, wie Sie die Daten analysieren. Für dieses Projekt ist es .json. Folglich müssen wir die Tabellendaten in Schlüssel-Wert-Paaren speichern. Wir können die Namen der Tabellenüberschriften als Schlüssel und die Tabellenzeilen als Werte verwenden.
Verwenden Sie die Funktion .text(), um die Kopfzeilen zu extrahieren und in einem Vektor zu speichern:
//for table in tables loop
let mut head:Vec<String> = Vec::new();
let head_elements = table.select(&head_elements_selector);
for head_element in head_elements{
let mut element = head_element.text().collect::<Vec<_>>().join(" ");
element = element.trim().replace("\n", " ");
head.push(element);
}
//head
["#", "Country, Other", "Total Cases", "New Cases", "Total Deaths", ...]
Extrahieren Sie die Zeilenwerte auf ähnliche Art und Weise:
//for table in tables loop
let mut rows:Vec<Vec<String>> = Vec::new();
let row_elements = table.select(&row_elements_selector);
for row_element in row_elements{
let mut row = Vec::new();
for td_element in row_element.select(&row_element_data_selector){
let mut element = td_element.text().collect::<Vec<_>>().join(" ");
element = element.trim().replace("\n", " ");
row.push(element);
}
rows.push(row)
}
//rows
[...
["", "World", "625,032,352", "+142,183", "6,555,767", ...]
...
["2", "India", "44,604,463", "", "528,745", ...]
...]
Verwenden Sie die Funktion zip(), um eine Übereinstimmung zwischen Kopf- und Zeilenwerten herzustellen:
for row in rows {
let zipped_array = head.iter().zip(row.iter()).map(|(a, b)|
(a,b)).collect::<Vec<_>>();
}
//zipped_array
[
...
[("#", ""), ("Country, Other", "World"), ("Total Cases", "625,032,352"), ("New Cases", "+142,183"), ("Total Deaths", "6,555,767"), ...]
...
]
Speichern Sie nun die zipped_array-Paare (Schlüssel, Wert) in einer IndexMap:
serde = {version="1.0.0",features = ["derive"]}
indexmap = {version="1.9.1", features = ["serde"]} (diese Abhängigkeiten hinzufügen)
use indexmap::IndexMap;
//use this to store all the IndexMaps
let mut table_data:Vec<IndexMap<String, String>> = Vec::new();
for row in rows {
let zipped_array = head.iter().zip(row.iter()).map(|(a, b)|
(a,b)).collect::<Vec<_>>();
let mut item_hash:IndexMap<String, String> = IndexMap::new();
for pair in zipped_array{
//we only want the non empty values
if !pair.1.to_string().is_empty(){
item_hash.insert(pair.0.to_string(), pair.1.to_string());
}
}
table_data.push(item_hash);
//table_data
[
...
{"Country, Other": "North America", "Total Cases": "116,665,220", "Total Deaths": "1,542,172", "Total Recovered": "111,708,347", "New Recovered": "+2,623", "Active Cases": "3,414,701", "Serious, Critical": "7,937", "Continent": "North America"}
,
{"Country, Other": "Asia", "Total Cases": "190,530,469", "New Cases": "+109,009", "Total Deaths": "1,481,406", "New Deaths": "+177", "Total Recovered": "184,705,387", "New Recovered": "+84,214", "Active Cases": "4,343,676", "Serious, Critical": "10,640", "Continent": "Asia"}
...
]
IndexMap ist eine gute Wahl für die Speicherung der Tabellendaten, da die Einfügereihenfolge der (Schlüssel-Wert)-Paare erhalten bleibt.
Serialisierung der Daten
Nun, da Sie json-ähnliche Objekte mit Tabellendaten erstellen können, ist es an der Zeit, diese in .json zu serialisieren. Bevor wir beginnen, stellen Sie sicher, dass Sie alle diese Abhängigkeiten installiert haben:
serde = {version="1.0.0",features = ["derive"]}
serde_json = "1.0.85"
indexmap = {version="1.9.1", features = ["serde"]}
Speichern Sie jede Tabelle_data in einem tables_data-Vektor:
let mut tables_data: Vec<Vec<IndexMap<String, String>>> = Vec::new();
For each table:
//fill table_data (see previous chapter)
tables_data.push(table_data);
Definieren Sie einen struct-Container für die tables_data:
#[derive(Serialize)]
struct FinalTableObject {
tables: IndexMap<String, Vec<IndexMap<String, String>>>,
}
Instanziieren Sie die Struktur:
let final_table_object = FinalTableObject{tables: tables_data};
Serialisieren Sie die Struktur in einen .json-String:
let serialized = serde_json::to_string_pretty(&final_table_object).unwrap();
Schreiben Sie die serialisierte .json-Zeichenkette in eine .json-Datei:
use std::fs::File;
use std::io::{Write};
let path = "out.json";
let mut output = File::create(path).unwrap();
let result = output.write_all(serialized.as_bytes());
match result {
Ok(()) => println!("Successfully wrote to {}", path),
Err(e) => println!("Failed to write to file: {}", e),
}
Aaaaalso, Sie sind fertig. Wenn alles richtig gelaufen ist, sollte Ihre .json-Ausgabe wie folgt aussehen:
{
"tables": [
[ //table data for #main_table_countries_today
{
"Country, Other": "North America",
"Total Cases": "116,665,220",
"Total Deaths": "1,542,172",
"Total Recovered": "111,708,347",
"New Recovered": "+2,623",
"Active Cases": "3,414,701",
"Serious, Critical": "7,937",
"Continent": "North America"
},
...
],
[...table data for #main_table_countries_yesterday...],
[...table data for #main_table_countries_yesterday2...],
]
}
You can find the whole code for the project at [Rust][A simple <table> scraper] (github.com)
Anpassung an andere Anwendungsfälle
Wenn Sie mir bis hierher gefolgt sind, haben Sie wahrscheinlich erkannt, dass Sie diesen Scraper auch auf anderen Websites verwenden können. Der Scraper ist nicht an eine bestimmte Anzahl von Tabellenspalten oder Namenskonventionen gebunden. Außerdem ist er nicht auf viele CSS-Selektoren angewiesen. Es sollte also nicht viel Aufwand nötig sein, damit er auch für andere Tabellen funktioniert, oder? Lassen Sie uns diese Theorie testen.

We need a selector for the <table> tag.

Wenn class="wikitable sortable jquery-tablesorter", könnten Sie den table_selector zu ändern:
let table_selector_string = ".wikitable.sortable.jquery-tablesorter";
let table_selector = Selector::parse(table_selector_string).unwrap();
This table has the same <thead> <tbody> structure, so there is no reason to change the other selectors.
Der Scraper sollte jetzt funktionieren. Lassen Sie uns einen Testlauf machen:
{
"tables": []
}
Webscraping mit Rust macht Spaß, nicht wahr?
Wie kann das scheitern?
Lassen Sie uns etwas tiefer graben:
Der einfachste Weg, um herauszufinden, was schief gelaufen ist, ist ein Blick auf den HTML-Code, der von der GET-Anforderung zurückgegeben wird:
let url = "https://en.wikipedia.org/wiki/List_of_countries_by_population_in_2010";
let response = reqwest::blocking::get(url).expect("Could not load url.");
et raw_html_string = response.text().unwrap();
let path = "debug.html";
let mut output = File::create(path).unwrap();
let result = output.write_all(raw_html_string.as_bytes());

Der von der GET-Anfrage zurückgegebene HTML-Code unterscheidet sich von dem, den wir auf der eigentlichen Website sehen. Der Browser bietet eine Umgebung, in der Javascript ausgeführt werden kann, um das Layout der Seite zu verändern. Im Kontext unseres Scrapers erhalten wir die unveränderte Version der Seite.
Our table_selector did not work because the “jquery-tablesorter” class is injected dynamically by Javascript. Also, you can see that the <table> structure is different. The <thead> tag is missing. The table head elements are now found in the first <tr> of the <tbody>. Thus, they will be picked up by the row_elements_selector.
Removing “jquery-tablesorter” from the table_selector is not enough, we also need to handle the missing <tbody> case:
let table_selector_string = ".wikitable.sortable";
if head.is_empty() {
head=rows[0].clone();
rows.remove(0);
}// take the first row values as head if there is no <thead>
Jetzt wollen wir es noch einmal probieren:
{
"tables": [
[
{
"Rank": "--",
"Country / territory": "World",
"Population 2010 (OECD estimate)": "6,843,522,711"
},
{
"Rank": "1",
"Country / territory": "China",
"Population 2010 (OECD estimate)": "1,339,724,852",
"Area (km 2 ) [1]": "9,596,961",
"Population density (people per km 2 )": "140"
},
{
"Rank": "2",
"Country / territory": "India",
"Population 2010 (OECD estimate)": "1,182,105,564",
"Area (km 2 ) [1]": "3,287,263",
"Population density (people per km 2 )": "360"
},
...
]
]
So ist es besser!
Zusammenfassung
Ich hoffe, dass dieser Artikel einen guten Bezugspunkt für Web Scraping mit Rust darstellt. Auch wenn das umfangreiche Typsystem und das Eigentumsmodell von Rust ein wenig überwältigend sein können, ist es keineswegs ungeeignet für Web Scraping. Sie erhalten einen freundlichen Compiler, der Sie ständig in die richtige Richtung weist. Sie finden auch eine Menge gut geschriebener Dokumentation: Die Rust-Programmiersprache - Die Rust-Programmiersprache (rust-lang.org).
Die Erstellung eines Web Scrapers ist nicht immer ein einfacher Prozess. Sie werden mit Javascript-Rendering, IP-Blöcken, Captchas und vielen anderen Rückschlägen konfrontiert. Bei WebScraping API stellen wir Ihnen alle notwendigen Tools zur Verfügung, um diese allgemeinen Probleme zu bekämpfen. Sind Sie neugierig, wie es funktioniert? Sie können unser Produkt kostenlos unter WebScrapingAPI - Produkt testen. Oder Sie können uns unter WebScrapingAPI - Kontakt kontaktieren. Wir beantworten gerne alle Ihre Fragen!
Nachrichten und Aktualisierungen
Bleiben Sie auf dem Laufenden mit den neuesten Web Scraping-Anleitungen und Nachrichten, indem Sie unseren Newsletter abonnieren.
We care about the protection of your data. Read our <l>Privacy Policy</l>.Privacy Policy.

Ähnliche Artikel

Erforschen Sie den detaillierten Vergleich zwischen Scrapy und Selenium für Web Scraping. Von der Datenerfassung in großem Maßstab bis hin zum Umgang mit dynamischen Inhalten - entdecken Sie die Vor- und Nachteile sowie die einzigartigen Funktionen der beiden Frameworks. Erfahren Sie, wie Sie das beste Framework für die Anforderungen und den Umfang Ihres Projekts auswählen können.


Lernen Sie, wie Sie mit Scrapy und Splash dynamische JavaScript-gerenderte Websites scrapen können. Von der Installation über das Schreiben eines Spiders bis hin zum Umgang mit Seitenumbrüchen und der Verwaltung von Splash-Antworten bietet dieser umfassende Leitfaden Schritt-für-Schritt-Anleitungen für Anfänger und Experten gleichermaßen.


Erforschen Sie die transformative Kraft des Web Scraping im Finanzsektor. Von Produktdaten bis zur Stimmungsanalyse bietet dieser Leitfaden Einblicke in die verschiedenen Arten von Webdaten, die für Investitionsentscheidungen zur Verfügung stehen.
