zyterb 0.1.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 +7 -0
- data/README.md +86 -0
- data/lib/zyterb/client.rb +116 -0
- data/lib/zyterb.rb +31 -0
- metadata +43 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e61dfe9e5c35f4915994e9bf59bbf3813d3b662da563e97442a23bb6ae8bc9b7
|
|
4
|
+
data.tar.gz: 95bfa6302732227afa6140b88463049de35b175dfbcecfed89854baa1bd2c53c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 60c991c67b8e5ca891e4986b0f5488ccb623171b7c9f1809d5de627df2160df813a42ce9481b1ff852415d034210184517f68244f44013e1794138a796d4ba63
|
|
7
|
+
data.tar.gz: 1cc581b173eeb7babb867ded4a30cdd30dd88c0a851d79eacbf88bd52e2ce05869f204fa64841263e86c427f64aed09e1e5bdd739cc64b44b3083574a3f42943
|
data/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Zyterb
|
|
2
|
+
|
|
3
|
+
A zero-dependency Ruby client for the [Zyte API](https://docs.zyte.com/zyte-api/usage/reference.html). It uses only Ruby's standard library (`Net::HTTP`, `JSON`, `Base64`).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'zyterb'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle install
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install zyterb
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Configuration
|
|
24
|
+
|
|
25
|
+
You can provide your Zyte API key via an environment variable:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
export ZYTE_API_KEY='your_api_key_here'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or via a configuration block:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
require 'zyterb'
|
|
35
|
+
|
|
36
|
+
Zyterb.configure do |config|
|
|
37
|
+
config.api_key = 'your_api_key_here'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
client = Zyterb::Client.new
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or pass it when initializing the client:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
client = Zyterb::Client.new(api_key: 'your_api_key_here')
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Basic Extraction
|
|
50
|
+
|
|
51
|
+
By default, it fetches `browserHtml`:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
result = client.extract("https://example.com")
|
|
55
|
+
puts result['browserHtml']
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### AI Structured Extraction
|
|
59
|
+
|
|
60
|
+
Zyte API supports AI-driven extraction for specific page types:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
# Extract an article
|
|
64
|
+
article = client.extract("https://example.com/blog/post", article: true)
|
|
65
|
+
puts article['article']['headline']
|
|
66
|
+
|
|
67
|
+
# Extract a product
|
|
68
|
+
product = client.extract("https://example.com/p/123", product: true)
|
|
69
|
+
puts product['product']['name']
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Supported AI fields include: `article`, `product`, `jobPosting`, `serp`, and more.
|
|
73
|
+
|
|
74
|
+
### Screenshots
|
|
75
|
+
|
|
76
|
+
The `#screenshot` method returns the raw binary image data (it handles Base64 decoding for you).
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
image_data = client.screenshot("https://example.com", format: 'png', full_page: true)
|
|
80
|
+
|
|
81
|
+
File.binwrite("example.png", image_data)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'uri'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'base64'
|
|
5
|
+
require 'zlib'
|
|
6
|
+
require 'stringio'
|
|
7
|
+
|
|
8
|
+
module Zyterb
|
|
9
|
+
class Client
|
|
10
|
+
API_URL = "https://api.zyte.com/v1/extract".freeze
|
|
11
|
+
|
|
12
|
+
# List of known Zyte AI extraction fields
|
|
13
|
+
AI_FIELDS = [
|
|
14
|
+
:article, :articleList, :articleNavigation,
|
|
15
|
+
:product, :productList, :productNavigation,
|
|
16
|
+
:jobPosting, :jobPostingNavigation,
|
|
17
|
+
:forumThread, :pageContent, :serp
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
attr_reader :api_key
|
|
21
|
+
|
|
22
|
+
def initialize(api_key: nil)
|
|
23
|
+
@api_key = api_key || ENV['ZYTE_API_KEY'] || Zyterb.configuration.api_key
|
|
24
|
+
raise ArgumentError, "API key is required. Set ENV['ZYTE_API_KEY'], use Zyterb.configure, or pass it to Client.new" if @api_key.nil? || @api_key.empty?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Extract data from a URL.
|
|
28
|
+
# @param url [String] The URL to extract data from.
|
|
29
|
+
# @param options [Hash] Additional options for Zyte API (e.g., article: true, browserHtml: true).
|
|
30
|
+
# @return [Hash] The parsed JSON response from Zyte.
|
|
31
|
+
def extract(url, options = {})
|
|
32
|
+
payload = { url: url }.merge(options)
|
|
33
|
+
|
|
34
|
+
# Default to browserHtml if no specific extraction type is requested
|
|
35
|
+
extraction_keys = [:browserHtml, :httpResponseBody, :screenshot] + AI_FIELDS
|
|
36
|
+
if (payload.keys & extraction_keys).empty?
|
|
37
|
+
payload[:browserHtml] = true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
response = request(payload)
|
|
41
|
+
parse_response(response)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Take a screenshot of a URL.
|
|
45
|
+
# @param url [String] The URL to screenshot.
|
|
46
|
+
# @param format [String] 'png' (default) or 'jpeg'.
|
|
47
|
+
# @param full_page [Boolean] Capture the full page or just the viewport.
|
|
48
|
+
# @param options [Hash] Additional options to merge into the payload.
|
|
49
|
+
# @return [String] The raw binary image data.
|
|
50
|
+
def screenshot(url, format: 'png', full_page: false, options: {})
|
|
51
|
+
payload = {
|
|
52
|
+
url: url,
|
|
53
|
+
screenshot: true,
|
|
54
|
+
screenshotOptions: { format: format, fullPage: full_page }
|
|
55
|
+
}.merge(options)
|
|
56
|
+
|
|
57
|
+
response = request(payload)
|
|
58
|
+
parsed = parse_response(response)
|
|
59
|
+
|
|
60
|
+
if parsed['screenshot']
|
|
61
|
+
Base64.decode64(parsed['screenshot'])
|
|
62
|
+
else
|
|
63
|
+
raise Error, "No screenshot returned from Zyte API"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def request(payload)
|
|
70
|
+
uri = URI.parse(API_URL)
|
|
71
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
72
|
+
http.use_ssl = true
|
|
73
|
+
http.read_timeout = 180
|
|
74
|
+
http.open_timeout = 180
|
|
75
|
+
|
|
76
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
|
77
|
+
req.basic_auth(@api_key, "")
|
|
78
|
+
req['Content-Type'] = 'application/json'
|
|
79
|
+
req['Accept-Encoding'] = 'gzip, deflate'
|
|
80
|
+
req.body = JSON.generate(payload)
|
|
81
|
+
|
|
82
|
+
http.request(req)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def parse_response(response)
|
|
86
|
+
body = response.body
|
|
87
|
+
|
|
88
|
+
if response['Content-Encoding'] == 'gzip' && body && !body.empty?
|
|
89
|
+
begin
|
|
90
|
+
body = Zlib::GzipReader.new(StringIO.new(body)).read
|
|
91
|
+
rescue Zlib::Error, Zlib::GzipFile::Error
|
|
92
|
+
# If decompression fails, we'll try to parse the raw body anyway
|
|
93
|
+
end
|
|
94
|
+
elsif response['Content-Encoding'] == 'deflate' && body && !body.empty?
|
|
95
|
+
begin
|
|
96
|
+
body = Zlib::Inflate.inflate(body)
|
|
97
|
+
rescue Zlib::Error
|
|
98
|
+
# If decompression fails, we'll try to parse the raw body anyway
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
begin
|
|
103
|
+
parsed = JSON.parse(body)
|
|
104
|
+
rescue JSON::ParserError
|
|
105
|
+
raise Error, "Invalid JSON response from Zyte API (#{response.code}): #{body.to_s[0..100]}..."
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
109
|
+
error_msg = parsed['detail'] || parsed['title'] || response.message
|
|
110
|
+
raise Error, "Zyte API Error (#{response.code}): #{error_msg}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
parsed
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/zyterb.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "zyterb/client"
|
|
2
|
+
|
|
3
|
+
module Zyterb
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_accessor :api_key
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@api_key = nil
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def configuration
|
|
16
|
+
@configuration ||= Configuration.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def configuration=(config)
|
|
20
|
+
@configuration = config
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def configure
|
|
24
|
+
yield(configuration)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reset
|
|
28
|
+
@configuration = Configuration.new
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: zyterb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Zyterb Author
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Simple Ruby client to extract data and take screenshots using Zyte API.
|
|
13
|
+
email:
|
|
14
|
+
- author@example.com
|
|
15
|
+
executables: []
|
|
16
|
+
extensions: []
|
|
17
|
+
extra_rdoc_files: []
|
|
18
|
+
files:
|
|
19
|
+
- README.md
|
|
20
|
+
- lib/zyterb.rb
|
|
21
|
+
- lib/zyterb/client.rb
|
|
22
|
+
homepage: https://github.com/getletterpress/zyterb
|
|
23
|
+
licenses:
|
|
24
|
+
- MIT
|
|
25
|
+
metadata: {}
|
|
26
|
+
rdoc_options: []
|
|
27
|
+
require_paths:
|
|
28
|
+
- lib
|
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 2.5.0
|
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0'
|
|
39
|
+
requirements: []
|
|
40
|
+
rubygems_version: 3.7.2
|
|
41
|
+
specification_version: 4
|
|
42
|
+
summary: A zero-dependency Ruby client for the Zyte API
|
|
43
|
+
test_files: []
|