proxy_fetcher 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -9
- data/lib/proxy_fetcher.rb +9 -0
- data/lib/proxy_fetcher/configuration.rb +24 -1
- data/lib/proxy_fetcher/manager.rb +11 -24
- data/lib/proxy_fetcher/providers/base.rb +30 -0
- data/lib/proxy_fetcher/providers/free_proxy_list.rb +47 -0
- data/lib/proxy_fetcher/providers/hide_my_ass.rb +70 -0
- data/lib/proxy_fetcher/providers/hide_my_name.rb +49 -0
- data/lib/proxy_fetcher/proxy.rb +5 -60
- data/lib/proxy_fetcher/version.rb +2 -2
- data/proxy_fetcher.gemspec +2 -2
- data/spec/fixtures/free_proxy_list.html +25 -0
- data/spec/fixtures/hide_my_ass.html +2530 -0
- data/spec/fixtures/hide_my_name.html +146 -0
- data/spec/proxy_fetcher/providers/free_proxy_list_spec.rb +14 -0
- data/spec/proxy_fetcher/providers/hide_my_ass_spec.rb +14 -0
- data/spec/proxy_fetcher/providers/hide_my_name_spec.rb +9 -0
- data/spec/proxy_fetcher/proxy_spec.rb +7 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/{proxy_fetcher/manager_spec.rb → support/manager_examples.rb} +7 -4
- metadata +15 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a36eb7a048bc985836e08d8e380412ceadd6e4b
|
4
|
+
data.tar.gz: cdceec65f99f25a4bfe8b8ae3531a9d203fb8710
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39954c3255c4ea3642023b4ceb0a9c03d4623d2cecd2b75237f4b219af71a292408583602ca5d7e3ce06ec54a8ac39c4e0c83b7d98ecceaea76ca8d86ddc5f81
|
7
|
+
data.tar.gz: 362d73388a1ed8b9d674e571c55763c265d808415efbc5db8c96e94c8ab110fffde2b137436ffe521f54b9940805df2c846a392c2a3316153f235a47f47ae96f
|
data/README.md
CHANGED
@@ -5,14 +5,16 @@
|
|
5
5
|
[![License](http://img.shields.io/badge/license-MIT-brightgreen.svg)](#license)
|
6
6
|
|
7
7
|
This gem can help your Ruby application to make HTTP(S) requests from proxy server, fetching and validating
|
8
|
-
current proxy lists from the [HideMyAss](http://hidemyass.com/)
|
8
|
+
current proxy lists from the different proxy services like [HideMyAss](http://hidemyass.com/) or Hide My Name.
|
9
|
+
|
10
|
+
**IMPORTANT** currently HideMyAss service closed free proxy list service, but it will be open soon and gem will be updated.
|
9
11
|
|
10
12
|
## Installation
|
11
13
|
|
12
14
|
If using bundler, first add 'proxy_fetcher' to your Gemfile:
|
13
15
|
|
14
16
|
```ruby
|
15
|
-
gem 'proxy_fetcher', '~> 0.
|
17
|
+
gem 'proxy_fetcher', '~> 0.2'
|
16
18
|
```
|
17
19
|
|
18
20
|
or if you want to use the latest version (from `master` branch), then:
|
@@ -30,7 +32,7 @@ bundle install
|
|
30
32
|
Otherwise simply install the gem:
|
31
33
|
|
32
34
|
```sh
|
33
|
-
gem install proxy_fetcher -v '0.
|
35
|
+
gem install proxy_fetcher -v '0.2'
|
34
36
|
```
|
35
37
|
|
36
38
|
## Example of usage
|
@@ -79,9 +81,9 @@ Every proxy is a `ProxyFetcher::Proxy` object that has next readers:
|
|
79
81
|
* `port`
|
80
82
|
* `country` (USA or Brazil for example)
|
81
83
|
* `response_time` (5217 for example)
|
82
|
-
* `connection_time` (rank from 0 to 100, where 0 — slow, 100 — high)
|
83
|
-
* `speed` (rank from 0 to 100, where 0 — slow, 100 — high)
|
84
|
-
* `type` (URI schema, HTTP
|
84
|
+
* `connection_time` (rank from 0 to 100, where 0 — slow, 100 — high. **Note** depends on the proxy provider)
|
85
|
+
* `speed` (rank from 0 to 100, where 0 — slow, 100 — high. **Note** depends on the proxy provider)
|
86
|
+
* `type` (URI schema, HTTP or HTTPS)
|
85
87
|
* `anonimity` (Low or High +KA for example)
|
86
88
|
|
87
89
|
Also you can call next instance method for every Proxy object:
|
@@ -109,16 +111,45 @@ You can sort or find any proxy by speed using next 3 instance methods:
|
|
109
111
|
* `medium?`
|
110
112
|
* `slow?`'
|
111
113
|
|
112
|
-
To change open/read timeout for `cleanup!` and `connectable?` methods yu need to change ProxyFetcher
|
114
|
+
To change open/read timeout for `cleanup!` and `connectable?` methods yu need to change ProxyFetcher.config:
|
113
115
|
|
114
116
|
```ruby
|
115
|
-
ProxyFetcher
|
116
|
-
ProxyFetcher
|
117
|
+
ProxyFetcher.config.read_timeout = 1 # default is 3
|
118
|
+
ProxyFetcher.config.open_timeout = 1 # default is 3
|
117
119
|
|
118
120
|
manager = ProxyFetcher::Manager.new
|
119
121
|
manager.cleanup!
|
120
122
|
```
|
121
123
|
|
124
|
+
## Providers
|
125
|
+
|
126
|
+
Currently ProxyFetcher can deal with next proxy providers:
|
127
|
+
|
128
|
+
* Hide My Name (default one)
|
129
|
+
* Free Proxy List
|
130
|
+
* HideMyAss
|
131
|
+
|
132
|
+
If you wanna use one of them just setup required in the config:
|
133
|
+
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
ProxyFetcher.config.provider = :free_proxy_list
|
137
|
+
|
138
|
+
manager = ProxyFetcher::Manager.new
|
139
|
+
manager.proxies
|
140
|
+
#=> ...
|
141
|
+
```
|
142
|
+
|
143
|
+
Also you can write your own provider. All you need is to create a class, that would be inherited from the
|
144
|
+
`ProxyFetcher::Providers::Base` class, and register your provider like this:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
ProxyFetcher::Configuration.register_provider(:your_provider, YourProviderClass)
|
148
|
+
```
|
149
|
+
|
150
|
+
Provider class must implement `self.load_proxy_list` and `#parse!(html_entry)` methods that will load and parse
|
151
|
+
provider HTML page with proxy list. Take a look at the samples in the `proxy_fetcher/providers` directory.
|
152
|
+
|
122
153
|
## TODO
|
123
154
|
|
124
155
|
* Proxy filters
|
data/lib/proxy_fetcher.rb
CHANGED
@@ -5,6 +5,15 @@ require 'nokogiri'
|
|
5
5
|
require 'proxy_fetcher/configuration'
|
6
6
|
require 'proxy_fetcher/proxy'
|
7
7
|
require 'proxy_fetcher/manager'
|
8
|
+
require 'proxy_fetcher/providers/base'
|
9
|
+
require 'proxy_fetcher/providers/hide_my_ass'
|
10
|
+
require 'proxy_fetcher/providers/hide_my_name'
|
11
|
+
require 'proxy_fetcher/providers/free_proxy_list'
|
8
12
|
|
9
13
|
module ProxyFetcher
|
14
|
+
class << self
|
15
|
+
def config
|
16
|
+
@config ||= ProxyFetcher::Configuration.new
|
17
|
+
end
|
18
|
+
end
|
10
19
|
end
|
@@ -1,10 +1,33 @@
|
|
1
1
|
module ProxyFetcher
|
2
2
|
class Configuration
|
3
|
-
|
3
|
+
UnknownProvider = Class.new(StandardError)
|
4
|
+
ProviderRegistered = Class.new(StandardError)
|
5
|
+
|
6
|
+
attr_accessor :open_timeout, :read_timeout, :provider
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def providers
|
10
|
+
@providers ||= {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def register_provider(name, klass)
|
14
|
+
raise ProviderRegistered, "#{name} provider already registered!" if providers.key?(name.to_sym)
|
15
|
+
|
16
|
+
providers[name.to_sym] = klass
|
17
|
+
end
|
18
|
+
end
|
4
19
|
|
5
20
|
def initialize
|
6
21
|
@open_timeout = 3
|
7
22
|
@read_timeout = 3
|
23
|
+
|
24
|
+
self.provider = :hide_my_name # currently default one
|
25
|
+
end
|
26
|
+
|
27
|
+
def provider=(name)
|
28
|
+
@provider = self.class.providers[name.to_sym]
|
29
|
+
|
30
|
+
raise UnknownProvider, "unregistered proxy provider (#{name})!" if @provider.nil?
|
8
31
|
end
|
9
32
|
end
|
10
33
|
end
|
@@ -1,12 +1,6 @@
|
|
1
1
|
module ProxyFetcher
|
2
2
|
class Manager
|
3
|
-
|
4
|
-
|
5
|
-
class << self
|
6
|
-
def config
|
7
|
-
@config ||= ProxyFetcher::Configuration.new
|
8
|
-
end
|
9
|
-
end
|
3
|
+
EmptyProxyList = Class.new(StandardError)
|
10
4
|
|
11
5
|
attr_reader :proxies
|
12
6
|
|
@@ -22,9 +16,7 @@ module ProxyFetcher
|
|
22
16
|
|
23
17
|
# Update current proxy list from the provider
|
24
18
|
def refresh_list!
|
25
|
-
|
26
|
-
rows = doc.xpath('//table[@id="listable"]/tbody/tr')
|
27
|
-
|
19
|
+
rows = ProxyFetcher.config.provider.load_proxy_list
|
28
20
|
@proxies = rows.map { |row| Proxy.new(row) }
|
29
21
|
end
|
30
22
|
|
@@ -45,11 +37,11 @@ module ProxyFetcher
|
|
45
37
|
# Pop first valid proxy (and back it to the end of the proxy list)
|
46
38
|
# Invalid proxies will be removed from the list
|
47
39
|
def get!
|
48
|
-
index =
|
40
|
+
index = proxies.find_index(&:connectable?)
|
49
41
|
return if index.nil?
|
50
42
|
|
51
|
-
proxy =
|
52
|
-
tail =
|
43
|
+
proxy = proxies.delete_at(index)
|
44
|
+
tail = proxies[index..-1]
|
53
45
|
|
54
46
|
@proxies = tail << proxy
|
55
47
|
|
@@ -65,7 +57,12 @@ module ProxyFetcher
|
|
65
57
|
|
66
58
|
alias validate! cleanup!
|
67
59
|
|
68
|
-
#
|
60
|
+
# Return random proxy
|
61
|
+
def random
|
62
|
+
proxies.sample
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns array of proxy URLs (just schema + host + port)
|
69
66
|
def raw_proxies
|
70
67
|
proxies.map(&:url)
|
71
68
|
end
|
@@ -74,15 +71,5 @@ module ProxyFetcher
|
|
74
71
|
def inspect
|
75
72
|
to_s
|
76
73
|
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
# Get HTML from the requested URL
|
81
|
-
def load_html(url)
|
82
|
-
uri = URI.parse(url)
|
83
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
84
|
-
response = http.get(uri.request_uri)
|
85
|
-
response.body
|
86
|
-
end
|
87
74
|
end
|
88
75
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ProxyFetcher
|
2
|
+
module Providers
|
3
|
+
class Base
|
4
|
+
attr_reader :proxy
|
5
|
+
|
6
|
+
def initialize(proxy_instance)
|
7
|
+
@proxy = proxy_instance
|
8
|
+
end
|
9
|
+
|
10
|
+
def set!(name, value)
|
11
|
+
@proxy.instance_variable_set(:"@#{name}", value)
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def parse_entry(entry, proxy_instance)
|
16
|
+
new(proxy_instance).parse!(entry)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get HTML from the requested URL
|
20
|
+
def load_html(url)
|
21
|
+
uri = URI.parse(url)
|
22
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
23
|
+
http.use_ssl = true if uri.scheme == 'https'
|
24
|
+
response = http.get(uri.request_uri)
|
25
|
+
response.body
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module ProxyFetcher
|
2
|
+
module Providers
|
3
|
+
class FreeProxyList < Base
|
4
|
+
PROVIDER_URL = 'https://free-proxy-list.net/'.freeze
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def load_proxy_list
|
8
|
+
doc = Nokogiri::HTML(load_html(PROVIDER_URL))
|
9
|
+
doc.xpath('//table[@id="proxylisttable"]/tbody/tr')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse!(html_entry)
|
14
|
+
html_entry.xpath('td').each_with_index do |td, index|
|
15
|
+
case index
|
16
|
+
when 0
|
17
|
+
set!(:addr, td.content.strip)
|
18
|
+
when 1 then
|
19
|
+
set!(:port, Integer(td.content.strip))
|
20
|
+
when 3 then
|
21
|
+
set!(:country, td.content.strip)
|
22
|
+
when 4
|
23
|
+
set!(:anonymity, td.content.strip)
|
24
|
+
when 6
|
25
|
+
set!(:type, parse_type(td))
|
26
|
+
else
|
27
|
+
# nothing
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def parse_type(td)
|
35
|
+
type = td.content.strip
|
36
|
+
|
37
|
+
if type && type.downcase.include?('yes')
|
38
|
+
'HTTPS'
|
39
|
+
else
|
40
|
+
'HTTP'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
ProxyFetcher::Configuration.register_provider(:free_proxy_list, ProxyFetcher::Providers::FreeProxyList)
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module ProxyFetcher
|
2
|
+
module Providers
|
3
|
+
class HideMyAss < Base
|
4
|
+
PROVIDER_URL = 'http://proxylist.hidemyass.com/'.freeze
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def load_proxy_list
|
8
|
+
doc = Nokogiri::HTML(load_html(PROVIDER_URL))
|
9
|
+
doc.xpath('//table[@id="listable"]/tbody/tr')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse!(html_entry)
|
14
|
+
html_entry.xpath('td').each_with_index do |td, index|
|
15
|
+
case index
|
16
|
+
when 1
|
17
|
+
set!(:addr, parse_addr(td))
|
18
|
+
when 2 then
|
19
|
+
set!(:port, Integer(td.content.strip))
|
20
|
+
when 3 then
|
21
|
+
set!(:country, td.content.strip)
|
22
|
+
when 4
|
23
|
+
set!(:response_time, parse_response_time(td))
|
24
|
+
set!(:speed, parse_indicator_value(td))
|
25
|
+
when 5
|
26
|
+
set!(:connection_time, parse_indicator_value(td))
|
27
|
+
when 6 then
|
28
|
+
set!(:type, td.content.strip)
|
29
|
+
when 7
|
30
|
+
set!(:anonymity, td.content.strip)
|
31
|
+
else
|
32
|
+
# nothing
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def parse_addr(html_doc)
|
40
|
+
good = []
|
41
|
+
bytes = []
|
42
|
+
css = html_doc.at_xpath('span/style/text()').to_s
|
43
|
+
css.split.each { |l| good << Regexp.last_match(1) if l =~ /\.(.+?)\{.*inline/ }
|
44
|
+
|
45
|
+
html_doc.xpath('span/span | span | span/text()').each do |span|
|
46
|
+
if span.is_a?(Nokogiri::XML::Text)
|
47
|
+
bytes << Regexp.last_match(1) if span.content.strip =~ /\.{0,1}(.+)\.{0,1}/
|
48
|
+
elsif (span['style'] && span['style'] =~ /inline/) ||
|
49
|
+
(span['class'] && good.include?(span['class'])) ||
|
50
|
+
(span['class'] =~ /^[0-9]/)
|
51
|
+
|
52
|
+
bytes << span.content
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
bytes.join('.').gsub(/\.+/, '.')
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_response_time(html_doc)
|
60
|
+
Integer(html_doc.at_xpath('div')['rel'])
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_indicator_value(html_doc)
|
64
|
+
Integer(html_doc.at('.indicator').attr('style').match(/width: (\d+)%/i)[1])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
ProxyFetcher::Configuration.register_provider(:hide_my_ass, ProxyFetcher::Providers::HideMyAss)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ProxyFetcher
|
2
|
+
module Providers
|
3
|
+
class HideMyName < Base
|
4
|
+
PROVIDER_URL = 'https://hidemy.name/en/proxy-list/?type=hs#list'.freeze
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def load_proxy_list
|
8
|
+
doc = Nokogiri::HTML(load_html(PROVIDER_URL))
|
9
|
+
doc.xpath('//table[@class="proxy__t"]/tbody/tr')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse!(html_entry)
|
14
|
+
html_entry.xpath('td').each_with_index do |td, index|
|
15
|
+
case index
|
16
|
+
when 0
|
17
|
+
set!(:addr, td.content.strip)
|
18
|
+
when 1 then
|
19
|
+
set!(:port, Integer(td.content.strip))
|
20
|
+
when 2 then
|
21
|
+
set!(:country, td.at_xpath('*//span[1]/following-sibling::text()[1]').content.strip)
|
22
|
+
when 3
|
23
|
+
set!(:response_time, Integer(td.at('p').content.strip[/\d+/]))
|
24
|
+
when 4
|
25
|
+
set!(:type, parse_type(td))
|
26
|
+
when 5
|
27
|
+
set!(:anonymity, td.content.strip)
|
28
|
+
else
|
29
|
+
# nothing
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def parse_type(td)
|
37
|
+
schemas = td.content.strip
|
38
|
+
|
39
|
+
if schemas && schemas.downcase.include?('https')
|
40
|
+
'HTTPS'
|
41
|
+
else
|
42
|
+
'HTTP'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
ProxyFetcher::Configuration.register_provider(:hide_my_name, ProxyFetcher::Providers::HideMyName)
|
data/lib/proxy_fetcher/proxy.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module ProxyFetcher
|
2
2
|
class Proxy
|
3
3
|
attr_reader :addr, :port, :country, :response_time,
|
4
|
-
:connection_time, :speed, :type, :
|
4
|
+
:connection_time, :speed, :type, :anonymity
|
5
5
|
|
6
6
|
def initialize(html_row)
|
7
|
-
|
7
|
+
ProxyFetcher.config.provider.parse_entry(html_row, self)
|
8
8
|
|
9
9
|
self
|
10
10
|
end
|
@@ -12,13 +12,13 @@ module ProxyFetcher
|
|
12
12
|
def connectable?
|
13
13
|
connection = Net::HTTP.new(addr, port)
|
14
14
|
connection.use_ssl = true if https?
|
15
|
-
connection.open_timeout = ProxyFetcher
|
16
|
-
connection.read_timeout = ProxyFetcher
|
15
|
+
connection.open_timeout = ProxyFetcher.config.open_timeout
|
16
|
+
connection.read_timeout = ProxyFetcher.config.read_timeout
|
17
17
|
|
18
18
|
connection.start { |http| return true if http.request_head('/') }
|
19
19
|
|
20
20
|
false
|
21
|
-
rescue Timeout::Error, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED
|
21
|
+
rescue Timeout::Error, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EOFError
|
22
22
|
false
|
23
23
|
end
|
24
24
|
|
@@ -51,60 +51,5 @@ module ProxyFetcher
|
|
51
51
|
def url
|
52
52
|
uri.to_s
|
53
53
|
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
# HideMyAss proxy list rows parsing by columns
|
58
|
-
def parse_row!(html)
|
59
|
-
html.xpath('td').each_with_index do |td, index|
|
60
|
-
case index
|
61
|
-
when 1
|
62
|
-
@addr = parse_addr(td)
|
63
|
-
when 2 then
|
64
|
-
@port = Integer(td.content.strip)
|
65
|
-
when 3 then
|
66
|
-
@country = td.content.strip
|
67
|
-
when 4
|
68
|
-
@response_time = parse_response_time(td)
|
69
|
-
@speed = parse_indicator_value(td)
|
70
|
-
when 5
|
71
|
-
@connection_time = parse_indicator_value(td)
|
72
|
-
when 6 then
|
73
|
-
@type = td.content.strip
|
74
|
-
when 7
|
75
|
-
@anonymity = td.content.strip
|
76
|
-
else
|
77
|
-
# nothing
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def parse_addr(html)
|
83
|
-
good = []
|
84
|
-
bytes = []
|
85
|
-
css = html.at_xpath('span/style/text()').to_s
|
86
|
-
css.split.each { |l| good << Regexp.last_match(1) if l =~ /\.(.+?)\{.*inline/ }
|
87
|
-
|
88
|
-
html.xpath('span/span | span | span/text()').each do |span|
|
89
|
-
if span.is_a?(Nokogiri::XML::Text)
|
90
|
-
bytes << Regexp.last_match(1) if span.content.strip =~ /\.{0,1}(.+)\.{0,1}/
|
91
|
-
elsif (span['style'] && span['style'] =~ /inline/) ||
|
92
|
-
(span['class'] && good.include?(span['class'])) ||
|
93
|
-
(span['class'] =~ /^[0-9]/)
|
94
|
-
|
95
|
-
bytes << span.content
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
bytes.join('.').gsub(/\.+/, '.')
|
100
|
-
end
|
101
|
-
|
102
|
-
def parse_response_time(html)
|
103
|
-
Integer(html.at_xpath('div')['rel'])
|
104
|
-
end
|
105
|
-
|
106
|
-
def parse_indicator_value(html)
|
107
|
-
Integer(html.at('.indicator').attr('style').match(/width: (\d+)%/i)[1])
|
108
|
-
end
|
109
54
|
end
|
110
55
|
end
|