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

Ruby, JSON og Nokogiri

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

For nylig arbejdede jeg på et projekt til kortlægning af data om broer. Ved hjælp af Nokogiri var jeg i stand til at fange en bys brodata fra en tabel. Derefter brugte jeg links i 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-kortdemo her.

Projektet

Mit mål var at tage et bord fra et brodata-websted og forvandle det til et Google-kort med geolokerede stifter, der ville producere informative popups for hver bro.

Ideen: HTML-tabel til kort

For at få dette til, skal jeg:

  1. Skrap data fra det originale websted.
  2. Konverter disse data til et JSON-objekt.
  3. Anvend disse data til at lave et nyt, interaktivt kort.

Dit projekt vil helt sikkert variere - hvor mange mennesker forsøger at kortlægge antikke broer? - men jeg håber, at denne proces viser sig at være nyttig for din kontekst.

Nokogiri

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

Skraberen

Hvis du følger GibHub-repoen, kan du finde min skraber i bridges_scraper.rb

kræver 'åben-uri'
kræver 'nokogiri'
kræver 'json'

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

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

url = 'https://bridgereports.com/city/wichita-kansas/'
html = åben (url)
doc = Nokogiri :: HTML (html)
broer = []
tabel = doc.at ('tabel')
table.search ('tr'). hver gør | tr |
  bridges.push (
    bærer: celler [1] .text,
    krydser: celler [2] .text,
    placering: celler [3] .text,
    design: celler [4] .text,
    status: celler [5] .text,
    year_build: celler [6] .text.to_i,
    year_recon: celler [7] .text,
    span_length: celler [8] .text.to_f,
    total_length: celler [9] .text.to_f,
    betingelse: celler [10] .text,
    suff_rating: celler [11] .text.to_f,
    id: celler [12] .text.to_i
  )
ende
json = JSON.pretty_generate (broer)
File.open ("data.json", 'w') {| fil | file.write (json)}

Nokogiri har masser af metoder (her er et snyderi og en startguide!). Vi bruger kun nogle få.

Tabellen findes med .at (‘tabel’), der returnerer den første forekomst af et tabelelement i DOM. Dette fungerer helt fint til denne relativt enkle side.

Med tabellen i hånden giver .search ('tr') en matrix af rækkeelementerne, som vi itererer med .each. I hver række renses dataene og skubbes ind i en enkelt post for broarrayet.

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 oplysninger fra andre tilknyttede sider. Specifikt havde jeg brug for bredden og længdegraden for hver bro, som ikke var med på bordet. Dog fandt jeg, at linket i den første celle i hver række førte til en side, der leverede disse detaljer.

Jeg var nødt til at skrive kode, der gjorde et par ting:

  • Samlede links fra den første celle i tabellen.
  • Oprettet et nyt Nokogiri-objekt fra HTML på den side.
  • Træk breddegrad og længdegrad ud.
  • Sleep programmet, indtil denne proces er afsluttet.
celler = tr.search ('th, td')
  links = {}
  celler [0] .css ('a'). hver gør | a |
    links [a.text] = a ['href']
  ende
  
  got_coords = falsk
  
  hvis der henvises til ['NBI-rapport']
    nbi = links ['NBI-rapport']
    rapport = "https://bridgereports.com" + nbi
    report_html = åben (rapport)
    sove 1 indtil rapport_html
    r = Nokogiri :: HTML (rapport_html)
    
    lat = r.css ('span.latitude'). text.strip.to_f
    lang = r.css ('span.longitude'). text.strip.to_f
    got_coords = sandt
  andet
    got_coords = sandt
  ende
  
  sove 1 indtil got_coords == sandt
  bridges.push (
    links: links,
    breddegrad: lat,
    længdegrad: lang,
    bærer: celler [1] .text,
    ..., # alle andre tidligere nøgle / værdipar
  )
ende

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

  • Jeg bruger "got_coords" som en enkel binær. Dette er som standard indstillet til usande og skiftes, når dataene indfanges ELLER simpelthen ikke er tilgængelige.
  • Breddegrad og længdegrad er placeret i spænd med tilsvarende klasser. Det gør sikring af dataene enkle: .css ('span.latitude') Dette efterfølges af .text, .strip og .to_f, som 1) får teksten fra spændvidden, 2) striber alt overskydende hvidrum, og 3) konverterer streng til et floatnummer.

JSON → Google Map

Det nyoprettede JSON-objekt skal ændres, så det passer til Google Maps API. Det gjorde jeg med JavaScript inde i map.js

JSON-dataene er tilgængelige inden for map.js, fordi de er flyttet til JS-mappen, tildelt en variabel kaldet “bridge_data” og inkluderet i et