Cómo raspar con Ruby y Nokogiri y mapear los datos

A veces, desea obtener datos de un sitio web para su propio proyecto. Entonces, ¿qué usas? ¡Ruby, Nokogiri y JSON al rescate!

Recientemente, estaba trabajando en un proyecto para mapear datos sobre puentes. Usando Nokogiri, pude capturar los datos del puente de una ciudad desde una tabla. Luego usé enlaces dentro de esa misma tabla para raspar las páginas asociadas. Finalmente, convertí los datos raspados a JSON y los usé para completar un mapa de Google.

¡Este artículo lo guía a través de las herramientas que utilicé y cómo funciona el código!

Vea el código completo en mi repositorio de GitHub.

Demo de mapa en vivo aquí.

El proyecto

Mi objetivo era tomar una tabla de un sitio web de datos de puentes y convertirla en un mapa de Google con pines geolocalizados que producirían ventanas emergentes informativas para cada puente.

Para que esto suceda, necesitaría:

  1. Extraiga datos del sitio web original.
  2. Convierta esos datos en un objeto JSON.
  3. Aplica esos datos para crear un mapa nuevo e interactivo.

Su proyecto variará, seguramente: ¿cuántas personas están tratando de mapear puentes antiguos? - pero espero que este proceso resulte útil para su contexto.

Nokogiri

Ruby tiene una increíble joya de raspado web llamada Nokogiri. Entre otras características, le permite buscar documentos HTML mediante selectores CSS. Eso significa que si conocemos los identificadores, las clases o incluso los tipos de elementos donde se almacenan los datos en el DOM, podemos extraerlos.

El raspador

Si está siguiendo el repositorio de GibHub, puede encontrar mi raspador en bridges_scraper.rb

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

Open-uri nos permite abrir el HTML como un archivo y pasárselo a Nokogiri para el trabajo pesado.

En el siguiente código, paso la información DOM de la URL con los datos del puente a Nokogiri. Luego encuentro el elemento de la tabla que contiene los datos, busco sus filas y las repito.

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 tiene muchos métodos (¡aquí hay una hoja de trucos y una guía de inicio!). Estamos usando solo unos pocos.

La tabla se encuentra con .at ('tabla') , que devuelve la primera aparición de un elemento de tabla en el DOM. Esto funciona bien para esta página relativamente simple.

Con la tabla en la mano, .search ('tr') proporciona una matriz de los elementos de fila sobre los que iteramos con .each . En cada fila, los datos se limpian y se insertan en una sola entrada para la matriz de puentes.

Una vez recopiladas todas las filas, los datos se convierten en JSON y se guardan en un nuevo archivo llamado "data.json".

Combinar datos de varias páginas

En este caso, necesitaba información de otras páginas asociadas. Específicamente, necesitaba la latitud y la longitud de cada puente, que no figuraba en la tabla. Sin embargo, he encontrado que el enlace en la primera celda de cada fila llegaba a una página que tenía proporcionar esos detalles.

Necesitaba escribir código que hiciera algunas cosas:

  • Enlaces recopilados de la primera celda de la tabla.
  • Creó un nuevo objeto Nokogiri a partir del HTML de esa página.
  • Extrae la latitud y la longitud.
  • Duerma el programa hasta que se complete ese proceso.
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

Vale la pena señalar algunas cosas adicionales aquí:

  • Estoy usando "got_coords" como un binario simple. Esto se establece en falso de forma predeterminada y se alterna cuando los datos se capturan O simplemente no están disponibles.
  • La latitud y la longitud se ubican en tramos con las clases correspondientes. Eso hace que asegurar los datos sea simple: .css ('span.latitude') Esto es seguido por .text, .strip y .to_f que 1) obtiene el texto del intervalo, 2) elimina cualquier espacio en blanco sobrante y 3) convierte el cadena a un número flotante.

JSON → Mapa de Google

El objeto JSON recién formado debe modificarse un poco para que se ajuste a la API de Google Maps. Hice esto con JavaScript dentro de map.js

Se puede acceder a los datos JSON dentro de map.js porque se movieron a la carpeta JS, se asignaron a una variable llamada "bridge_data" y se incluyeron en una etiqueta en index.html.

¡Todo bien! Ahora convertiremos el archivo JSON (asignado a la variable bridge_data) en una nueva matriz que puede utilizar 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;});

Estoy usando .map para crear una nueva matriz dimensional llamada "ubicaciones". Cada entrada tiene información, que aparecerá en nuestra ventana emergente de Google Maps si el usuario hace clic en ese pin en el mapa. También incluimos la latitud, la longitud y la identificación única del puente.

El resultado es un mapa de Google que traza la variedad de ubicaciones con ventanas emergentes ricas en información para cada puente.

¿Esto te ayudó? ¡Dale unas palmadas y sigue!