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.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +86 -0
  3. data/lib/zyterb/client.rb +116 -0
  4. data/lib/zyterb.rb +31 -0
  5. 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: []