Comment gratter avec Ruby et Nokogiri et cartographier les données

Parfois, vous souhaitez récupérer des données sur un site Web pour votre propre projet. Alors qu'est-ce que vous utilisez? Ruby, Nokogiri et JSON à la rescousse!

Récemment, je travaillais sur un projet de cartographie des données sur les ponts. En utilisant Nokogiri, j'ai pu capturer les données du pont d'une ville à partir d'une table. J'ai ensuite utilisé des liens dans ce même tableau pour gratter les pages associées. Enfin, j'ai converti les données récupérées en JSON et je les ai utilisées pour remplir une carte Google.

Cet article vous explique les outils que j'ai utilisés et le fonctionnement du code!

Voir le code complet sur mon dépôt GitHub.

Démo de la carte en direct ici.

Le projet

Mon objectif était de prendre une table d'un site Web de données de pont et de la transformer en une carte Google avec des broches géolocalisées qui produiraient des fenêtres contextuelles informatives pour chaque pont.

Pour y arriver, je devrais:

  1. Grattez les données du site Web d'origine.
  2. Convertissez ces données en un objet JSON.
  3. Appliquez ces données pour créer une nouvelle carte interactive.

Votre projet variera sûrement - combien de personnes essaient de cartographier des ponts antiques? - mais j'espère que ce processus s'avérera utile pour votre contexte.

Nokogiri

Ruby a un incroyable joyau de grattage Web appelé Nokogiri. Entre autres fonctionnalités, il vous permet de rechercher des documents HTML à l'aide de sélecteurs CSS. Cela signifie que si nous connaissons les identifiants, les classes ou même les types d'éléments où les données sont stockées dans le DOM, nous pouvons les extraire.

Le grattoir

Si vous suivez le repo GibHub, vous pouvez trouver mon grattoir dans bridges_scraper.rb

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

Open-uri nous permet d'ouvrir le HTML comme un fichier et de le transmettre à Nokogiri pour le gros du travail.

Dans le code ci-dessous, je transmets les informations DOM de l'URL avec les données du pont à Nokogiri. Je trouve ensuite l'élément de table contenant les données, je recherche ses lignes et je les parcours.

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 a beaucoup de méthodes (voici une feuille de triche et un guide de démarrage!). Nous n'en utilisons que quelques-uns.

La table se trouve avec .at ('table') , qui renvoie la première occurrence d'un élément de table dans le DOM. Cela fonctionne très bien pour cette page relativement simple.

Avec le tableau en main, .search ('tr') fournit un tableau des éléments de ligne que nous parcourons avec .each . Dans chaque ligne, les données sont nettoyées et poussées dans une seule entrée pour le tableau des ponts.

Une fois toutes les lignes collectées, les données sont converties en JSON et enregistrées dans un nouveau fichier appelé «data.json».

Combinaison de données de plusieurs pages

Dans ce cas, j'avais besoin d'informations provenant d'autres pages associées. Plus précisément, j'avais besoin de la latitude et de la longitude de chaque pont, qui ne figuraient pas sur la table. Cependant, je trouve que le lien dans la première cellule de chaque ligne a conduit à une page qui ne fournit ces détails.

J'avais besoin d'écrire du code qui faisait plusieurs choses:

  • Liens rassemblés à partir de la première cellule du tableau.
  • Création d'un nouvel objet Nokogiri à partir du HTML de cette page.
  • Arrachez la latitude et la longitude.
  • Mettez le programme en veille jusqu'à ce que ce processus soit terminé.
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

Quelques points supplémentaires méritent d'être soulignés ici:

  • J'utilise "got_coords" comme un simple binaire. Ce paramètre est défini sur false par défaut et est basculé lorsque les données sont capturées OU simplement indisponibles.
  • La latitude et la longitude sont situées dans des travées avec les classes correspondantes. Cela simplifie la sécurisation des données: .css ('span.latitude') Ceci est suivi de .text, .strip et .to_f qui 1) récupère le texte de la plage, 2) supprime tout espace blanc excédentaire et 3) convertit le chaîne en un nombre flottant.

JSON → Google Map

L'objet JSON nouvellement formé doit être modifié d'une touche pour s'adapter à l'API Google Maps. Je l'ai fait avec JavaScript dans map.js

Les données JSON sont accessibles dans map.js car elles ont été déplacées vers le dossier JS, affectées à une variable appelée «bridge_data» et incluses dans une balise dans index.html.

D'accord! Nous allons maintenant convertir le fichier JSON (attribué à la variable bridge_data) en un nouveau tableau utilisable par 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;});

J'utilise .map pour créer un nouveau tableau dimensionnel appelé «emplacements». Chaque entrée contient des informations qui apparaîtront dans notre popup Google Maps si l'utilisateur clique sur cette épingle sur la carte. Nous incluons également la latitude, la longitude et l'identifiant unique du pont.

Le résultat est une carte Google qui trace le tableau des emplacements avec des popups riches en informations pour chaque pont!

Cela vous a-t-il aidé? Donnez-lui quelques applaudissements et suivez!