uhaul 0.1.0 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/uhaul/config.rb +4 -4
- data/lib/uhaul/crawler.rb +1 -1
- data/lib/uhaul/dimensions.rb +9 -15
- data/lib/uhaul/facility.rb +4 -3
- data/lib/uhaul/features.rb +4 -15
- data/lib/uhaul/geocode.rb +2 -2
- data/lib/uhaul/link.rb +0 -1
- data/lib/uhaul/price.rb +9 -18
- data/lib/uhaul/rates.rb +14 -33
- data/lib/uhaul/sitemap.rb +1 -1
- data/lib/uhaul/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4acc4dc138e904232902f124d9ee0c78923dc74c609c42515b97e83cdeea3fb2
|
4
|
+
data.tar.gz: 9e98885feb0367400d6a444f191b859d2e54abf646c9dda26938545f0d2bf8c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ecf98d2ab243fe983f6ef926c6fd425d988676173892a92990845b489561de61525ea30653d66c1b9d5956842006901064847c46aba4c5b9894c8f1a2846940
|
7
|
+
data.tar.gz: dde69a5ed918065d156872112b37b6d67b6f04a462f18f492da53ca7ecd56224ad38d1659f9e0b2bfa86f7e2f79684a0f7cdf391e1dbbece994e80dfec480e40
|
data/README.md
CHANGED
@@ -18,9 +18,9 @@ gem install uhaul
|
|
18
18
|
require 'uhaul'
|
19
19
|
|
20
20
|
UHaul.configure do |config|
|
21
|
-
config.user_agent = '../..' # ENV['
|
22
|
-
config.timeout = 30 # ENV['
|
23
|
-
config.proxy_url = 'http://user:pass@superproxy.zenrows.com:1337' # ENV['
|
21
|
+
config.user_agent = '../..' # ENV['UHAUL_USER_AGENT']
|
22
|
+
config.timeout = 30 # ENV['UHAUL_TIMEOUT']
|
23
|
+
config.proxy_url = 'http://user:pass@superproxy.zenrows.com:1337' # ENV['UHAUL_PROXY_URL']
|
24
24
|
end
|
25
25
|
```
|
26
26
|
|
data/lib/uhaul/config.rb
CHANGED
@@ -20,10 +20,10 @@ module UHaul
|
|
20
20
|
attr_accessor :proxy_url
|
21
21
|
|
22
22
|
def initialize
|
23
|
-
@accept_language = ENV.fetch('
|
24
|
-
@user_agent = ENV.fetch('
|
25
|
-
@timeout = Integer(ENV.fetch('
|
26
|
-
@proxy_url = ENV.fetch('
|
23
|
+
@accept_language = ENV.fetch('UHAUL_ACCEPT_LANGUAGE', 'en-US,en;q=0.9')
|
24
|
+
@user_agent = ENV.fetch('UHAUL_USER_AGENT', "uhaul.rb/#{VERSION}")
|
25
|
+
@timeout = Integer(ENV.fetch('UHAUL_TIMEOUT', 60))
|
26
|
+
@proxy_url = ENV.fetch('UHAUL_PROXY_URL', nil)
|
27
27
|
end
|
28
28
|
|
29
29
|
# @return [Boolean]
|
data/lib/uhaul/crawler.rb
CHANGED
data/lib/uhaul/dimensions.rb
CHANGED
@@ -3,11 +3,9 @@
|
|
3
3
|
module UHaul
|
4
4
|
# The dimensions (width + depth + sqft) of a price.
|
5
5
|
class Dimensions
|
6
|
-
|
7
|
-
DEFAULT_DEPTH = 5.0 # feet
|
8
|
-
DEFAULT_HEIGHT = 8.0 # feet
|
6
|
+
class ParseError < StandardError; end
|
9
7
|
|
10
|
-
DIMENSIONS_REGEX = /(?<width>[\d\.]+)
|
8
|
+
DIMENSIONS_REGEX = /(?<width>[\d\.]+)'\s*x\s*(?<depth>[\d\.]+)'\s*x\s*(?<height>[\d\.]+)'/
|
11
9
|
|
12
10
|
# @attribute [rw] depth
|
13
11
|
# @return [Float]
|
@@ -40,11 +38,6 @@ module UHaul
|
|
40
38
|
"#<#{self.class.name} #{props.join(' ')}>"
|
41
39
|
end
|
42
40
|
|
43
|
-
# @return [String] e.g. "5×5"
|
44
|
-
def id
|
45
|
-
"#{format('%g', @width)}×#{format('%g', @depth)}"
|
46
|
-
end
|
47
|
-
|
48
41
|
# @return [Integer]
|
49
42
|
def sqft
|
50
43
|
Integer(@width * @depth)
|
@@ -60,16 +53,17 @@ module UHaul
|
|
60
53
|
"#{format('%g', @width)}' × #{format('%g', @depth)}' (#{sqft} sqft)"
|
61
54
|
end
|
62
55
|
|
63
|
-
# @param
|
56
|
+
# @param text [String]
|
64
57
|
#
|
65
58
|
# @return [Dimensions]
|
66
|
-
def self.parse(
|
67
|
-
text = element.at_css('.unit-select-item-detail').text
|
59
|
+
def self.parse(text:)
|
68
60
|
match = DIMENSIONS_REGEX.match(text)
|
61
|
+
raise ParseError, "unknown length / width / height for #{text}" unless match
|
69
62
|
|
70
|
-
width =
|
71
|
-
depth =
|
72
|
-
|
63
|
+
width = Float(match[:width])
|
64
|
+
depth = Float(match[:depth])
|
65
|
+
height = Float(match[:height])
|
66
|
+
new(depth:, width:, height:)
|
73
67
|
end
|
74
68
|
end
|
75
69
|
end
|
data/lib/uhaul/facility.rb
CHANGED
@@ -7,6 +7,8 @@ module UHaul
|
|
7
7
|
class Facility
|
8
8
|
class ParseError < StandardError; end
|
9
9
|
|
10
|
+
PRICES_SELECTOR = '#roomTypes > ul:not([id*="VehicleStorage"]) > li'
|
11
|
+
|
10
12
|
SITEMAP_URLS = %w[
|
11
13
|
https://www.uhaul.com/Locations/Sitemaps/Sitemap-for-Storage-in-AL.ashx
|
12
14
|
https://www.uhaul.com/Locations/Sitemaps/Sitemap-for-Storage-in-AK.ashx
|
@@ -62,8 +64,7 @@ module UHaul
|
|
62
64
|
].freeze
|
63
65
|
|
64
66
|
DEFAULT_EMAIL = 'service@uhaul.com'
|
65
|
-
DEFAULT_PHONE = '+1-800-468-4285
|
66
|
-
'
|
67
|
+
DEFAULT_PHONE = '+1-800-468-4285'
|
67
68
|
|
68
69
|
# @attribute [rw] id
|
69
70
|
# @return [String]
|
@@ -130,7 +131,7 @@ module UHaul
|
|
130
131
|
|
131
132
|
geocode = Geocode.parse(data: data['geo'])
|
132
133
|
address = Address.parse(data: data['address'])
|
133
|
-
prices =
|
134
|
+
prices = document.css(PRICES_SELECTOR).map { |element| Price.parse(element:) }.compact
|
134
135
|
|
135
136
|
new(id:, url:, name:, address:, geocode:, prices:)
|
136
137
|
end
|
data/lib/uhaul/features.rb
CHANGED
@@ -3,15 +3,13 @@
|
|
3
3
|
module UHaul
|
4
4
|
# The features (e.g. climate-controlled, inside-drive-up-access, outside-drive-up-access, etc) of a price.
|
5
5
|
class Features
|
6
|
-
# @param
|
6
|
+
# @param text [String]
|
7
7
|
#
|
8
8
|
# @return [Features]
|
9
|
-
def self.parse(
|
10
|
-
text = element.text
|
11
|
-
|
9
|
+
def self.parse(text:)
|
12
10
|
new(
|
13
|
-
climate_controlled: text.include?('
|
14
|
-
drive_up_access: text.include?('Drive Up
|
11
|
+
climate_controlled: text.include?('Climate') && !text.include?('No Climate'),
|
12
|
+
drive_up_access: text.include?('Drive Up'),
|
15
13
|
first_floor_access: text.include?('1st Floor')
|
16
14
|
)
|
17
15
|
end
|
@@ -36,15 +34,6 @@ module UHaul
|
|
36
34
|
"#<#{self.class.name} #{props.join(' ')}>"
|
37
35
|
end
|
38
36
|
|
39
|
-
# @return [String] e.g. ""
|
40
|
-
def id
|
41
|
-
[].tap do |ids|
|
42
|
-
ids << 'cc' if climate_controlled?
|
43
|
-
ids << 'dua' if drive_up_access?
|
44
|
-
ids << 'ffa' if first_floor_access?
|
45
|
-
end.join('-')
|
46
|
-
end
|
47
|
-
|
48
37
|
# @return [String] e.g. "Climate Controlled + First Floor Access"
|
49
38
|
def text
|
50
39
|
amenities.join(' + ')
|
data/lib/uhaul/geocode.rb
CHANGED
data/lib/uhaul/link.rb
CHANGED
data/lib/uhaul/price.rb
CHANGED
@@ -22,18 +22,6 @@ module UHaul
|
|
22
22
|
# @return [Rates]
|
23
23
|
attr_accessor :rates
|
24
24
|
|
25
|
-
# @param facility_id [Integer]
|
26
|
-
#
|
27
|
-
# @return [Array<Price>]
|
28
|
-
def self.fetch(facility_id:)
|
29
|
-
url = "https://www.uhaul.com/facility-units/#{facility_id}"
|
30
|
-
data = Crawler.json(url:)['data']
|
31
|
-
return [] if data['error']
|
32
|
-
|
33
|
-
html = data['html']['units']
|
34
|
-
Nokogiri::HTML(html).css(PRICE_SELECTOR).map { |element| parse(element:) }
|
35
|
-
end
|
36
|
-
|
37
25
|
# @param id [String]
|
38
26
|
# @param dimensions [Dimensions]
|
39
27
|
# @param features [Features]
|
@@ -56,7 +44,7 @@ module UHaul
|
|
56
44
|
"#<#{self.class.name} #{props.join(' ')}>"
|
57
45
|
end
|
58
46
|
|
59
|
-
# @return [String] e.g. "123 | 5' × 5' (25 sqft) | $
|
47
|
+
# @return [String] e.g. "123 | 5' × 5' (25 sqft) | $90"
|
60
48
|
def text
|
61
49
|
"#{@id} | #{@dimensions.text} | #{@rates.text} | #{@features.text}"
|
62
50
|
end
|
@@ -65,12 +53,15 @@ module UHaul
|
|
65
53
|
#
|
66
54
|
# @return [Price]
|
67
55
|
def self.parse(element:)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
56
|
+
id_element = element.at_css('form input[name="RentableInventoryPk"]')
|
57
|
+
return unless id_element
|
58
|
+
|
59
|
+
id = id_element['value']
|
60
|
+
text = element.text.strip.gsub(/\s+/, ' ')
|
72
61
|
|
73
|
-
|
62
|
+
dimensions = Dimensions.parse(text:)
|
63
|
+
features = Features.parse(text:)
|
64
|
+
rates = Rates.parse(text:)
|
74
65
|
|
75
66
|
new(id:, dimensions:, features:, rates:)
|
76
67
|
end
|
data/lib/uhaul/rates.rb
CHANGED
@@ -3,57 +3,38 @@
|
|
3
3
|
module UHaul
|
4
4
|
# The rates (street + web) for a facility
|
5
5
|
class Rates
|
6
|
-
|
7
|
-
WEB_SELECTOR = '.part_item_price'
|
8
|
-
VALUE_REGEX = /(?<value>[\d\.]+)/
|
6
|
+
RATE_REGEX = /\$(?<price>[\d\.\,]+) Per Month/
|
9
7
|
|
10
|
-
# @attribute [rw]
|
8
|
+
# @attribute [rw] rate
|
11
9
|
# @return [Integer]
|
12
|
-
attr_accessor :
|
10
|
+
attr_accessor :price
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
attr_accessor :web
|
12
|
+
alias street price
|
13
|
+
alias web price
|
17
14
|
|
18
|
-
# @param
|
15
|
+
# @param text [String]
|
19
16
|
#
|
20
17
|
# @return [Rates]
|
21
|
-
def self.parse(
|
22
|
-
|
23
|
-
web = parse_value(element: element.at_css(WEB_SELECTOR))
|
24
|
-
|
25
|
-
new(street: street || web, web: web || street)
|
26
|
-
end
|
27
|
-
|
28
|
-
# @param element [Nokogiri::XML::Element]
|
29
|
-
#
|
30
|
-
# @return [Float, nil]
|
31
|
-
def self.parse_value(element:)
|
32
|
-
return if element.nil?
|
18
|
+
def self.parse(text:)
|
19
|
+
price = Float(RATE_REGEX.match(text)[:price].gsub(',', ''))
|
33
20
|
|
34
|
-
|
35
|
-
Float(match[:value]) if match
|
21
|
+
new(price:)
|
36
22
|
end
|
37
23
|
|
38
24
|
# @param street [Integer]
|
39
25
|
# @param web [Integer]
|
40
|
-
def initialize(
|
41
|
-
@
|
42
|
-
@web = web
|
26
|
+
def initialize(price:)
|
27
|
+
@price = price
|
43
28
|
end
|
44
29
|
|
45
30
|
# @return [String]
|
46
31
|
def inspect
|
47
|
-
|
48
|
-
"street=#{@street.inspect}",
|
49
|
-
"web=#{@web.inspect}"
|
50
|
-
]
|
51
|
-
"#<#{self.class.name} #{props.join(' ')}>"
|
32
|
+
"#<#{self.class.name} price=#{@price.inspect}>"
|
52
33
|
end
|
53
34
|
|
54
|
-
# @return [String] e.g. "$80
|
35
|
+
# @return [String] e.g. "$80"
|
55
36
|
def text
|
56
|
-
"$#{@
|
37
|
+
"$#{@price}"
|
57
38
|
end
|
58
39
|
end
|
59
40
|
end
|
data/lib/uhaul/sitemap.rb
CHANGED
data/lib/uhaul/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: uhaul
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Sylvestre
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|
@@ -117,7 +117,7 @@ metadata:
|
|
117
117
|
homepage_uri: https://github.com/ksylvest/uhaul
|
118
118
|
source_code_uri: https://github.com/ksylvest/uhaul
|
119
119
|
changelog_uri: https://github.com/ksylvest/uhaul
|
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 UHaul.
|
139
139
|
test_files: []
|