nsastorage 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: 6bf4dc5114c043fc32d886eb98d630f24bfff9d3ccefbdd3a92b006c3cb967d4
|
4
|
+
data.tar.gz: c81742bdf7d0d35c5dc23b2584ad10d47b370a1de6e564e08233ed9db7b21538
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 801e52c62bdac13fcc9338496e47d866bb8392cf7b988b2535596963c5030bc90a5400ce7ad173d71e4a7cac61a89c0a196f0d064115431219fe5301167b9f2e
|
7
|
+
data.tar.gz: ad2da5dfc46c674e85bc310312f6e30feb65f19df3036ce0ba91ee16bd5676c30fe1e5d75241a2e0bbaf7fcd79d67034360455cd00f95ebcf3f31294feff0d94
|
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')]")
|
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.2.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: []
|