Wie man mit Ruby und Nokogiri kratzt und die Daten abbildet

Manchmal möchten Sie Daten von einer Website für Ihr eigenes Projekt abrufen. Also, was benutzt du? Ruby, Nokogiri und JSON zur Rettung!

Vor kurzem habe ich an einem Projekt gearbeitet, um Daten über Brücken abzubilden. Mit Nokogiri konnte ich die Brückendaten einer Stadt aus einer Tabelle erfassen. Ich habe dann Links in derselben Tabelle verwendet, um zugehörige Seiten zu entfernen. Schließlich habe ich die gescrapten Daten in JSON konvertiert und damit eine Google Map ausgefüllt.

Dieser Artikel führt Sie durch die von mir verwendeten Tools und die Funktionsweise des Codes!

Den vollständigen Code finden Sie auf meinem GitHub-Repo.

Live-Karten-Demo hier.

Das Projekt

Mein Ziel war es, eine Tabelle von einer Bridge-Daten-Website in eine Google-Karte mit geolokalisierten Pins umzuwandeln, die Informations-Popups für jede Bridge erstellen.

Um dies zu erreichen, müsste ich:

  1. Kratzen Sie Daten von der ursprünglichen Website.
  2. Konvertieren Sie diese Daten in ein JSON-Objekt.
  3. Wenden Sie diese Daten an, um eine neue interaktive Karte zu erstellen.

Ihr Projekt wird sicherlich variieren - wie viele Menschen versuchen, antike Brücken zu kartieren? - aber ich hoffe, dieser Prozess wird sich für Ihren Kontext als nützlich erweisen.

Nokogiri

Ruby hat ein erstaunliches Web-Scraping-Juwel namens Nokogiri. Unter anderem können Sie HTML-Dokumente nach CSS-Selektoren durchsuchen. Das heißt, wenn wir die IDs, Klassen oder sogar Arten von Elementen kennen, in denen die Daten im DOM gespeichert sind, können wir sie herauszupfen.

Der Schaber

Wenn Sie dem GibHub-Repo folgen, finden Sie meinen Scraper in bridge_scraper.rb

require 'open-uri'require 'nokogiri'require 'json'

Mit Open-uri können wir den HTML-Code wie eine Datei öffnen und ihn für das schwere Heben an Nokogiri übergeben.

Im folgenden Code übergebe ich die DOM-Informationen von der URL mit den Brückendaten an Nokogiri. Ich finde dann das Tabellenelement, das die Daten enthält, suche nach seinen Zeilen und iteriere durch sie.

url = '//bridgereports.com/city/wichita-kansas/'html = open(url)
doc = Nokogiri::HTML(html)bridges = []table = doc.at('table')
table.search('tr').each do |tr| bridges.push( carries: cells[1].text, crosses: cells[2].text, location: cells[3].text, design: cells[4].text, status: cells[5].text, year_build: cells[6].text.to_i, year_recon: cells[7].text, span_length: cells[8].text.to_f, total_length: cells[9].text.to_f, condition: cells[10].text, suff_rating: cells[11].text.to_f, id: cells[12].text.to_i )end
json = JSON.pretty_generate(bridges)File.open("data.json", 'w')  file.write(json) 

Nokogiri hat viele Methoden (hier ist ein Spickzettel und eine Starter-Anleitung!). Wir verwenden nur wenige.

Die Tabelle wird mit .at ('table') gefunden , das das erste Auftreten eines Tabellenelements im DOM zurückgibt. Dies funktioniert gut für diese relativ einfache Seite.

Mit der Tabelle in der Hand bietet .search ('tr') ein Array der Zeilenelemente , die wir mit .each durchlaufen . In jeder Zeile werden die Daten bereinigt und in einen einzelnen Eintrag für das Bridges-Array verschoben.

Nachdem alle Zeilen gesammelt wurden, werden die Daten in JSON konvertiert und in einer neuen Datei namens "data.json" gespeichert.

Daten von mehreren Seiten kombinieren

In diesem Fall benötigte ich Informationen von anderen zugehörigen Seiten. Insbesondere brauchte ich den Breiten- und Längengrad jeder Brücke, der nicht auf dem Tisch stand. Ich stellte jedoch fest, dass der Link in der ersten Zelle jeder Zeile zu einer Seite führte, die diese Details enthielt .

Ich musste Code schreiben, der einige Dinge tat:

  • Sammelte Links aus der ersten Zelle in der Tabelle.
  • Erstellt ein neues Nokogiri-Objekt aus dem HTML-Code auf dieser Seite.
  • Zupfen Sie den Breiten- und Längengrad heraus.
  • Ruhen Sie das Programm aus, bis dieser Vorgang abgeschlossen ist.
cells = tr.search('th, td') links = {} cells[0].css('a').each do |a| links[a.text] = a['href'] end got_coords = false if links['NBI report'] nbi = links['NBI report'] report = "//bridgereports.com" + nbi report_html = open(report) sleep 1 until report_html r = Nokogiri::HTML(report_html) lat = r.css('span.latitude').text.strip.to_f long = r.css('span.longitude').text.strip.to_f
 got_coords = true else got_coords = true end sleep 1 until got_coords == true
 bridges.push( links: links, latitude: lat, longitude: long, carries: cells[1].text, ..., # all other previous key/value pairs )end

Ein paar zusätzliche Dinge sind hier hervorzuheben:

  • Ich benutze die "got_coords" als einfache Binärdatei. Dies ist standardmäßig auf false gesetzt und wird umgeschaltet, wenn die Daten erfasst werden ODER einfach nicht verfügbar sind.
  • Der Breiten- und Längengrad befinden sich in Bereichen mit entsprechenden Klassen. Das macht das Sichern der Daten einfach: .css ('span.latitude') Darauf folgen .text, .strip und .to_f, die 1) den Text aus dem span abrufen , 2) überschüssiges Leerzeichen entfernen und 3) das konvertieren Zeichenfolge zu einer Float-Nummer.

JSON → Google Map

Das neu gebildete JSON-Objekt muss ein wenig an die Google Maps-API angepasst werden. Ich habe dies mit JavaScript in map.js gemacht

Auf die JSON-Daten kann in map.js zugegriffen werden, da sie in den JS-Ordner verschoben, einer Variablen namens "bridge_data" zugewiesen und in einem Tag in index.html enthalten sind.

Gut! Wir konvertieren jetzt die JSON-Datei (die der Variablen bridge_data zugewiesen ist) in ein neues Array, das von Google Maps verwendet werden kann.

const locations = bridge_data.map(function(b) { var mapEntry = []; var info = "Built In: " + b.year_build + "

" + "Span Length: " + b.span_length + " ft

" + "Total Length: " + b.total_length + " ft

" + "Condition: " + b.condition + "

" + "Design: " + b.design + "

"; mapEntry.push( info, b.latitude, b.longitude, b.id ) return mapEntry;});

Ich verwende .map, um ein neues dimensionales Array mit dem Namen "Orte" zu erstellen. Jeder Eintrag enthält Informationen, die in unserem Google Maps-Popup angezeigt werden, wenn der Nutzer auf diesen Pin auf der Karte klickt. Wir geben auch den Breiten-, Längen- und eindeutigen Brücken-ID an.

Das Ergebnis ist eine Google Map, die das Array von Standorten mit Info-reichen Popups für jede Brücke darstellt!

Hat dir das geholfen? Gib es ein paar Klatschen und folge!