spider 0.6.0 → 0.8.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/VERSION +1 -1
- data/lib/spider/included_in_file.rb +2 -2
- data/lib/spider/included_in_memcached.rb +1 -1
- data/lib/spider/included_in_redis.rb +2 -2
- data/lib/spider/next_urls_in_sqs.rb +6 -6
- data/lib/spider/robot_rules.rb +4 -4
- data/lib/spider/spider_instance.rb +90 -81
- data/lib/spider.rb +9 -4
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 393d7a240dbf77360ca4aeac52e8f162443903bac06817bbbf12440e597221f8
|
|
4
|
+
data.tar.gz: a26ff2a1f661fcde2712dc11f4a7fc0a9920e2c111bde01ef07b2135f0428c15
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 72498a31555641b00d448d28cf7a20e652a2824c7891b9f0ca7a04c126d0481f3df98267c876e63f01d1103c56e6a747e448bb4968cdbab9245806dfdf25ca77
|
|
7
|
+
data.tar.gz: 141feb5b9446ad7f40c2c79ddbb1b43e70a93a6347115c5bbc96a2a55b47d0eef096b99f9586d3ca122d93d0e48e04b4d0b4f44b4316475053256f6645a6837c
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.8.0
|
|
@@ -15,14 +15,14 @@ class IncludedInFile
|
|
|
15
15
|
def initialize(filepath)
|
|
16
16
|
@filepath = filepath
|
|
17
17
|
# create file if not exists
|
|
18
|
-
File.write(@filepath,
|
|
18
|
+
File.write(@filepath, "") unless File.file?(@filepath)
|
|
19
19
|
@urls = File.readlines(@filepath).map(&:chomp)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# Add an item to the file & array of URL.
|
|
23
23
|
def <<(v)
|
|
24
24
|
@urls << v.to_s
|
|
25
|
-
File.write(@filepath, "#{v}\r\n", File.size(@filepath), mode:
|
|
25
|
+
File.write(@filepath, "#{v}\r\n", File.size(@filepath), mode: "a")
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
# True if the item is in the file.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Use AmazonSQS to track nodes to visit.
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
3
|
+
require "rubygems"
|
|
4
|
+
require "right_aws"
|
|
5
|
+
require "yaml"
|
|
6
6
|
|
|
7
7
|
# A specialized class using AmazonSQS to track nodes to walk. It supports
|
|
8
8
|
# two operations: push and pop . Together these can be used to
|
|
@@ -20,7 +20,7 @@ class NextUrlsInSQS
|
|
|
20
20
|
# Construct a new NextUrlsInSQS instance. All arguments here are
|
|
21
21
|
# passed to RightAWS::SqsGen2 (part of the right_aws gem) or used
|
|
22
22
|
# to set the AmazonSQS queue name (optional).
|
|
23
|
-
def initialize(aws_access_key, aws_secret_access_key, queue_name =
|
|
23
|
+
def initialize(aws_access_key, aws_secret_access_key, queue_name = "ruby-spider")
|
|
24
24
|
@sqs = RightAws::SqsGen2.new(aws_access_key, aws_secret_access_key)
|
|
25
25
|
@queue = @sqs.queue(queue_name)
|
|
26
26
|
end
|
|
@@ -30,14 +30,14 @@ class NextUrlsInSQS
|
|
|
30
30
|
def pop
|
|
31
31
|
while true
|
|
32
32
|
message = @queue.pop
|
|
33
|
-
return YAML
|
|
33
|
+
return YAML.load(message.to_s) unless message.nil?
|
|
34
34
|
sleep 5
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Put data on the queue. Data is encoded with YAML.
|
|
39
39
|
def push(a_msg)
|
|
40
|
-
encoded_message = YAML
|
|
40
|
+
encoded_message = YAML.dump(a_msg)
|
|
41
41
|
@queue.push(a_msg)
|
|
42
42
|
end
|
|
43
43
|
end
|
data/lib/spider/robot_rules.rb
CHANGED
|
@@ -11,13 +11,13 @@ require "uri"
|
|
|
11
11
|
|
|
12
12
|
# Based on Perl's WWW::RobotRules module, by Gisle Aas.
|
|
13
13
|
class RobotRules
|
|
14
|
-
def initialize(
|
|
14
|
+
def initialize(user_agent)
|
|
15
15
|
@user_agent = user_agent.scan(/\S+/).first.sub(%r{/.*},
|
|
16
16
|
"").downcase
|
|
17
17
|
@rules = Hash.new { |rules, rule| rules[rule] = Array.new }
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def parse(
|
|
20
|
+
def parse(text_uri, robots_data)
|
|
21
21
|
uri = URI.parse(text_uri)
|
|
22
22
|
location = "#{uri.host}:#{uri.port}"
|
|
23
23
|
@rules.delete(location)
|
|
@@ -69,12 +69,12 @@ uri.scheme
|
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
def allowed?(
|
|
72
|
+
def allowed?(text_uri)
|
|
73
73
|
uri = URI.parse(text_uri)
|
|
74
74
|
location = "#{uri.host}:#{uri.port}"
|
|
75
75
|
path = uri.path
|
|
76
76
|
|
|
77
|
-
return true unless %w
|
|
77
|
+
return true unless %w[http https].include?(uri.scheme)
|
|
78
78
|
|
|
79
79
|
not @rules[location].any? { |rule| path.index(rule) == 0 }
|
|
80
80
|
end
|
|
@@ -1,41 +1,51 @@
|
|
|
1
1
|
# Specialized spidering rules.
|
|
2
2
|
|
|
3
|
-
require File.dirname(__FILE__)+
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
3
|
+
require File.dirname(__FILE__)+"/robot_rules.rb"
|
|
4
|
+
require "open-uri"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "net/http"
|
|
7
|
+
require "net/https"
|
|
8
8
|
|
|
9
|
-
module Net
|
|
10
|
-
class HTTPResponse
|
|
9
|
+
module Net # :nodoc:
|
|
10
|
+
class HTTPResponse # :nodoc:
|
|
11
11
|
def success?; false; end
|
|
12
12
|
def redirect?; false; end
|
|
13
13
|
end
|
|
14
|
-
class HTTPSuccess
|
|
14
|
+
class HTTPSuccess # :nodoc:
|
|
15
15
|
def success?; true; end
|
|
16
16
|
end
|
|
17
|
-
class HTTPRedirection
|
|
17
|
+
class HTTPRedirection # :nodoc:
|
|
18
18
|
def redirect?; true; end
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
class NilClass #:nodoc:
|
|
23
|
-
def merge(h); h; end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
22
|
class SpiderInstance
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
# Matches an anchor tag's attribute string carrying a rel value that should
|
|
24
|
+
# not be followed (nofollow, sponsored, or ugc), regardless of quoting.
|
|
25
|
+
NOFOLLOW_REL = /rel\s*=\s*["']?[^"'>]*\b(?:nofollow|sponsored|ugc)\b/i
|
|
26
|
+
|
|
27
|
+
# Seconds to wait before each HTTP request. Use to be polite to servers.
|
|
28
|
+
# s.crawl_delay = 1
|
|
29
|
+
#
|
|
30
|
+
# Maximum number of pages to fetch before stopping. nil means no limit.
|
|
31
|
+
# s.max_urls = 100
|
|
32
|
+
attr_writer :crawl_delay, :max_urls
|
|
33
|
+
|
|
34
|
+
def initialize(next_urls, seen = [], rules = nil, robots_seen = []) # :nodoc:
|
|
35
|
+
@url_checks = []
|
|
36
|
+
@cache = :memory
|
|
37
|
+
@callbacks = {}
|
|
38
|
+
@next_urls = [ next_urls ]
|
|
39
|
+
@seen = seen
|
|
40
|
+
@rules = rules || RobotRules.new("Ruby Spider #{Spider::VERSION}")
|
|
41
|
+
@robots_seen = robots_seen
|
|
42
|
+
@headers = {}
|
|
43
|
+
@setup = nil
|
|
44
|
+
@teardown = nil
|
|
45
|
+
@interrupted = false
|
|
46
|
+
@crawl_delay = 0
|
|
47
|
+
@max_urls = nil
|
|
48
|
+
@urls_fetched = 0
|
|
39
49
|
end
|
|
40
50
|
|
|
41
51
|
# Add a predicate that determines whether to continue down this URL's path.
|
|
@@ -70,7 +80,7 @@ class SpiderInstance
|
|
|
70
80
|
if cacher.respond_to?(:<<) && cacher.respond_to?(:include?)
|
|
71
81
|
@seen = cacher
|
|
72
82
|
else
|
|
73
|
-
raise ArgumentError,
|
|
83
|
+
raise ArgumentError, "expected something that responds to << and included?"
|
|
74
84
|
end
|
|
75
85
|
end
|
|
76
86
|
|
|
@@ -149,10 +159,10 @@ class SpiderInstance
|
|
|
149
159
|
HeaderSetter.new(self)
|
|
150
160
|
end
|
|
151
161
|
|
|
152
|
-
def raw_headers
|
|
162
|
+
def raw_headers # :nodoc:
|
|
153
163
|
@headers
|
|
154
164
|
end
|
|
155
|
-
def raw_headers=(v)
|
|
165
|
+
def raw_headers=(v) # :nodoc:
|
|
156
166
|
@headers = v
|
|
157
167
|
end
|
|
158
168
|
|
|
@@ -161,14 +171,14 @@ class SpiderInstance
|
|
|
161
171
|
@headers = {}
|
|
162
172
|
end
|
|
163
173
|
|
|
164
|
-
def start!
|
|
174
|
+
def start! # :nodoc:
|
|
165
175
|
trap("SIGINT") { @interrupted = true }
|
|
166
176
|
begin
|
|
167
177
|
next_urls = @next_urls.pop
|
|
168
178
|
next_urls.each do |prior_url, urls|
|
|
169
|
-
urls = [urls] unless urls.kind_of?(Array)
|
|
179
|
+
urls = [ urls ] unless urls.kind_of?(Array)
|
|
170
180
|
urls.map do |a_url|
|
|
171
|
-
[a_url, (URI.parse(a_url) rescue nil)]
|
|
181
|
+
[ a_url, (URI.parse(a_url) rescue nil) ]
|
|
172
182
|
end.select do |a_url, parsed_url|
|
|
173
183
|
allowable_url?(a_url, parsed_url)
|
|
174
184
|
end.each do |a_url, parsed_url|
|
|
@@ -180,17 +190,18 @@ class SpiderInstance
|
|
|
180
190
|
end
|
|
181
191
|
end
|
|
182
192
|
@teardown.call(a_url) unless @teardown.nil?
|
|
193
|
+
@interrupted = true if @max_urls && @urls_fetched >= @max_urls
|
|
183
194
|
break if @interrupted
|
|
184
195
|
end
|
|
185
196
|
end
|
|
186
197
|
end while !@next_urls.empty? && !@interrupted
|
|
187
198
|
end
|
|
188
199
|
|
|
189
|
-
def stop!
|
|
200
|
+
def stop! # :nodoc:
|
|
190
201
|
@interrupted = true
|
|
191
202
|
end
|
|
192
203
|
|
|
193
|
-
def success_or_failure(code)
|
|
204
|
+
def success_or_failure(code) # :nodoc:
|
|
194
205
|
if code > 199 && code < 300
|
|
195
206
|
:success
|
|
196
207
|
else
|
|
@@ -198,23 +209,23 @@ class SpiderInstance
|
|
|
198
209
|
end
|
|
199
210
|
end
|
|
200
211
|
|
|
201
|
-
def allowable_url?(a_url, parsed_url)
|
|
212
|
+
def allowable_url?(a_url, parsed_url) # :nodoc:
|
|
202
213
|
!parsed_url.nil? && !@seen.include?(parsed_url) && allowed?(a_url, parsed_url) &&
|
|
203
|
-
@url_checks.map{|url_check|url_check.call(a_url)}.all?
|
|
214
|
+
@url_checks.map { |url_check|url_check.call(a_url) }.all?
|
|
204
215
|
end
|
|
205
216
|
|
|
206
217
|
# True if the robots.txt for that URL allows access to it.
|
|
207
218
|
def allowed?(a_url, parsed_url) # :nodoc:
|
|
208
|
-
return false unless [
|
|
219
|
+
return false unless [ "http", "https" ].include?(parsed_url.scheme)
|
|
209
220
|
u = "#{parsed_url.scheme}://#{parsed_url.host}:#{parsed_url.port}/robots.txt"
|
|
210
221
|
parsed_u = URI.parse(u)
|
|
211
|
-
return false unless @url_checks.map{|url_check|url_check.call(a_url)}.all?
|
|
222
|
+
return false unless @url_checks.map { |url_check|url_check.call(a_url) }.all?
|
|
212
223
|
begin
|
|
213
224
|
unless @robots_seen.include?(u)
|
|
214
|
-
#open(u, 'User-Agent' => 'Ruby Spider',
|
|
225
|
+
# open(u, 'User-Agent' => 'Ruby Spider',
|
|
215
226
|
# 'Accept' => 'text/html,text/xml,application/xml,text/plain', :ssl_verify => false) do |url|
|
|
216
227
|
# @rules.parse(u, url.read)
|
|
217
|
-
#end
|
|
228
|
+
# end
|
|
218
229
|
get_page(parsed_u) do |r|
|
|
219
230
|
@rules.parse(u, r.body)
|
|
220
231
|
end
|
|
@@ -223,24 +234,26 @@ class SpiderInstance
|
|
|
223
234
|
@rules.allowed?(a_url)
|
|
224
235
|
rescue OpenURI::HTTPError
|
|
225
236
|
true # No robots.txt
|
|
226
|
-
rescue
|
|
237
|
+
rescue StandardError, Timeout::Error # to keep it from crashing
|
|
227
238
|
false
|
|
228
239
|
end
|
|
229
240
|
end
|
|
230
241
|
|
|
231
|
-
def get_page(parsed_url, &block)
|
|
242
|
+
def get_page(parsed_url, &block) # :nodoc:
|
|
232
243
|
@seen << parsed_url
|
|
233
244
|
begin
|
|
245
|
+
sleep @crawl_delay if @crawl_delay && @crawl_delay > 0
|
|
234
246
|
http = Net::HTTP.new(parsed_url.host, parsed_url.port)
|
|
235
|
-
if parsed_url.scheme ==
|
|
247
|
+
if parsed_url.scheme == "https"
|
|
236
248
|
http.use_ssl = true
|
|
237
249
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
238
250
|
end
|
|
239
251
|
# Uses start because http.finish cannot be called.
|
|
240
|
-
r = http.start {|h| h.request(Net::HTTP::Get.new(parsed_url.request_uri, @headers))}
|
|
252
|
+
r = http.start { |h| h.request(Net::HTTP::Get.new(parsed_url.request_uri, @headers)) }
|
|
241
253
|
if r.redirect?
|
|
242
|
-
get_page(URI.parse(construct_complete_url(parsed_url,r[
|
|
254
|
+
get_page(URI.parse(construct_complete_url(parsed_url, r["Location"])), &block)
|
|
243
255
|
else
|
|
256
|
+
@urls_fetched += 1
|
|
244
257
|
block.call(r)
|
|
245
258
|
end
|
|
246
259
|
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError => e
|
|
@@ -249,66 +262,62 @@ class SpiderInstance
|
|
|
249
262
|
end
|
|
250
263
|
end
|
|
251
264
|
|
|
252
|
-
def do_callbacks(a_url, resp, prior_url)
|
|
253
|
-
cbs = [@callbacks[:every],
|
|
265
|
+
def do_callbacks(a_url, resp, prior_url) # :nodoc:
|
|
266
|
+
cbs = [ @callbacks[:every],
|
|
254
267
|
resp.success? ? @callbacks[:success] : @callbacks[:failure],
|
|
255
|
-
@callbacks[resp.code.to_i]]
|
|
268
|
+
@callbacks[resp.code.to_i] ]
|
|
256
269
|
|
|
257
270
|
cbs.each do |cb|
|
|
258
271
|
cb.call(a_url, resp, prior_url) if cb
|
|
259
272
|
end
|
|
260
273
|
end
|
|
261
274
|
|
|
262
|
-
def generate_next_urls(a_url, resp)
|
|
275
|
+
def generate_next_urls(a_url, resp) # :nodoc:
|
|
276
|
+
# Only scan for links if the content-type is HTML or the URL ends with .html
|
|
277
|
+
content_type = resp["Content-Type"] || resp["content-type"] || ""
|
|
278
|
+
url_ends_with_html = a_url.downcase.end_with?(".html")
|
|
279
|
+
|
|
280
|
+
unless content_type.downcase.include?("text/html") || url_ends_with_html
|
|
281
|
+
return []
|
|
282
|
+
end
|
|
283
|
+
|
|
263
284
|
web_page = resp.body
|
|
264
|
-
base_url = (web_page.scan(/base\s+href="(.*?)"/i).flatten +
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
285
|
+
base_url = (web_page.scan(/base\s+href="(.*?)"/i).flatten + [ a_url ])[0]
|
|
286
|
+
|
|
287
|
+
# Extract each anchor tag once, then pull href and rel from the same tag,
|
|
288
|
+
# respecting rel="nofollow"/"sponsored"/"ugc".
|
|
289
|
+
web_page.scan(/<a\s([^>]*)>/i).flatten.map do |attrs|
|
|
290
|
+
next nil if attrs =~ NOFOLLOW_REL
|
|
291
|
+
|
|
292
|
+
href = attrs[/href\s*=\s*"([^"]*)"/i, 1]
|
|
293
|
+
next nil if href.nil? || href.empty?
|
|
294
|
+
|
|
268
295
|
begin
|
|
269
|
-
parsed_link = URI.parse(
|
|
270
|
-
if parsed_link.fragment ==
|
|
296
|
+
parsed_link = URI.parse(href)
|
|
297
|
+
if parsed_link.fragment == "#"
|
|
271
298
|
nil
|
|
272
299
|
else
|
|
273
|
-
construct_complete_url(base_url,
|
|
300
|
+
construct_complete_url(base_url, href, parsed_link)
|
|
274
301
|
end
|
|
275
|
-
rescue
|
|
302
|
+
rescue StandardError
|
|
276
303
|
nil
|
|
277
304
|
end
|
|
278
305
|
end.compact
|
|
279
306
|
end
|
|
280
307
|
|
|
281
|
-
def construct_complete_url(base_url, additional_url, parsed_additional_url = nil)
|
|
308
|
+
def construct_complete_url(base_url, additional_url, parsed_additional_url = nil) # :nodoc:
|
|
282
309
|
parsed_additional_url ||= URI.parse(additional_url)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
# Include port if it's not the default port
|
|
287
|
-
port_part = (u.port && ((u.scheme == 'http' && u.port != 80) || (u.scheme == 'https' && u.port != 443))) ? ":#{u.port}" : ""
|
|
288
|
-
if additional_url[0].chr == '/'
|
|
289
|
-
"#{u.scheme}://#{u.host}#{port_part}#{additional_url}"
|
|
290
|
-
elsif u.path.nil? || u.path == ''
|
|
291
|
-
"#{u.scheme}://#{u.host}#{port_part}/#{additional_url}"
|
|
292
|
-
elsif u.path[0].chr == '/'
|
|
293
|
-
"#{u.scheme}://#{u.host}#{port_part}#{u.path}/#{additional_url}"
|
|
294
|
-
else
|
|
295
|
-
"#{u.scheme}://#{u.host}#{port_part}/#{u.path}/#{additional_url}"
|
|
296
|
-
end
|
|
297
|
-
else
|
|
298
|
-
additional_url
|
|
299
|
-
end
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
def remove_trailing_slash(s) #:nodoc:
|
|
303
|
-
s.sub(%r{/*$},'')
|
|
310
|
+
return additional_url unless parsed_additional_url.scheme.nil?
|
|
311
|
+
base = base_url.is_a?(URI) ? base_url : URI.parse(base_url.to_s)
|
|
312
|
+
URI.join(base, additional_url).to_s
|
|
304
313
|
end
|
|
305
314
|
|
|
306
|
-
class HeaderSetter
|
|
315
|
+
class HeaderSetter # :nodoc:
|
|
307
316
|
def initialize(si)
|
|
308
317
|
@si = si
|
|
309
318
|
end
|
|
310
|
-
def []=(k,v)
|
|
311
|
-
@si.raw_headers = @si.raw_headers.merge({k => v})
|
|
319
|
+
def []=(k, v)
|
|
320
|
+
@si.raw_headers = @si.raw_headers.merge({ k => v })
|
|
312
321
|
end
|
|
313
322
|
end
|
|
314
323
|
end
|
data/lib/spider.rb
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
require File.dirname(__FILE__)+
|
|
1
|
+
require File.dirname(__FILE__)+"/spider/spider_instance"
|
|
2
2
|
|
|
3
3
|
# A spidering library for Ruby. Handles robots.txt, scraping, finding more
|
|
4
4
|
# links, and doing it all over again.
|
|
5
5
|
class Spider
|
|
6
|
-
|
|
7
6
|
VERSION = File.read(
|
|
8
|
-
File.expand_path(
|
|
7
|
+
File.expand_path("../VERSION", __dir__)
|
|
9
8
|
).strip.freeze
|
|
10
9
|
|
|
11
10
|
def self.version
|
|
@@ -22,6 +21,12 @@ class Spider
|
|
|
22
21
|
# a_url =~ %r{^http://cashcats.biz.*}
|
|
23
22
|
# end
|
|
24
23
|
#
|
|
24
|
+
# # Be polite: wait one second between requests.
|
|
25
|
+
# s.crawl_delay = 1
|
|
26
|
+
#
|
|
27
|
+
# # Stop after fetching 100 pages.
|
|
28
|
+
# s.max_urls = 100
|
|
29
|
+
#
|
|
25
30
|
# s.on 404 do |a_url, resp, prior_url|
|
|
26
31
|
# puts "URL not found: #{a_url}"
|
|
27
32
|
# end
|
|
@@ -37,7 +42,7 @@ class Spider
|
|
|
37
42
|
|
|
38
43
|
def self.start_at(a_url, &block)
|
|
39
44
|
rules = RobotRules.new("Ruby Spider #{Spider::VERSION}")
|
|
40
|
-
a_spider = SpiderInstance.new({nil => [a_url]}, [], rules, [])
|
|
45
|
+
a_spider = SpiderInstance.new({ nil => [ a_url ] }, [], rules, [])
|
|
41
46
|
block.call(a_spider)
|
|
42
47
|
a_spider.start!
|
|
43
48
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spider
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- John Nagro
|
|
@@ -45,7 +45,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
46
|
version: '0'
|
|
47
47
|
requirements: []
|
|
48
|
-
rubygems_version:
|
|
48
|
+
rubygems_version: 4.0.14
|
|
49
49
|
specification_version: 4
|
|
50
50
|
summary: A Web spidering library
|
|
51
51
|
test_files: []
|