vessel 0.2.0 → 0.3.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/CHANGELOG.md +70 -0
- data/README.md +418 -34
- data/bin/console +16 -0
- data/bin/vessel +8 -0
- data/lib/vessel/cargo/callbacks.rb +27 -0
- data/lib/vessel/cargo/field.rb +41 -0
- data/lib/vessel/cargo/field_type.rb +17 -0
- data/lib/vessel/cargo/fieldable.rb +27 -0
- data/lib/vessel/cargo/fields.rb +54 -0
- data/lib/vessel/cargo/settings.rb +113 -0
- data/lib/vessel/cargo.rb +38 -78
- data/lib/vessel/cli.rb +61 -2
- data/lib/vessel/driver/ferrum/driver.rb +43 -0
- data/lib/vessel/driver/ferrum/page.rb +64 -0
- data/lib/vessel/driver/mechanize/driver.rb +35 -0
- data/lib/vessel/driver/mechanize/page.rb +79 -0
- data/lib/vessel/driver/page.rb +59 -0
- data/lib/vessel/driver/registry.rb +26 -0
- data/lib/vessel/driver.rb +133 -0
- data/lib/vessel/engine/stats.rb +51 -0
- data/lib/vessel/engine.rb +106 -27
- data/lib/vessel/loader.rb +36 -0
- data/lib/vessel/logger.rb +25 -0
- data/lib/vessel/middleware.rb +7 -9
- data/lib/vessel/middleware_scheduler.rb +51 -0
- data/lib/vessel/proxy.rb +41 -0
- data/lib/vessel/request.rb +28 -11
- data/lib/vessel/response.rb +106 -0
- data/lib/vessel/scheduler.rb +19 -34
- data/lib/vessel/skeleton/Gemfile +5 -0
- data/lib/vessel/skeleton/config/boot.rb +17 -0
- data/lib/vessel/skeleton/config/environments/dev/dev.rb +5 -0
- data/lib/vessel/skeleton/config/environments/prod/prod.rb +7 -0
- data/lib/vessel/skeleton/config/fields/.keep +0 -0
- data/lib/vessel/skeleton/config/middleware/debug.rb +15 -0
- data/lib/vessel/skeleton/crawlers/.keep +0 -0
- data/lib/vessel/skeleton/lib/helpers/.keep +0 -0
- data/lib/vessel/skeleton/lib/loader.rb +4 -0
- data/lib/vessel/skeleton/log/.keep +0 -0
- data/lib/vessel/templates/template.erb +9 -0
- data/lib/vessel/templator.rb +32 -0
- data/lib/vessel/util.rb +16 -0
- data/lib/vessel/version.rb +1 -1
- data/lib/vessel.rb +35 -5
- metadata +96 -29
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vessel
|
|
4
|
+
class Cargo
|
|
5
|
+
class FieldType
|
|
6
|
+
class << self
|
|
7
|
+
def add(*names, &block)
|
|
8
|
+
names.each { |n| types.merge!(n.to_sym => block) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def types
|
|
12
|
+
@types ||= {}
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "vessel/cargo/field"
|
|
4
|
+
require "vessel/cargo/field_type"
|
|
5
|
+
require "vessel/cargo/fields"
|
|
6
|
+
|
|
7
|
+
module Vessel
|
|
8
|
+
class Cargo
|
|
9
|
+
module Fieldable
|
|
10
|
+
def fields
|
|
11
|
+
@fields ||= Fields.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def field(name, *rest, &)
|
|
15
|
+
options = rest.last.is_a?(Hash) ? rest.pop : {}
|
|
16
|
+
service = options.delete(:service)
|
|
17
|
+
field = Field.new(name: name, value: options[:value], context: self, **options, &)
|
|
18
|
+
value = field.apply
|
|
19
|
+
if service
|
|
20
|
+
fields.service[name.to_sym] = value
|
|
21
|
+
else
|
|
22
|
+
fields[field.name] = value
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vessel
|
|
4
|
+
class Cargo
|
|
5
|
+
class Fields
|
|
6
|
+
attr_reader :service
|
|
7
|
+
|
|
8
|
+
def initialize(data = nil)
|
|
9
|
+
@service = {}
|
|
10
|
+
@fields = data.to_h
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def []=(key, value)
|
|
14
|
+
@fields[key.to_sym] = value
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def [](key)
|
|
18
|
+
@fields[key.to_sym]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def values_at(*args)
|
|
22
|
+
@fields.values_at(*args)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def count
|
|
26
|
+
@fields.count
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def select(&)
|
|
30
|
+
@fields.send(:select, &)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def zip(value)
|
|
34
|
+
@fields.zip(value)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def key?(value)
|
|
38
|
+
@fields.key?(value)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def keys
|
|
42
|
+
@fields.keys
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def to_h
|
|
46
|
+
@fields
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def each(&)
|
|
50
|
+
@fields.each(&)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "vessel/proxy"
|
|
4
|
+
require "vessel/util"
|
|
5
|
+
|
|
6
|
+
module Vessel
|
|
7
|
+
class Cargo
|
|
8
|
+
module Settings
|
|
9
|
+
include Vessel::Util
|
|
10
|
+
|
|
11
|
+
DELAY = 0
|
|
12
|
+
START_URLS = {}.freeze
|
|
13
|
+
MIDDLEWARE = [].freeze
|
|
14
|
+
MIN_THREADS = 1
|
|
15
|
+
MAX_THREADS = Concurrent.processor_count
|
|
16
|
+
NETWORK_ERROR_ATTEMPTS = 5
|
|
17
|
+
|
|
18
|
+
def name
|
|
19
|
+
settings[:domain]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def domain(value)
|
|
23
|
+
settings[:domain] = value
|
|
24
|
+
Vessel.loader.register(name, self)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def start_urls(*url_handlers)
|
|
28
|
+
settings[:start_urls] = url_handlers.compact.flatten.inject({}) do |options, url_handler|
|
|
29
|
+
options.merge(url_handler.is_a?(Hash) ? url_handler : { url_handler => Request::DEFAULT_HANDLER })
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def driver(name, **options)
|
|
34
|
+
settings[:driver_name] = name.to_sym
|
|
35
|
+
settings[:driver_options] = options
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def delay(value)
|
|
39
|
+
settings[:delay] = value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def headers(value)
|
|
43
|
+
settings[:headers] = value
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def cookie(name:, value:, domain: nil, path: nil, httponly: nil, secure: nil, expires: nil)
|
|
47
|
+
settings[:cookies] ||= []
|
|
48
|
+
domain ||= settings[:domain]
|
|
49
|
+
cookie = { name: name, value: value, domain: domain, path: path,
|
|
50
|
+
httponly: httponly, secure: secure, expires: expires }.compact
|
|
51
|
+
settings[:cookies] << cookie
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cookies(*value)
|
|
55
|
+
value.flatten.each { |c| cookie(**c) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def threads(min: MIN_THREADS, max: MAX_THREADS)
|
|
59
|
+
settings[:min_threads] = min
|
|
60
|
+
settings[:max_threads] = max
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def middleware(*classes)
|
|
64
|
+
settings[:middleware] = classes
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def proxy(klass)
|
|
68
|
+
settings[:proxy] = klass
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def blacklist(patterns)
|
|
72
|
+
settings[:blacklist] = patterns
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def whitelist(patterns)
|
|
76
|
+
settings[:whitelist] = patterns
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def network_error_attempts(value)
|
|
80
|
+
settings[:network_error_attempts] = value.to_i
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def allow_cookies(value)
|
|
84
|
+
settings[:allow_cookies] = value
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def settings
|
|
88
|
+
@settings ||= if superclass.respond_to?(:settings)
|
|
89
|
+
deep_clone(superclass.settings)
|
|
90
|
+
else
|
|
91
|
+
{
|
|
92
|
+
delay: DELAY, start_urls: START_URLS, middleware: MIDDLEWARE,
|
|
93
|
+
min_threads: MIN_THREADS, max_threads: MAX_THREADS,
|
|
94
|
+
driver_name: :ferrum, driver_options: {}, headers: nil,
|
|
95
|
+
cookies: nil, allow_cookies: true, proxy: nil,
|
|
96
|
+
blacklist: nil, whitelist: nil, domain: nil,
|
|
97
|
+
network_error_attempts: NETWORK_ERROR_ATTEMPTS
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def to_domain_name(klass)
|
|
103
|
+
# name&.split("::")&.last&.downcase
|
|
104
|
+
klass.name.gsub(/[a-zA-Z](?=[A-Z])/, '\0 ').downcase.split.map do |value|
|
|
105
|
+
next "." if value == "dot"
|
|
106
|
+
next "-" if value == "dash"
|
|
107
|
+
|
|
108
|
+
value
|
|
109
|
+
end.join
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
data/lib/vessel/cargo.rb
CHANGED
|
@@ -1,87 +1,52 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "ferrum"
|
|
4
3
|
require "forwardable"
|
|
4
|
+
require "vessel/cargo/fieldable"
|
|
5
|
+
require "vessel/cargo/settings"
|
|
6
|
+
require "vessel/cargo/callbacks"
|
|
5
7
|
|
|
6
8
|
module Vessel
|
|
7
9
|
class Cargo
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
MAX_THREADS = Concurrent.processor_count
|
|
13
|
-
|
|
14
|
-
class << self
|
|
15
|
-
attr_reader :settings
|
|
16
|
-
|
|
17
|
-
def run(settings = nil, &block)
|
|
18
|
-
self.settings.merge!(Hash(settings))
|
|
19
|
-
Engine.run(self, &block)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def domain(name)
|
|
23
|
-
settings[:domain] = name
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def start_urls(*urls)
|
|
27
|
-
settings[:start_urls] = urls.flatten
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def delay(value)
|
|
31
|
-
settings[:delay] = value
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def timeout(value)
|
|
35
|
-
settings[:timeout] = value
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def headers(value)
|
|
39
|
-
settings[:headers] = value
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def threads(min: MIN_THREADS, max: MAX_THREADS)
|
|
43
|
-
settings[:min_threads] = min
|
|
44
|
-
settings[:max_threads] = max
|
|
45
|
-
end
|
|
10
|
+
def self.run(settings = nil, &)
|
|
11
|
+
self.settings.merge!(Hash(settings))
|
|
12
|
+
Engine.run(self, &)
|
|
13
|
+
end
|
|
46
14
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
15
|
+
def self.build_request(url:, **options)
|
|
16
|
+
Request.new(url: url,
|
|
17
|
+
delay: settings[:delay],
|
|
18
|
+
cookies: settings[:cookies],
|
|
19
|
+
headers: settings[:headers],
|
|
20
|
+
**options)
|
|
21
|
+
end
|
|
50
22
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
end
|
|
23
|
+
def self.start_requests
|
|
24
|
+
return build_request(url: nil) if settings[:start_urls].empty?
|
|
54
25
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
end
|
|
26
|
+
settings[:start_urls].map { |u, h| build_request(url: u, handler: h) }
|
|
27
|
+
end
|
|
58
28
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
delay: DELAY,
|
|
62
|
-
middleware: MIDDLEWARE,
|
|
63
|
-
start_urls: START_URLS,
|
|
64
|
-
min_threads: MIN_THREADS,
|
|
65
|
-
max_threads: MAX_THREADS,
|
|
66
|
-
ferrum: Hash.new,
|
|
67
|
-
intercept: nil,
|
|
68
|
-
headers: nil,
|
|
69
|
-
domain: name&.split('::')&.last&.downcase
|
|
70
|
-
}
|
|
71
|
-
end
|
|
29
|
+
def self.store_cookies(cookies)
|
|
30
|
+
settings[:cookies] = cookies
|
|
72
31
|
end
|
|
73
32
|
|
|
33
|
+
include Callbacks
|
|
34
|
+
include Fieldable
|
|
35
|
+
extend Settings
|
|
74
36
|
extend Forwardable
|
|
75
|
-
delegate %i[at_css css at_xpath xpath] => :page
|
|
76
37
|
|
|
77
|
-
|
|
38
|
+
delegate %i[at_css css at_xpath xpath absolute_url join_url
|
|
39
|
+
url url_encode url_decode uri_encode uri_decode
|
|
40
|
+
data attempt body raw] => :response
|
|
78
41
|
|
|
79
|
-
|
|
80
|
-
@page = page
|
|
81
|
-
end
|
|
42
|
+
attr_reader :settings, :domain, :response, :page, :size
|
|
82
43
|
|
|
83
|
-
def
|
|
84
|
-
self.class.settings
|
|
44
|
+
def initialize(response = nil)
|
|
45
|
+
@settings = self.class.settings
|
|
46
|
+
@domain = settings[:domain]
|
|
47
|
+
@response = response
|
|
48
|
+
@page = response&.page
|
|
49
|
+
@size = response&.size
|
|
85
50
|
end
|
|
86
51
|
|
|
87
52
|
def parse
|
|
@@ -90,16 +55,11 @@ module Vessel
|
|
|
90
55
|
|
|
91
56
|
private
|
|
92
57
|
|
|
93
|
-
def request(**options)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def absolute_url(relative)
|
|
98
|
-
Addressable::URI.join(page.current_url, relative).to_s
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def current_url
|
|
102
|
-
Addressable::URI.parse(page.current_url)
|
|
58
|
+
def request(url:, encode: true, **options)
|
|
59
|
+
url = absolute_url(url, encode: encode)
|
|
60
|
+
self.class.build_request(url: url, **options)
|
|
103
61
|
end
|
|
104
62
|
end
|
|
63
|
+
|
|
64
|
+
Crawler = Cargo
|
|
105
65
|
end
|
data/lib/vessel/cli.rb
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "pp"
|
|
1
5
|
require "thor"
|
|
2
6
|
|
|
3
7
|
module Vessel
|
|
@@ -7,9 +11,64 @@ module Vessel
|
|
|
7
11
|
puts Vessel::VERSION
|
|
8
12
|
end
|
|
9
13
|
|
|
10
|
-
desc "
|
|
14
|
+
desc "list", "List all available crawlers of current project."
|
|
15
|
+
def list
|
|
16
|
+
Vessel.boot
|
|
17
|
+
Vessel.loader.load_crawlers
|
|
18
|
+
puts Vessel.loader.list_crawlers
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
desc "settings NAME", "Get the settings for given name."
|
|
22
|
+
def settings(name)
|
|
23
|
+
crawler = find_crawler(name)
|
|
24
|
+
settings = crawler.settings
|
|
25
|
+
puts "Delay: #{settings[:delay]}"
|
|
26
|
+
puts "Allow cookies: #{settings[:allow_cookies]}"
|
|
27
|
+
puts "Cookies: #{settings[:cookies]}"
|
|
28
|
+
puts "Middleware: #{settings[:middleware]}"
|
|
29
|
+
puts "Start urls:\n"
|
|
30
|
+
PP.pp(settings[:start_urls])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc "start NAME", "Start crawling given name."
|
|
11
34
|
def start(name)
|
|
12
|
-
|
|
35
|
+
crawler = find_crawler(name)
|
|
36
|
+
engine = Engine.new(crawler)
|
|
37
|
+
engine.run
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc "parse NAME URL CALLBACK --data=key:value", "Parse given url and call a callback with response."
|
|
41
|
+
method_option :data, type: :hash, default: {}, required: false
|
|
42
|
+
def parse(name, url, callback)
|
|
43
|
+
crawler = find_crawler(name)
|
|
44
|
+
engine = Engine.new(crawler)
|
|
45
|
+
data = options[:data].transform_keys(&:to_sym)
|
|
46
|
+
engine.parse(url, callback, data)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
desc "new NAME", "Generate new project."
|
|
50
|
+
def new(name)
|
|
51
|
+
templates = File.expand_path("skeleton", __dir__)
|
|
52
|
+
FileUtils.cp_r(templates, "./#{name}")
|
|
53
|
+
puts "Project #{name} generated"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
desc "generate NAME OPTIONS", "Generate crawler skeleton with given name and parameters."
|
|
57
|
+
def generate(name, *options)
|
|
58
|
+
require "erb"
|
|
59
|
+
Vessel.boot
|
|
60
|
+
templator = Vessel.loader.templator
|
|
61
|
+
templator.new(name, options).build
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def find_crawler(name)
|
|
67
|
+
Vessel.boot
|
|
68
|
+
Vessel.loader.load_crawler(name)
|
|
69
|
+
crawler = Vessel.loader.find_crawler(name)
|
|
70
|
+
abort "Crawler not found" unless crawler
|
|
71
|
+
crawler
|
|
13
72
|
end
|
|
14
73
|
end
|
|
15
74
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ferrum"
|
|
4
|
+
require "vessel/driver/ferrum/page"
|
|
5
|
+
|
|
6
|
+
module Vessel
|
|
7
|
+
class Driver
|
|
8
|
+
module Ferrum
|
|
9
|
+
class Driver < ::Vessel::Driver
|
|
10
|
+
DEFAULT_OPTIONS = {
|
|
11
|
+
timeout: 60,
|
|
12
|
+
js_errors: false,
|
|
13
|
+
process_timeout: 30,
|
|
14
|
+
pending_connection_errors: false,
|
|
15
|
+
browser_options: { "ignore-certificate-errors" => nil }
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def self.direct_network_errors
|
|
19
|
+
@direct_network_errors ||= (super + [::Ferrum::TimeoutError, ::Ferrum::StatusError]).freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
driver_name :ferrum
|
|
23
|
+
|
|
24
|
+
def start
|
|
25
|
+
driver_options = settings.fetch(:driver_options, {})
|
|
26
|
+
@browser = ::Ferrum::Browser.new(**DEFAULT_OPTIONS, **driver_options)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def stop
|
|
30
|
+
browser&.quit
|
|
31
|
+
@browser = nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def create_page(proxy: nil)
|
|
35
|
+
options = {}
|
|
36
|
+
options.merge!(proxy: proxy) if proxy
|
|
37
|
+
page = browser.create_page(**options)
|
|
38
|
+
Page.new(page, context: browser.contexts[page.context_id])
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
5
|
+
module Vessel
|
|
6
|
+
class Driver
|
|
7
|
+
module Ferrum
|
|
8
|
+
class Page < Page
|
|
9
|
+
extend Forwardable
|
|
10
|
+
|
|
11
|
+
delegate %i[css at_css xpath at_xpath body current_url go_to] => :page
|
|
12
|
+
|
|
13
|
+
attr_reader :page
|
|
14
|
+
|
|
15
|
+
def initialize(page, context:)
|
|
16
|
+
super()
|
|
17
|
+
@page = page
|
|
18
|
+
@context = context
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def close
|
|
22
|
+
@context.dispose if page.use_proxy?
|
|
23
|
+
page.close
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def headers=(headers)
|
|
27
|
+
return unless headers
|
|
28
|
+
|
|
29
|
+
page.headers.set(headers)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def headers
|
|
33
|
+
page.headers.get
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def cookies=(cookies)
|
|
37
|
+
cookies.each { |c| page.cookies.set(c) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def cookies
|
|
41
|
+
page.cookies.all.values
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def status
|
|
45
|
+
page.network.status
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def size
|
|
49
|
+
page.network.traffic.map do |req|
|
|
50
|
+
[req.response&.body_size, req.response&.headers_size]
|
|
51
|
+
end.flatten.compact.inject(&:+)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def blacklist=(patterns)
|
|
55
|
+
page.network.blacklist = patterns
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def whitelist=(patterns)
|
|
59
|
+
page.network.whitelist = patterns
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mechanize"
|
|
4
|
+
require "vessel/driver/mechanize/page"
|
|
5
|
+
|
|
6
|
+
module Vessel
|
|
7
|
+
class Driver
|
|
8
|
+
module Mechanize
|
|
9
|
+
class Driver < ::Vessel::Driver
|
|
10
|
+
def self.indirect_network_errors
|
|
11
|
+
@indirect_network_errors ||= (super + [::Mechanize::ResponseCodeError]).freeze
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
driver_name :mechanize
|
|
15
|
+
|
|
16
|
+
def start
|
|
17
|
+
# Nothing to start here
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def stop
|
|
21
|
+
# Nothing to stop here
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_page(proxy: nil)
|
|
25
|
+
mechanize = ::Mechanize.new do |m|
|
|
26
|
+
m.user_agent = settings.dig(:headers, "User-Agent")
|
|
27
|
+
m.set_proxy(proxy[:host], proxy[:port], proxy[:user], proxy[:password]) if proxy
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
Page.new(mechanize)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
5
|
+
module Vessel
|
|
6
|
+
class Driver
|
|
7
|
+
module Mechanize
|
|
8
|
+
class Page < Page
|
|
9
|
+
extend Forwardable
|
|
10
|
+
|
|
11
|
+
delegate %i[css at_css xpath at_xpath body] => :page
|
|
12
|
+
|
|
13
|
+
attr_reader :mechanize
|
|
14
|
+
|
|
15
|
+
def initialize(mechanize)
|
|
16
|
+
super()
|
|
17
|
+
@mechanize = mechanize
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def close
|
|
21
|
+
mechanize.shutdown
|
|
22
|
+
@mechanize = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def go_to(url)
|
|
26
|
+
mechanize.get(url)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def headers=(headers)
|
|
30
|
+
mechanize.request_headers = headers
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def headers
|
|
34
|
+
mechanize.request_headers
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def cookies=(cookies)
|
|
38
|
+
cookies.each { |c| mechanize.cookie_jar << ::Mechanize::Cookie.new(c) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def cookies
|
|
42
|
+
mechanize.cookies
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def status
|
|
46
|
+
page.code.to_i
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def size
|
|
50
|
+
page.content.bytesize
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def current_url
|
|
54
|
+
page&.uri.to_s
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# rubocop:disable all
|
|
58
|
+
def blacklist=(*)
|
|
59
|
+
@@_blacklist_warning ||= begin
|
|
60
|
+
warn "blacklist is not supported by mechanize driver"
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def whitelist=(*)
|
|
66
|
+
@@_whitelist_warning ||= begin
|
|
67
|
+
warn "whitelist is not supported by mechanize driver"
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
# rubocop:enable all
|
|
72
|
+
|
|
73
|
+
def page
|
|
74
|
+
mechanize.current_page
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|