zenrows 0.1.0 → 0.2.1
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/.mcp.json +10 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +28 -0
- data/CLAUDE.md +1 -1
- data/Makefile +19 -0
- data/README.md +100 -19
- data/lib/zenrows/api_client.rb +180 -0
- data/lib/zenrows/api_response.rb +185 -0
- data/lib/zenrows/backends/http_rb.rb +10 -11
- data/lib/zenrows/backends/net_http.rb +141 -0
- data/lib/zenrows/client.rb +35 -9
- data/lib/zenrows/configuration.rb +6 -0
- data/lib/zenrows/css_extractor.rb +111 -0
- data/lib/zenrows/proxy.rb +19 -0
- data/lib/zenrows/version.rb +1 -1
- data/lib/zenrows.rb +10 -2
- data/sig/manifest.yaml +5 -0
- data/sig/zenrows/api_client.rbs +15 -0
- data/sig/zenrows/api_response.rbs +28 -0
- data/sig/zenrows/backends/base.rbs +9 -0
- data/sig/zenrows/backends/http_rb.rbs +3 -0
- data/sig/zenrows/backends/net_http.rbs +28 -0
- data/sig/zenrows/backends.rbs +2 -0
- data/sig/zenrows/client.rbs +11 -0
- data/sig/zenrows/configuration.rbs +20 -0
- data/sig/zenrows/css_extractor.rbs +14 -0
- data/sig/zenrows/errors.rbs +27 -0
- data/sig/zenrows/js_instructions.rbs +28 -0
- data/sig/zenrows/proxy.rbs +14 -0
- data/sig/zenrows.rbs +4 -1
- data/test/zenrows/api_client_test.rb +161 -0
- data/test/zenrows/api_response_test.rb +142 -0
- data/test/zenrows/css_extractor_test.rb +84 -0
- data/test/zenrows/js_instructions_test.rb +2 -1
- data/test/zenrows/proxy_test.rb +39 -0
- metadata +25 -2
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
require "webmock/minitest"
|
|
5
|
+
|
|
6
|
+
class ApiClientTest < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
Zenrows.configure do |config|
|
|
9
|
+
config.api_key = "test_api_key"
|
|
10
|
+
end
|
|
11
|
+
@client = Zenrows::ApiClient.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def teardown
|
|
15
|
+
Zenrows.reset_configuration!
|
|
16
|
+
WebMock.reset!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_initialization_with_global_config
|
|
20
|
+
assert_equal "test_api_key", @client.api_key
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_initialization_with_custom_api_key
|
|
24
|
+
client = Zenrows::ApiClient.new(api_key: "custom_key")
|
|
25
|
+
|
|
26
|
+
assert_equal "custom_key", client.api_key
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_get_basic_request
|
|
30
|
+
stub_request(:get, "https://api.zenrows.com/v1/")
|
|
31
|
+
.with(query: hash_including(apikey: "test_api_key", url: "https://example.com"))
|
|
32
|
+
.to_return(status: 200, body: "<html>Test</html>")
|
|
33
|
+
|
|
34
|
+
response = @client.get("https://example.com")
|
|
35
|
+
|
|
36
|
+
assert_equal 200, response.status
|
|
37
|
+
assert_equal "<html>Test</html>", response.html
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_get_with_autoparse
|
|
41
|
+
stub_request(:get, "https://api.zenrows.com/v1/")
|
|
42
|
+
.with(query: hash_including(autoparse: "true"))
|
|
43
|
+
.to_return(status: 200, body: '{"title":"Product"}')
|
|
44
|
+
|
|
45
|
+
response = @client.get("https://amazon.com/dp/123", autoparse: true)
|
|
46
|
+
|
|
47
|
+
assert_equal "Product", response.parsed["title"]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_get_with_css_extractor_hash
|
|
51
|
+
stub_request(:get, "https://api.zenrows.com/v1/")
|
|
52
|
+
.with(query: hash_including(css_extractor: '{"title":"h1"}'))
|
|
53
|
+
.to_return(status: 200, body: '{"title":"Page Title"}')
|
|
54
|
+
|
|
55
|
+
response = @client.get("https://example.com", css_extractor: {title: "h1"})
|
|
56
|
+
|
|
57
|
+
assert_equal "Page Title", response.extracted["title"]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_get_with_css_extractor_dsl
|
|
61
|
+
extractor = Zenrows::CssExtractor.build do
|
|
62
|
+
extract :title, "h1"
|
|
63
|
+
links :urls, "a"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
stub_request(:get, "https://api.zenrows.com/v1/")
|
|
67
|
+
.with(query: hash_including(css_extractor: '{"title":"h1","urls":"a @href"}'))
|
|
68
|
+
.to_return(status: 200, body: '{"title":"Test","urls":["link1"]}')
|
|
69
|
+
|
|
70
|
+
response = @client.get("https://example.com", css_extractor: extractor)
|
|
71
|
+
|
|
72
|
+
assert_equal "Test", response.extracted["title"]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_get_with_markdown_response
|
|
76
|
+
stub_request(:get, "https://api.zenrows.com/v1/")
|
|
77
|
+
.with(query: hash_including(response_type: "markdown"))
|
|
78
|
+
.to_return(status: 200, body: "# Title\n\nText")
|
|
79
|
+
|
|
80
|
+
response = @client.get("https://example.com", response_type: "markdown")
|
|
81
|
+
|
|
82
|
+
assert_equal "# Title\n\nText", response.markdown
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_get_with_js_render
|
|
86
|
+
stub_request(:get, "https://api.zenrows.com/v1/")
|
|
87
|
+
.with(query: hash_including(js_render: "true"))
|
|
88
|
+
.to_return(status: 200, body: "<html>Rendered</html>")
|
|
89
|
+
|
|
90
|
+
response = @client.get("https://example.com", js_render: true)
|
|
91
|
+
|
|
92
|
+
assert_equal "<html>Rendered</html>", response.html
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_get_with_premium_proxy
|
|
96
|
+
stub_request(:get, "https://api.zenrows.com/v1/")
|
|
97
|
+
.with(query: hash_including(premium_proxy: "true", proxy_country: "us"))
|
|
98
|
+
.to_return(status: 200, body: "OK")
|
|
99
|
+
|
|
100
|
+
response = @client.get("https://example.com", premium_proxy: true, proxy_country: "us")
|
|
101
|
+
|
|
102
|
+
assert_predicate response, :success?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_get_with_antibot
|
|
106
|
+
stub_request(:get, "https://api.zenrows.com/v1/")
|
|
107
|
+
.with(query: hash_including(antibot: "true"))
|
|
108
|
+
.to_return(status: 200, body: "OK")
|
|
109
|
+
|
|
110
|
+
response = @client.get("https://example.com", antibot: true)
|
|
111
|
+
|
|
112
|
+
assert_predicate response, :success?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_authentication_error
|
|
116
|
+
stub_request(:get, /api\.zenrows\.com/)
|
|
117
|
+
.with(query: hash_including(apikey: "test_api_key"))
|
|
118
|
+
.to_return(status: 401, body: "Unauthorized")
|
|
119
|
+
|
|
120
|
+
assert_raises Zenrows::AuthenticationError do
|
|
121
|
+
@client.get("https://example.com")
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_rate_limit_error
|
|
126
|
+
stub_request(:get, /api\.zenrows\.com/)
|
|
127
|
+
.with(query: hash_including(apikey: "test_api_key"))
|
|
128
|
+
.to_return(status: 429, body: "Too Many Requests", headers: {"Retry-After" => "60"})
|
|
129
|
+
|
|
130
|
+
error = assert_raises Zenrows::RateLimitError do
|
|
131
|
+
@client.get("https://example.com")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
assert_equal 60, error.retry_after
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def test_bot_detected_error
|
|
138
|
+
stub_request(:get, /api\.zenrows\.com/)
|
|
139
|
+
.with(query: hash_including(apikey: "test_api_key"))
|
|
140
|
+
.to_return(status: 403, body: "Bot detected")
|
|
141
|
+
|
|
142
|
+
error = assert_raises Zenrows::BotDetectedError do
|
|
143
|
+
@client.get("https://example.com")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
assert_includes error.suggestion, "premium_proxy"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def test_post_request
|
|
150
|
+
stub_request(:post, "https://api.zenrows.com/v1/")
|
|
151
|
+
.with(
|
|
152
|
+
query: hash_including(apikey: "test_api_key", url: "https://example.com"),
|
|
153
|
+
body: "form_data=value"
|
|
154
|
+
)
|
|
155
|
+
.to_return(status: 200, body: "Posted")
|
|
156
|
+
|
|
157
|
+
response = @client.post("https://example.com", body: "form_data=value")
|
|
158
|
+
|
|
159
|
+
assert_predicate response, :success?
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class ApiResponseTest < Minitest::Test
|
|
6
|
+
def test_html_response
|
|
7
|
+
http_response = mock_response("<html><body>Test</body></html>", 200)
|
|
8
|
+
response = Zenrows::ApiResponse.new(http_response)
|
|
9
|
+
|
|
10
|
+
assert_equal "<html><body>Test</body></html>", response.html
|
|
11
|
+
assert_equal "<html><body>Test</body></html>", response.body
|
|
12
|
+
assert_equal 200, response.status
|
|
13
|
+
assert_predicate response, :success?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_json_response_with_html
|
|
17
|
+
body = '{"html":"<html>Test</html>","xhr":[]}'
|
|
18
|
+
http_response = mock_response(body, 200)
|
|
19
|
+
response = Zenrows::ApiResponse.new(http_response, json_response: true)
|
|
20
|
+
|
|
21
|
+
assert_equal "<html>Test</html>", response.html
|
|
22
|
+
assert_empty response.xhr
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_markdown_response
|
|
26
|
+
body = "# Title\n\nParagraph text"
|
|
27
|
+
http_response = mock_response(body, 200)
|
|
28
|
+
response = Zenrows::ApiResponse.new(http_response, response_type: "markdown")
|
|
29
|
+
|
|
30
|
+
assert_equal "# Title\n\nParagraph text", response.markdown
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_autoparse_response
|
|
34
|
+
body = '{"title":"Product Name","price":"$29.99"}'
|
|
35
|
+
http_response = mock_response(body, 200)
|
|
36
|
+
response = Zenrows::ApiResponse.new(http_response, autoparse: true)
|
|
37
|
+
|
|
38
|
+
assert_equal "Product Name", response.parsed["title"]
|
|
39
|
+
assert_equal "$29.99", response.parsed["price"]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_css_extractor_response
|
|
43
|
+
body = '{"title":"Page Title","links":["link1","link2"]}'
|
|
44
|
+
http_response = mock_response(body, 200)
|
|
45
|
+
response = Zenrows::ApiResponse.new(http_response, css_extractor: {title: "h1"})
|
|
46
|
+
|
|
47
|
+
assert_equal "Page Title", response.extracted["title"]
|
|
48
|
+
assert_equal ["link1", "link2"], response.extracted["links"]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_js_instructions_report
|
|
52
|
+
body = '{"html":"...","js_instructions_report":{"instructions_executed":2}}'
|
|
53
|
+
http_response = mock_response(body, 200)
|
|
54
|
+
response = Zenrows::ApiResponse.new(http_response, json_response: true)
|
|
55
|
+
|
|
56
|
+
assert_equal({"instructions_executed" => 2}, response.js_instructions_report)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_headers_accessors
|
|
60
|
+
headers = {
|
|
61
|
+
"Concurrency-Limit" => "200",
|
|
62
|
+
"Concurrency-Remaining" => "199",
|
|
63
|
+
"X-Request-Cost" => "0.001",
|
|
64
|
+
"Zr-Final-Url" => "https://example.com/final"
|
|
65
|
+
}
|
|
66
|
+
http_response = mock_response("", 200, headers)
|
|
67
|
+
response = Zenrows::ApiResponse.new(http_response)
|
|
68
|
+
|
|
69
|
+
assert_equal 200, response.concurrency_limit
|
|
70
|
+
assert_equal 199, response.concurrency_remaining
|
|
71
|
+
assert_in_delta 0.001, response.request_cost
|
|
72
|
+
assert_equal "https://example.com/final", response.final_url
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_success_status_codes
|
|
76
|
+
[200, 201, 204, 299].each do |code|
|
|
77
|
+
http_response = mock_response("", code)
|
|
78
|
+
response = Zenrows::ApiResponse.new(http_response)
|
|
79
|
+
|
|
80
|
+
assert_predicate response, :success?, "Expected #{code} to be success"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_non_success_status_codes
|
|
85
|
+
[400, 401, 403, 404, 500].each do |code|
|
|
86
|
+
http_response = mock_response("", code)
|
|
87
|
+
response = Zenrows::ApiResponse.new(http_response)
|
|
88
|
+
|
|
89
|
+
refute_predicate response, :success?, "Expected #{code} to not be success"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def mock_response(body, status, headers = {})
|
|
96
|
+
MockHttpResponse.new(body, status, headers)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class MockHttpResponse
|
|
100
|
+
attr_reader :body, :headers
|
|
101
|
+
|
|
102
|
+
def initialize(body, status, headers = {})
|
|
103
|
+
@body = MockBody.new(body)
|
|
104
|
+
@status = MockStatus.new(status)
|
|
105
|
+
@headers = MockHeaders.new(headers)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
attr_reader :status
|
|
109
|
+
|
|
110
|
+
class MockBody
|
|
111
|
+
def initialize(content)
|
|
112
|
+
@content = content
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def to_s
|
|
116
|
+
@content
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class MockStatus
|
|
121
|
+
def initialize(code)
|
|
122
|
+
@code = code
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
attr_reader :code
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class MockHeaders
|
|
129
|
+
def initialize(headers)
|
|
130
|
+
@headers = headers
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def [](key)
|
|
134
|
+
@headers[key]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def to_h
|
|
138
|
+
@headers
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class CssExtractorTest < Minitest::Test
|
|
6
|
+
def test_build_with_block
|
|
7
|
+
extractor = Zenrows::CssExtractor.build do
|
|
8
|
+
extract :title, "h1"
|
|
9
|
+
extract :price, ".price"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
assert_equal({title: "h1", price: ".price"}, extractor.to_h)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_extract_with_attribute
|
|
16
|
+
extractor = Zenrows::CssExtractor.new
|
|
17
|
+
extractor.extract(:link, "a.main", attribute: "href")
|
|
18
|
+
|
|
19
|
+
assert_equal({link: "a.main @href"}, extractor.to_h)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_links_helper
|
|
23
|
+
extractor = Zenrows::CssExtractor.new
|
|
24
|
+
extractor.links(:nav_links, "nav a")
|
|
25
|
+
|
|
26
|
+
assert_equal({nav_links: "nav a @href"}, extractor.to_h)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_images_helper
|
|
30
|
+
extractor = Zenrows::CssExtractor.new
|
|
31
|
+
extractor.images(:product_images, "img.product")
|
|
32
|
+
|
|
33
|
+
assert_equal({product_images: "img.product @src"}, extractor.to_h)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_chaining
|
|
37
|
+
extractor = Zenrows::CssExtractor.new
|
|
38
|
+
.extract(:title, "h1")
|
|
39
|
+
.extract(:price, ".price")
|
|
40
|
+
.links(:links, "a")
|
|
41
|
+
|
|
42
|
+
assert_equal 3, extractor.size
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_to_json
|
|
46
|
+
extractor = Zenrows::CssExtractor.build do
|
|
47
|
+
extract :title, "h1"
|
|
48
|
+
extract :link, "a", attribute: "href"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
json = extractor.to_json
|
|
52
|
+
parsed = JSON.parse(json)
|
|
53
|
+
|
|
54
|
+
assert_equal "h1", parsed["title"]
|
|
55
|
+
assert_equal "a @href", parsed["link"]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_empty
|
|
59
|
+
extractor = Zenrows::CssExtractor.new
|
|
60
|
+
|
|
61
|
+
assert_empty extractor
|
|
62
|
+
|
|
63
|
+
extractor.extract(:title, "h1")
|
|
64
|
+
|
|
65
|
+
refute_empty extractor
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_size
|
|
69
|
+
extractor = Zenrows::CssExtractor.build do
|
|
70
|
+
extract :one, ".one"
|
|
71
|
+
extract :two, ".two"
|
|
72
|
+
extract :three, ".three"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
assert_equal 3, extractor.size
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_string_name_converted_to_symbol
|
|
79
|
+
extractor = Zenrows::CssExtractor.new
|
|
80
|
+
extractor.extract("title", "h1")
|
|
81
|
+
|
|
82
|
+
assert_equal({title: "h1"}, extractor.to_h)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -10,7 +10,7 @@ class JsInstructionsTest < Minitest::Test
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
assert_equal 2, instructions.size
|
|
13
|
-
|
|
13
|
+
refute_empty instructions
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def test_click
|
|
@@ -113,6 +113,7 @@ class JsInstructionsTest < Minitest::Test
|
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
json = instructions.to_json
|
|
116
|
+
|
|
116
117
|
assert_equal '[{"click":".btn"},{"wait":500}]', json
|
|
117
118
|
end
|
|
118
119
|
|
data/test/zenrows/proxy_test.rb
CHANGED
|
@@ -111,4 +111,43 @@ class ProxyTest < Minitest::Test
|
|
|
111
111
|
@proxy.build(wait: 200_000)
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
|
+
|
|
115
|
+
def test_build_with_device_mobile
|
|
116
|
+
config = @proxy.build(device: "mobile")
|
|
117
|
+
|
|
118
|
+
assert_includes config[:password], "device=mobile"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_build_with_device_desktop
|
|
122
|
+
config = @proxy.build(device: :desktop)
|
|
123
|
+
|
|
124
|
+
assert_includes config[:password], "device=desktop"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def test_build_with_antibot
|
|
128
|
+
config = @proxy.build(antibot: true)
|
|
129
|
+
|
|
130
|
+
assert_includes config[:password], "antibot=true"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def test_build_with_session_ttl
|
|
134
|
+
config = @proxy.build(session_id: true, session_ttl: "30m")
|
|
135
|
+
|
|
136
|
+
assert_includes config[:password], "session_ttl=30m"
|
|
137
|
+
assert_match(/session_id=\d+/, config[:password])
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_build_with_all_valid_session_ttl_values
|
|
141
|
+
%w[30s 5m 30m 1h 1d].each do |ttl|
|
|
142
|
+
config = @proxy.build(session_ttl: ttl)
|
|
143
|
+
|
|
144
|
+
assert_includes config[:password], "session_ttl=#{ttl}"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def test_raises_on_invalid_session_ttl
|
|
149
|
+
assert_raises ArgumentError do
|
|
150
|
+
@proxy.build(session_ttl: "invalid")
|
|
151
|
+
end
|
|
152
|
+
end
|
|
114
153
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zenrows
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ernest Bursa
|
|
@@ -46,27 +46,50 @@ executables: []
|
|
|
46
46
|
extensions: []
|
|
47
47
|
extra_rdoc_files: []
|
|
48
48
|
files:
|
|
49
|
+
- ".mcp.json"
|
|
49
50
|
- ".standard.yml"
|
|
51
|
+
- ".tool-versions"
|
|
50
52
|
- ".yardopts"
|
|
51
53
|
- CHANGELOG.md
|
|
52
54
|
- CLAUDE.md
|
|
53
55
|
- LICENSE.txt
|
|
56
|
+
- Makefile
|
|
54
57
|
- README.md
|
|
55
58
|
- Rakefile
|
|
56
59
|
- lib/zenrows.rb
|
|
60
|
+
- lib/zenrows/api_client.rb
|
|
61
|
+
- lib/zenrows/api_response.rb
|
|
57
62
|
- lib/zenrows/backends/base.rb
|
|
58
63
|
- lib/zenrows/backends/http_rb.rb
|
|
64
|
+
- lib/zenrows/backends/net_http.rb
|
|
59
65
|
- lib/zenrows/client.rb
|
|
60
66
|
- lib/zenrows/configuration.rb
|
|
67
|
+
- lib/zenrows/css_extractor.rb
|
|
61
68
|
- lib/zenrows/errors.rb
|
|
62
69
|
- lib/zenrows/js_instructions.rb
|
|
63
70
|
- lib/zenrows/proxy.rb
|
|
64
71
|
- lib/zenrows/railtie.rb
|
|
65
72
|
- lib/zenrows/version.rb
|
|
66
73
|
- plan.md
|
|
74
|
+
- sig/manifest.yaml
|
|
67
75
|
- sig/zenrows.rbs
|
|
76
|
+
- sig/zenrows/api_client.rbs
|
|
77
|
+
- sig/zenrows/api_response.rbs
|
|
78
|
+
- sig/zenrows/backends.rbs
|
|
79
|
+
- sig/zenrows/backends/base.rbs
|
|
80
|
+
- sig/zenrows/backends/http_rb.rbs
|
|
81
|
+
- sig/zenrows/backends/net_http.rbs
|
|
82
|
+
- sig/zenrows/client.rbs
|
|
83
|
+
- sig/zenrows/configuration.rbs
|
|
84
|
+
- sig/zenrows/css_extractor.rbs
|
|
85
|
+
- sig/zenrows/errors.rbs
|
|
86
|
+
- sig/zenrows/js_instructions.rbs
|
|
87
|
+
- sig/zenrows/proxy.rbs
|
|
68
88
|
- test/test_helper.rb
|
|
89
|
+
- test/zenrows/api_client_test.rb
|
|
90
|
+
- test/zenrows/api_response_test.rb
|
|
69
91
|
- test/zenrows/client_test.rb
|
|
92
|
+
- test/zenrows/css_extractor_test.rb
|
|
70
93
|
- test/zenrows/js_instructions_test.rb
|
|
71
94
|
- test/zenrows/proxy_test.rb
|
|
72
95
|
- test/zenrows_test.rb
|
|
@@ -86,7 +109,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
86
109
|
requirements:
|
|
87
110
|
- - ">="
|
|
88
111
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: 3.
|
|
112
|
+
version: 3.2.0
|
|
90
113
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
114
|
requirements:
|
|
92
115
|
- - ">="
|