Як скребнути за допомогою Рубі та Нокогірі та зіставити дані

Іноді вам хочеться отримати дані з веб-сайту для власного проекту. То що ти використовуєш? Рубі, Нокогірі та JSON на допомогу!

Нещодавно я працював над проектом зіставлення даних про мости. Використовуючи Нокогірі, я зміг зібрати дані таблиці міст міста. Потім я використав посилання в тій самій таблиці, щоб зішкребти пов'язані сторінки. Нарешті, я перетворив скребкові дані в JSON і використовував їх для заповнення карти Google.

Ця стаття розповість вам про інструменти, якими я користувався, і про те, як працює код!

Повний код читайте в моєму репозиторії GitHub.

Демонстрація живої карти тут.

Проект

Моєю метою було взяти таблицю з веб-сайту даних мостів і перетворити її на карту Google з геолокаційними штифтами, яка дасть інформаційні спливаючі вікна для кожного мосту.

Щоб це сталося, мені потрібно:

  1. Видаліть дані з оригінального веб-сайту.
  2. Перетворіть ці дані в об’єкт JSON.
  3. Застосуйте ці дані, щоб створити нову інтерактивну карту.

Ваш проект, безумовно, відрізнятиметься - скільки людей намагається нанести на карту античні мости? - але я сподіваюся, що цей процес стане корисним для вашого контексту.

Нокогірі

У Рубі є дивовижна самоцвіт, що вискаблює веб, під назвою Нокогірі. Серед інших можливостей це дозволяє шукати документи HTML за допомогою селекторів CSS. Це означає, що якщо ми знаємо ідентифікатори, класи або навіть типи елементів, де дані зберігаються в DOM, ми можемо їх вирвати.

Скребок

Якщо ви стежите за репозитарієм GibHub, ви можете знайти мій скрепер у bridges_scraper.rb

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

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

У наведеному нижче коді я передаю інформацію про DOM з URL-адреси з мостом даних до Нокогірі. Потім я знаходжу елемент таблиці, що містить дані, шукаю його рядки і переглядаю їх.

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) 

У Нокогірі є безліч методів (ось шпаргалка та посібник для початківців!). Ми використовуємо лише декілька.

У таблиці знайдено .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.

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 для створення нового розмірного масиву під назвою “location”. Кожен запис містить інформацію, яка з’явиться у нашому спливаючому вікні Карт Google, якщо користувач натисне на цей штифт на карті. Ми також включаємо широту, довготу та унікальний ідентифікатор мосту.

Результат - карта Google, яка наносить масив місць з інформаційними спливаючими вікнами для кожного мосту!

Вам це допомогло? Дайте йому кілька хлопань і слідуйте!