Sådan skrabes med Ruby og Nokogiri og kortlægges dataene

Nogle gange vil du hente data fra et websted til dit eget projekt. Så hvad bruger du? Ruby, Nokogiri og JSON til undsætning!

For nylig arbejdede jeg på et projekt for at kortlægge data om broer. Ved hjælp af Nokogiri var jeg i stand til at hente en bys brodata fra et bord. Jeg brugte derefter links inden for den samme tabel til at skrabe tilknyttede sider. Endelig konverterede jeg de skrabede data til JSON og brugte dem til at udfylde et Google Map.

Denne artikel leder dig gennem de værktøjer, jeg brugte, og hvordan koden fungerer!

Se den fulde kode på min GitHub repo.

Live kort demo her.

Projektet

Mit mål var at tage en tabel fra et brodata-websted og gøre det til et Google-kort med geolokaliserede stifter, der ville producere informationsvinduer til hver bro.

For at få dette til at ske, skal jeg:

  1. Skrab data fra det oprindelige websted.
  2. Konverter disse data til et JSON-objekt.
  3. Anvend disse data for at oprette et nyt, interaktivt kort.

Dit projekt vil helt sikkert variere - hvor mange mennesker prøver at kortlægge antikke broer? - men jeg håber, at denne proces vil være nyttig i din sammenhæng.

Nokogiri

Ruby har en fantastisk webskrabningsperle kaldet Nokogiri. Blandt andre funktioner giver det dig mulighed for at søge i HTML-dokumenter efter CSS-vælgere. Det betyder, at hvis vi kender id'erne, klasser eller endda typer af elementer, hvor dataene er gemt i DOM, er vi i stand til at plukke dem ud.

Skraberen

Hvis du følger med GibHub repo, kan du finde min skraber i bridges_scraper.rb

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

Open-uri lader os åbne HTML som en fil og videresende den til Nokogiri til tunge løft.

I koden nedenfor sender jeg DOM-oplysningerne fra URL'en med brodataene til Nokogiri. Derefter finder jeg tabelelementet, der indeholder dataene, søger efter dens rækker og gentager dem.

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 har mange metoder (her er et snydeark og en startguide!). Vi bruger kun nogle få.

Tabellen findes med .at ('tabel') , som returnerer den første forekomst af et tabelelement i DOM. Dette fungerer fint for denne relativt enkle side.

Med tabellen i hånden giver .search ('tr') en matrix af rækkeelementerne, som vi gentager med .each . I hver række ryddes dataene op og skubbes ind i en enkelt post for broer-arrayet.

Når alle rækkerne er samlet, konverteres dataene til JSON og gemmes i en ny fil kaldet "data.json".

Kombination af data fra flere sider

I dette tilfælde havde jeg brug for information fra andre tilknyttede sider. Specifikt havde jeg brug for bredde- og længdegrad for hver bro, som ikke var med på bordet. Imidlertid fandt jeg, at linket i den første celle i hver række førte til en side, der indeholdt disse detaljer.

Jeg havde brug for at skrive kode, der gjorde et par ting:

  • Samlede links fra den første celle i tabellen.
  • Oprettet et nyt Nokogiri-objekt ud fra HTML på den side.
  • Pluk breddegrad og længdegrad ud.
  • Sov programmet, indtil processen er afsluttet.
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

Et par ekstra ting er værd at påpege her:

  • Jeg bruger “got_coords” som en simpel binær. Dette er som standard sat til falsk og skiftes, når dataene fanges ELLER simpelthen ikke er tilgængelige.
  • Breddegrad og længdegrad er placeret i spændvidder med tilsvarende klasser. Det gør fastgørelse af data enkel: .css (span.latitude ') Dette efterfølges af .text, .strip og .to_f som 1) får teksten fra span, 2) strimler eventuelt overskydende mellemrum, og 3) omsætter streng til et float-nummer.

JSON → Google Map

Det nyoprettede JSON-objekt skal ændres et strejf for at passe til Google Maps API. Jeg gjorde dette med JavaScript inde i map.js

JSON-dataene er tilgængelige i map.js, fordi de er flyttet til JS-mappen, tildelt en variabel kaldet "bridge_data" og inkluderet i et tag i index.html.

Okay! Vi konverterer nu JSON-filen (tildelt variablen bridge_data) til et nyt array, der kan bruges af Google Maps.

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;});

Jeg bruger .map til at oprette et nyt dimensionelt array kaldet "locations". Hver post har info, som vises i vores Google Maps-popup, hvis brugeren klikker på den pin på kortet. Vi inkluderer også breddegrad, længdegrad og unikt bro-ID.

Resultatet er et Google Map, der plotter en række placeringer med inforige pop op-vinduer til hver bro!

Hjalp dette dig? Giv det et par klapper, og følg!