nsastorage 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/nsastorage/address.rb +10 -6
- data/lib/nsastorage/crawler.rb +14 -0
- data/lib/nsastorage/dimensions.rb +4 -3
- data/lib/nsastorage/facility.rb +21 -14
- data/lib/nsastorage/features.rb +8 -23
- data/lib/nsastorage/geocode.rb +9 -6
- data/lib/nsastorage/price.rb +13 -2
- data/lib/nsastorage/rates.rb +2 -2
- data/lib/nsastorage/sitemap.rb +1 -1
- data/lib/nsastorage/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '027882423797fb41dbf5b61cdf99e94656445b0028a8b9b71aed0f372b927a65'
|
4
|
+
data.tar.gz: c7b6123557b33879dce6088866076feb600edfca9e689e1e1d4ec623a15f504c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a90cecc900f1b755674878cadd0eaec6cf635407df8ccebfac8110886c55e1912f9e70e8ae18da6dab7e9725dd2f49be5b54d0eb5e71eb398273bbe767740015
|
7
|
+
data.tar.gz: 07a8772d62aa98fc6802201fb98ac85f369b00dcc2164e20ae4a8eaa5c44d80199781afaaf14ca0584edf0bab1de9e8d278dc691a258e3aab9ea805d3758ecfc
|
data/lib/nsastorage/address.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
module NSAStorage
|
4
4
|
# The address (street + city + state + zip) of a facility.
|
5
5
|
class Address
|
6
|
+
ADDRESS_SELECTOR = '.item-des-box .text-box i'
|
7
|
+
ADDRESS_REGEX = /(?<street>.+),\s+(?<city>.+),\s+(?<state>.+)\s+(?<zip>\d{5})/
|
6
8
|
# @attribute [rw] street
|
7
9
|
# @return [String]
|
8
10
|
attr_accessor :street
|
@@ -46,15 +48,17 @@ module NSAStorage
|
|
46
48
|
"#{street}, #{city}, #{state} #{zip}"
|
47
49
|
end
|
48
50
|
|
49
|
-
# @param
|
51
|
+
# @param document [Nokogiri::HTML::Document]
|
50
52
|
#
|
51
53
|
# @return [Address]
|
52
|
-
def self.parse(
|
54
|
+
def self.parse(document:)
|
55
|
+
element = document.at_css(ADDRESS_SELECTOR)
|
56
|
+
match = element.text.match(ADDRESS_REGEX)
|
53
57
|
new(
|
54
|
-
street:
|
55
|
-
city:
|
56
|
-
state:
|
57
|
-
zip:
|
58
|
+
street: match[:street],
|
59
|
+
city: match[:city],
|
60
|
+
state: match[:state],
|
61
|
+
zip: match[:zip]
|
58
62
|
)
|
59
63
|
end
|
60
64
|
end
|
data/lib/nsastorage/crawler.rb
CHANGED
@@ -5,6 +5,13 @@ module NSAStorage
|
|
5
5
|
class Crawler
|
6
6
|
HOST = 'https://www.nsastorage.com'
|
7
7
|
|
8
|
+
# @attribute url [String]
|
9
|
+
# @raise [FetchError]
|
10
|
+
# @return [Hash]
|
11
|
+
def self.json(url:)
|
12
|
+
new.json(url:)
|
13
|
+
end
|
14
|
+
|
8
15
|
# @param url [String]
|
9
16
|
# @raise [FetchError]
|
10
17
|
# @return [Nokogiri::HTML::Document]
|
@@ -42,6 +49,13 @@ module NSAStorage
|
|
42
49
|
response
|
43
50
|
end
|
44
51
|
|
52
|
+
# @param url [String]
|
53
|
+
# @raise [FetchError]
|
54
|
+
# @return [Hash]
|
55
|
+
def json(url:)
|
56
|
+
JSON.parse(String(fetch(url:).body))
|
57
|
+
end
|
58
|
+
|
45
59
|
# @param url [String]
|
46
60
|
# @raise [FetchError]
|
47
61
|
# @return [Nokogiri::XML::Document]
|
@@ -5,6 +5,8 @@ module NSAStorage
|
|
5
5
|
class Dimensions
|
6
6
|
DEFAULT_HEIGHT = 8.0 # feet
|
7
7
|
|
8
|
+
DIMENSIONS_REGEX = /(?<width>[\d\.]+) x (?<depth>[\d\.]+)/
|
9
|
+
|
8
10
|
# @attribute [rw] depth
|
9
11
|
# @return [Float]
|
10
12
|
attr_accessor :depth
|
@@ -55,9 +57,8 @@ module NSAStorage
|
|
55
57
|
#
|
56
58
|
# @return [Dimensions]
|
57
59
|
def self.parse(element:)
|
58
|
-
text = element.text
|
59
|
-
match =
|
60
|
-
raise text.inspect if match.nil?
|
60
|
+
text = element.at_css('.unit-select-item-detail').text
|
61
|
+
match = DIMENSIONS_REGEX.match(text)
|
61
62
|
|
62
63
|
width = Float(match[:width])
|
63
64
|
depth = Float(match[:depth])
|
data/lib/nsastorage/facility.rb
CHANGED
@@ -7,13 +7,11 @@ module NSAStorage
|
|
7
7
|
class Facility
|
8
8
|
class ParseError < StandardError; end
|
9
9
|
|
10
|
-
DEFAULT_EMAIL = '
|
11
|
-
DEFAULT_PHONE = '
|
10
|
+
DEFAULT_EMAIL = 'customerservice@nsabrands.com'
|
11
|
+
DEFAULT_PHONE = '+1-844-434-1150'
|
12
12
|
|
13
13
|
SITEMAP_URL = 'https://www.nsastorage.com/sitemap.xml'
|
14
14
|
|
15
|
-
ID_REGEX = %r{/(?<id>\d+)}
|
16
|
-
|
17
15
|
# @attribute [rw] id
|
18
16
|
# @return [String]
|
19
17
|
attr_accessor :id
|
@@ -64,13 +62,14 @@ module NSAStorage
|
|
64
62
|
#
|
65
63
|
# @return [Facility]
|
66
64
|
def self.parse(url:, document:)
|
67
|
-
|
68
|
-
|
65
|
+
id = Integer(document.at_css('[data-facility-id]')['data-facility-id'])
|
66
|
+
name = document.at_css('.section-title').text.strip
|
69
67
|
|
70
|
-
|
71
|
-
|
68
|
+
geocode = Geocode.parse(document:)
|
69
|
+
address = Address.parse(document:)
|
70
|
+
prices = Price.fetch(facility_id: id)
|
72
71
|
|
73
|
-
new(id:, url:, name
|
72
|
+
new(id:, url:, name:, address:, geocode:, prices:)
|
74
73
|
end
|
75
74
|
|
76
75
|
# @param document [Nokogiri::HTML::Document]
|
@@ -78,11 +77,19 @@ module NSAStorage
|
|
78
77
|
# @raise [ParseError]
|
79
78
|
#
|
80
79
|
# @return [Hash]
|
81
|
-
def self.
|
82
|
-
document
|
83
|
-
|
84
|
-
|
85
|
-
|
80
|
+
def self.parse_ld_json_script(document:)
|
81
|
+
parse_ld_json_scripts(document:).find do |data|
|
82
|
+
data['@type'] == 'SelfStorage'
|
83
|
+
end || raise(ParseError, 'missing ld+json')
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param document [Nokogiri::HTML::Document]
|
87
|
+
#
|
88
|
+
# @return [Array<Hash>]
|
89
|
+
def self.parse_ld_json_scripts(document:)
|
90
|
+
elements = document.xpath('//script[@type="application/ld+json"]')
|
91
|
+
|
92
|
+
elements.map { |element| element.text.empty? ? {} : JSON.parse(element.text) }
|
86
93
|
end
|
87
94
|
|
88
95
|
# @param id [String]
|
data/lib/nsastorage/features.rb
CHANGED
@@ -11,20 +11,17 @@ module NSAStorage
|
|
11
11
|
|
12
12
|
new(
|
13
13
|
climate_controlled: text.include?('Climate controlled'),
|
14
|
-
|
15
|
-
|
16
|
-
first_floor_access: text.include?('1st floor access')
|
14
|
+
drive_up_access: text.include?('Drive Up Access'),
|
15
|
+
first_floor_access: text.include?('1st Floor')
|
17
16
|
)
|
18
17
|
end
|
19
18
|
|
20
19
|
# @param climate_controlled [Boolean]
|
21
|
-
# @param
|
22
|
-
# @param outside_drive_up_access [Boolean]
|
20
|
+
# @param drive_up_access [Boolean]
|
23
21
|
# @param first_floor_access [Boolean]
|
24
|
-
def initialize(climate_controlled:,
|
22
|
+
def initialize(climate_controlled:, drive_up_access:, first_floor_access:)
|
25
23
|
@climate_controlled = climate_controlled
|
26
|
-
@
|
27
|
-
@outside_drive_up_access = outside_drive_up_access
|
24
|
+
@drive_up_access = drive_up_access
|
28
25
|
@first_floor_access = first_floor_access
|
29
26
|
end
|
30
27
|
|
@@ -32,8 +29,7 @@ module NSAStorage
|
|
32
29
|
def inspect
|
33
30
|
props = [
|
34
31
|
"climate_controlled=#{@climate_controlled}",
|
35
|
-
"
|
36
|
-
"outside_drive_up_access=#{@outside_drive_up_access}",
|
32
|
+
"drive_up_access=#{@drive_up_access}",
|
37
33
|
"first_floor_access=#{@first_floor_access}"
|
38
34
|
]
|
39
35
|
|
@@ -49,8 +45,7 @@ module NSAStorage
|
|
49
45
|
def amenities
|
50
46
|
[].tap do |amenities|
|
51
47
|
amenities << 'Climate Controlled' if climate_controlled?
|
52
|
-
amenities << '
|
53
|
-
amenities << 'Outside Drive-Up Access' if outside_drive_up_access?
|
48
|
+
amenities << 'Drive-Up Access' if drive_up_access?
|
54
49
|
amenities << 'First Floor Access' if first_floor_access?
|
55
50
|
end
|
56
51
|
end
|
@@ -60,19 +55,9 @@ module NSAStorage
|
|
60
55
|
@climate_controlled
|
61
56
|
end
|
62
57
|
|
63
|
-
# @return [Boolean]
|
64
|
-
def inside_drive_up_access?
|
65
|
-
@inside_drive_up_access
|
66
|
-
end
|
67
|
-
|
68
|
-
# @return [Boolean]
|
69
|
-
def outside_drive_up_access?
|
70
|
-
@outside_drive_up_access
|
71
|
-
end
|
72
|
-
|
73
58
|
# @return [Boolean]
|
74
59
|
def drive_up_access?
|
75
|
-
|
60
|
+
@drive_up_access
|
76
61
|
end
|
77
62
|
|
78
63
|
# @return [Boolean]
|
data/lib/nsastorage/geocode.rb
CHANGED
@@ -3,6 +3,9 @@
|
|
3
3
|
module NSAStorage
|
4
4
|
# The geocode (latitude + longitude) of a facility.
|
5
5
|
class Geocode
|
6
|
+
LATITUDE_REGEX = /\\u0022lat\\u0022:(?<latitude>[\+\-\d\.]+)/
|
7
|
+
LONGITUDE_REGEX = /\\u0022long\\u0022:(?<longitude>[\+\-\d\.]+)/
|
8
|
+
|
6
9
|
# @attribute [rw] latitude
|
7
10
|
# @return [Float]
|
8
11
|
attr_accessor :latitude
|
@@ -11,14 +14,14 @@ module NSAStorage
|
|
11
14
|
# @return [Float]
|
12
15
|
attr_accessor :longitude
|
13
16
|
|
14
|
-
# @param
|
17
|
+
# @param document [Nokogiri::HTML::Document]
|
15
18
|
#
|
16
19
|
# @return [Geocode]
|
17
|
-
def self.parse(
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
)
|
20
|
+
def self.parse(document:)
|
21
|
+
latitude = LATITUDE_REGEX.match(document.text)[:latitude]
|
22
|
+
longitude = LONGITUDE_REGEX.match(document.text)[:longitude]
|
23
|
+
|
24
|
+
new(latitude: Float(latitude), longitude: Float(longitude))
|
22
25
|
end
|
23
26
|
|
24
27
|
# @param latitude [Float]
|
data/lib/nsastorage/price.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module NSAStorage
|
4
|
-
# The price (id + dimensions + rate) for a facility
|
4
|
+
# The price (id + dimensions + rate) for a facility.
|
5
5
|
class Price
|
6
|
+
ID_REGEX = %r{(?<id>\d+)/rent/}
|
6
7
|
# @attribute [rw] id
|
7
8
|
# @return [String]
|
8
9
|
attr_accessor :id
|
@@ -19,6 +20,15 @@ module NSAStorage
|
|
19
20
|
# @return [Rates]
|
20
21
|
attr_accessor :rates
|
21
22
|
|
23
|
+
# @param facility_id [Integer]
|
24
|
+
#
|
25
|
+
# @return [Array<Price>]
|
26
|
+
def self.fetch(facility_id:)
|
27
|
+
url = "https://www.nsastorage.com/facility-units/#{facility_id}"
|
28
|
+
html = Crawler.json(url:)['data']['html']['units']
|
29
|
+
Nokogiri::HTML(html).css('[data-unit-size]').map { |element| parse(element:) }
|
30
|
+
end
|
31
|
+
|
22
32
|
# @param id [String]
|
23
33
|
# @param dimensions [Dimensions]
|
24
34
|
# @param features [Features]
|
@@ -50,8 +60,9 @@ module NSAStorage
|
|
50
60
|
#
|
51
61
|
# @return [Price]
|
52
62
|
def self.parse(element:)
|
63
|
+
link = element.at_xpath("//a[contains(text(), 'Rent')]|//a[contains(text(), 'Reserve')]")
|
53
64
|
new(
|
54
|
-
id:
|
65
|
+
id: ID_REGEX.match(link['href'])[:id],
|
55
66
|
dimensions: Dimensions.parse(element:),
|
56
67
|
features: Features.parse(element:),
|
57
68
|
rates: Rates.parse(element:)
|
data/lib/nsastorage/rates.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
module NSAStorage
|
4
4
|
# The rates (street + web) for a facility
|
5
5
|
class Rates
|
6
|
-
STREET_SELECTOR = '.
|
7
|
-
WEB_SELECTOR = '.
|
6
|
+
STREET_SELECTOR = '.part_item_old_price'
|
7
|
+
WEB_SELECTOR = '.part_item_price'
|
8
8
|
VALUE_REGEX = /(?<value>[\d\.]+)/
|
9
9
|
|
10
10
|
# @attribute [rw] street
|
data/lib/nsastorage/sitemap.rb
CHANGED
data/lib/nsastorage/version.rb
CHANGED
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nsastorage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Sylvestre
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
11
|
date: 2024-12-11 00:00:00.000000000 Z
|
@@ -117,7 +117,7 @@ metadata:
|
|
117
117
|
homepage_uri: https://github.com/ksylvest/nsastorage
|
118
118
|
source_code_uri: https://github.com/ksylvest/nsastorage
|
119
119
|
changelog_uri: https://github.com/ksylvest/nsastorage
|
120
|
-
post_install_message:
|
120
|
+
post_install_message:
|
121
121
|
rdoc_options: []
|
122
122
|
require_paths:
|
123
123
|
- lib
|
@@ -132,8 +132,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
132
|
- !ruby/object:Gem::Version
|
133
133
|
version: '0'
|
134
134
|
requirements: []
|
135
|
-
rubygems_version: 3.5.
|
136
|
-
signing_key:
|
135
|
+
rubygems_version: 3.5.23
|
136
|
+
signing_key:
|
137
137
|
specification_version: 4
|
138
138
|
summary: A crawler for NSAStorage.
|
139
139
|
test_files: []
|