Как да изстъргвате с Ruby и Nokogiri и да картографирате данните

Понякога искате да вземете данни от уебсайт за вашия собствен проект. И така, какво използвате? Руби, Нокогири и JSON на помощ!

Наскоро работех по проект за картографиране на данни за мостове. Използвайки Nokogiri, успях да уловя данни от моста на града от таблица. След това използвах връзки в същата таблица за изстъргване на свързани страници. Накрая преобразувах изтритите данни в JSON и ги използвах за попълване на Google Map.

Тази статия ще ви преведе през инструментите, които използвах и как работи кодът!

Вижте пълния код на моето репо GitHub.

Демо карта на живо тук.

Проектът

Целта ми беше да взема таблица от уебсайт за мостови данни и да я превърна в карта на Google с геолокирани щифтове, които биха създали информационни изскачащи прозорци за всеки мост.

За да се случи това, трябва да:

  1. Изтрийте данни от оригиналния уебсайт.
  2. Преобразувайте тези данни в JSON обект.
  3. Приложете тези данни, за да направите нова, интерактивна карта.

Вашият проект със сигурност ще варира - колко хора се опитват да картографират античните мостове? - но се надявам този процес да се окаже полезен за вашия контекст.

Нокогири

Руби има невероятен скъпоценен камък, наречен Nokogiri. Наред с други функции, тя ви позволява да търсите в HTML документи по CSS селектори. Това означава, че ако знаем идентификаторите, класовете или дори типовете елементи, където данните се съхраняват в DOM, можем да ги извадим.

Скреперът

Ако следите заедно с репозитория на GibHub, можете да намерите моя скрепер в bridges_scraper.rb

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

Open-uri ни позволява да отворим HTML като файл и да го предадем на Nokogiri за тежката работа.

В кода по-долу предавам информацията за DOM от URL адреса с мостовите данни на Nokogiri. След това намирам елемента на таблицата, съдържащ данните, търся редовете му и прелиствам през тях.

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 има много методи (ето мамят и начално ръководство!). Използваме само няколко.

Таблицата се намира с .at ('table') , който връща първото появяване на елемент на таблица в DOM. Това работи добре за тази сравнително проста страница.

С таблицата в ръка .search ('tr') предоставя масив от редови елементи, които итерираме с .each . Във всеки ред данните се изчистват и избутват в един запис за мостовия масив.

След като всички редове бъдат събрани, данните се преобразуват в JSON и се записват в нов файл, наречен „data.json“.

Комбиниране на данни от множество страници

В този случай ми трябваше информация от други свързани страници. По-конкретно, имах нужда от географската ширина и дължина на всеки мост, които не бяха представени на масата. Установих обаче, че връзката в първата клетка на всеки ред води до страница, която наистина предоставя тези подробности.

Трябваше да напиша код, който направи няколко неща:

  • Събрани връзки от първата клетка в таблицата.
  • Създаде нов обект Nokogiri от HTML на тази страница.
  • Извадете географската ширина и дължина.
  • Изключете програмата, докато този процес завърши.
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

Тук си струва да се отбележат няколко допълнителни неща:

  • Използвам “got_coords” като обикновен двоичен файл. По подразбиране е зададено на false и се превключва, когато данните се улавят ИЛИ просто не са налични.
  • Географската ширина и дължина са разположени в обхвати със съответните класове. Това прави защитата на данните лесна: .css ('span.latitude') Това е последвано от .text, .strip и .to_f, които 1) получават текста от интервала, 2) премахват излишното празно пространство и 3) преобразуват низ до плаващ номер.

JSON → Google Map

Новосформираният обект JSON трябва да бъде модифициран, за да отговаря на API на Google Maps. Направих това с JavaScript вътре в map.js

JSON данните са достъпни в map.js, защото са преместени в папката JS, присвоени на променлива, наречена „bridge_data“, и включени в таг в index.html.

Добре! Сега ще преобразуваме файла JSON (присвоен на променливата bridge_data) в нов масив, който може да се използва от 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;});

Използвам .map, за да създам нов измерен масив, наречен „местоположения“. Всеки запис има информация, която ще се появи в изскачащия прозорец на Google Maps, ако потребителят щракне върху този щифт на картата. Включваме също географската ширина, дължина и уникалния идентификатор на моста.

Резултатът е Google Map, който нанася масива от местоположения с богати на информация изскачащи прозорци за всеки мост!

Това помогна ли ви? Дайте няколко пляскания и следвайте!