require 'httparty' require 'nokogiri' require 'date' class NokogiriParser < HTTParty::Parser def html Nokogiri::HTML(body) end end class HVPage include HTTParty parser NokogiriParser base_uri "http://www.holidayvalley.com" end page = HVPage.get("/HolidayValley/snowreport.aspx") class InvalidData < Exception end module Transforms def self.parse_range(value) value.split("-").map(&:to_i) end def self.to_int(value) value.to_i end def self.is_true(value) value.downcase == "yes" end def self.parse_date(value) Date.strptime(value, "%m/%d/%Y").strftime("%a, %d %b %Y %H:%M:%S %Z") end def self.parse_date_time(value) DateTime.parse(value).strftime("%a, %d %b %Y %H:%M:%S %Z") end def self.parse_open(value) value.downcase == "open" end def self.parse_groomed(value) value.downcase == "groomed" end def self.parse_snowmaking(value) value.downcase == "new" end def self.parse_difficulty(url) if /green\.gif/ =~ url "Easier" elsif /blue\.gif/ =~ url "Intermediate" elsif /black\.gif/ =~ url "Advanced" elsif /doubleBlack\.gif/ =~ url "Expert" elsif /freestyle\.gif/ =~ url "Freestyle" end end def self.parse_lift_name(value) data = /([^(]+)\(([^']*)'\)/.match(value) [data[1].strip, data[2].to_i] end end MTN_REPORT_XPATH = { last_updated: ["//table[1]/tr[1]/td[2]/text()", :last, Transforms.method(:parse_date_time)], report_for: ["//table[1]/tr[2]/td[2]/text()", 1, Transforms.method(:parse_date)], snowfall_24hr: ["//table[1]/tr[3]/td[2]/text()", :first, Transforms.method(:to_int)], snowfall_48hr: ["//table[1]/tr[4]/td[2]/text()", :first, Transforms.method(:to_int)], snowfall_7day: ["//table[1]/tr[5]/td[2]/text()", :first, Transforms.method(:to_int)], snowfall_season: ["//table[1]/tr[6]/td[2]/text()", :first, Transforms.method(:to_int)], base_depth: ["//table[1]/tr[7]/td[2]/text()", :first, Transforms.method(:parse_range)], snowmaking_current: ["//table[1]/tr[8]/td[2]/text()", :first, Transforms.method(:is_true)], snowmaking_24hours: ["//table[1]/tr[9]/td[2]/text()", :first, Transforms.method(:is_true)], primary_surface: ["//table[1]/tr[10]/td[2]/text()", :first, String.method(:new)], secondary_surface: ["//table[1]/tr[11]/td[2]/text()", :first, String.method(:new)], } def get_transformed_value(page, xpath, finder, transform) data = page.xpath(xpath) data = finder.is_a?(Symbol) ? data.send(finder) : data[finder] transform.call(data.text) end def get_trail_info(page, row) data = page.xpath("//table[2]/tr[#{row}]/child::*").map(&:text) difficulty_info = page.xpath("//table[2]/tr[#{row}]/td/img/@src").to_s if data.size != 6 raise InvalidData.new("Invalid data in row") end { trail_name: data[0], difficulty: Transforms.parse_difficulty(difficulty_info), open_day: Transforms.parse_open(data[2]), open_night: Transforms.parse_open(data[3]), groomed: Transforms.parse_groomed(data[4]), snow_making: Transforms.parse_snowmaking(data[5]), } end def get_lift_info(page, row) data = page.xpath("//table[3]/tr[#{row}]/child::*").map(&:text) if data.size != 4 raise InvalidData.new("Invalid data in row") end lift_name, vertical = Transforms.parse_lift_name(data[0]) { lift_name: lift_name, vertical: vertical, open_day: Transforms.parse_open(data[1]), open_night: Transforms.parse_open(data[2]), notes: data[3], } end i = 3 while true begin puts get_trail_info(page, i) i += 1 rescue InvalidData break end end k = {} MTN_REPORT_XPATH.each do |key, value| data = get_transformed_value(page, *value) k[key] = data end puts k i = 2 while true begin puts get_lift_info(page, i) i += 1 rescue InvalidData break end end