serpapi 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 95f3b097af670a6c28466a55561c547a15a08ef963eb35dd0e10f0429c488051
4
+ data.tar.gz: 4b31b19506dc48a68ebdfb891a26c7124277b371cbf1c213c418cfaf3bbc18d7
5
+ SHA512:
6
+ metadata.gz: 980bf5f4884b299aab3e15c2de5432721ebe655c6df137b410b115c10a9bafb5cb279c4c431ad39d040c6b11df711e74de23c833b2f835f09ebe90b4338ad640
7
+ data.tar.gz: 5c47df09a7f99d725e4e22c82aecfa60ec64631e6697ea1674ff5fb762ae2fd79d077976969a3a0a32df0528638c0628bc9b720561f04257c0d2310ca5bdf1ab
@@ -0,0 +1,256 @@
1
+ # Client implementation for SerpApi.com
2
+ #
3
+ module SerpApi
4
+ # Client for SerpApi.com
5
+ # powered by HTTP.rb
6
+ #
7
+ # features:
8
+ # * async non-block search
9
+ # * persistent HTTP connection
10
+ # * search API
11
+ # * location API
12
+ # * account API
13
+ # * search archive API
14
+ #
15
+ class Client
16
+ # Backend service URL
17
+ BACKEND = 'serpapi.com'.freeze
18
+
19
+ # HTTP timeout requests
20
+ attr_reader :timeout,
21
+ # Query parameters
22
+ :params,
23
+ # HTTP persistent
24
+ :persistent,
25
+ # HTTP.rb client
26
+ :socket
27
+
28
+ # Constructor
29
+ # The `Serpapi::Client` constructor takes a hash of options as input.
30
+ #
31
+ # **Example:**
32
+ #
33
+ # ```ruby
34
+ # require 'serpapi'
35
+ #
36
+ # client = SerpApi::Client.new(
37
+ # api_key: "secure API key",
38
+ # engine: "google",
39
+ # timeout: 30,
40
+ # persistent: true
41
+ # )
42
+ #
43
+ # result = client.search(q: "coffee")
44
+ #
45
+ # client.close
46
+ # ```
47
+ #
48
+ # **Parameters:**
49
+ #
50
+ # * `api_key`: [String] User secret API key.
51
+ # * `engine`: [String] Search engine selected.
52
+ # * `persistent`: [Boolean] Keep socket connection open to save on SSL handshake / connection reconnectino (2x
53
+ # faster). [default: true]
54
+ # * `async`: [Boolean] Support non-blocking job submission. [default: false]
55
+ # * `timeout`: [Integer] HTTP get max timeout in seconds [default: 120s == 2m]
56
+ # * `symbolize_names`: [Boolean] Convert JSON keys to symbols. [default: true]
57
+ #
58
+ # **Key:**kr
59
+ #
60
+ # The `key` parameter can be either a symbol or a string.
61
+ #
62
+ # **Note:**
63
+ #
64
+ # * All parameters are optional.
65
+ # * The `close` method should be called when the client is no longer needed.
66
+ #
67
+ # @param [Hash] params default for the search
68
+ #
69
+ def initialize(params = {})
70
+ raise SerpApiError, 'params cannot be nil' if params.nil?
71
+ raise SerpApiError, "params must be hash, not: #{params.class}" unless params.instance_of?(Hash)
72
+
73
+ # store client HTTP request timeout
74
+ @timeout = params[:timeout] || 120
75
+ @timeout.freeze
76
+
77
+ # enable HTTP persistent mode
78
+ @persistent = true
79
+ @persistent = params[:persistent] if params.key?(:persistent)
80
+ @persistent.freeze
81
+
82
+ # delete this client only configuration keys
83
+ %i[timeout persistent].each do |option|
84
+ params.delete(option) if params.key?(option)
85
+ end
86
+
87
+ # set default query parameters
88
+ @params = params.clone || {}
89
+
90
+ # track ruby library as a client for statistic purpose
91
+ @params[:source] = 'serpapi-ruby:' << SerpApi::VERSION
92
+
93
+ # ensure default parameter would not be modified later
94
+ @params.freeze
95
+
96
+ # create connection socket
97
+ return unless persistent?
98
+
99
+ @socket = HTTP.persistent("https://#{BACKEND}")
100
+ end
101
+
102
+ # perform a search using SerpApi.com
103
+ #
104
+ # see: https://serpapi.com/search-api
105
+ #
106
+ # note that the raw response
107
+ # from the search engine is converted to JSON by SerpApi.com backend.
108
+ # thus, most of the compute power is on the backsdend and not on the client side.
109
+ # @param [Hash] params includes engine, api_key, search fields and more..
110
+ # this override the default params provided to the constructor.
111
+ # @return [Hash] search results formatted as a Hash.
112
+ def search(params = {})
113
+ get('/search', :json, params)
114
+ end
115
+
116
+ # html search perform a search using SerpApi.com
117
+ # the output is raw HTML from the search engine.
118
+ # it is useful for training AI models, RAG, debugging
119
+ # or when you need to parse the HTML yourself.
120
+ #
121
+ # @return [String] raw html search results directly from the search engine.
122
+ def html(params = {})
123
+ get('/search', :html, params)
124
+ end
125
+
126
+ # Get location using Location API
127
+ #
128
+ # example: spec/serpapi/location_api_spec.rb
129
+ # doc: https://serpapi.com/locations-api
130
+ #
131
+ # @param [Hash] params must includes fields: q, limit
132
+ # @return [Array<Hash>] list of matching locations
133
+ def location(params = {})
134
+ get('/locations.json', :json, params)
135
+ end
136
+
137
+ # Retrieve search result from the Search Archive API
138
+ #
139
+ # ```ruby
140
+ # client = SerpApi::Client.new(engine: 'google', api_key: ENV['SERPAPI_KEY'])
141
+ # results = client.search(q: 'Coffee', location: 'Portland')
142
+ # search_id = results[:search_metadata][:id]
143
+ # archive_search = client.search_archive(search_id)
144
+ # ```
145
+ # example: spec/serpapi/client/search_archive_api_spec.rb
146
+ # doc: https://serpapi.com/search-archive-api
147
+ #
148
+ # @param [String|Integer] search_id from original search `results[:search_metadata][:id]`
149
+ # @param [Symbol] format :json or :html [default: json, optional]
150
+ # @return [String|Hash] raw html or JSON / Hash
151
+ def search_archive(search_id, format = :json)
152
+ raise SerpApiError, 'format must be json or html' unless [:json, :html].include?(format)
153
+
154
+ get("/searches/#{search_id}.#{format}", format)
155
+ end
156
+
157
+ # Get account information using Account API
158
+ #
159
+ # example: spec/serpapi/client/account_api_spec.rb
160
+ # doc: https://serpapi.com/account-api
161
+ #
162
+ # @param [String] api_key secret key [optional if already provided to the constructor]
163
+ # @return [Hash] account information
164
+ def account(api_key = nil)
165
+ params = (api_key.nil? ? {} : { api_key: api_key })
166
+ get('/account', :json, params)
167
+ end
168
+
169
+ # @return [String] default search engine
170
+ def engine
171
+ @params[:engine]
172
+ end
173
+
174
+ # @return [String] api_key user secret API key as provided to the constructor
175
+ def api_key
176
+ @params[:api_key]
177
+ end
178
+
179
+ # close open connection if active
180
+ def close
181
+ @socket.close if @socket
182
+ end
183
+
184
+ private
185
+
186
+ # @param [Hash] params to merge with default parameters provided to the constructor.
187
+ # @return [Hash] merged query parameters after cleanup
188
+ def query(params)
189
+ raise SerpApiError, "params must be hash, not: #{params.class}" unless params.instance_of?(Hash)
190
+
191
+ # merge default params with custom params
192
+ q = @params.clone.merge(params)
193
+
194
+ # do not pollute default params with custom params
195
+ q.delete(:symbolize_names) if q.key?(:symbolize_names)
196
+
197
+ # delete empty key/value
198
+ q.compact
199
+ end
200
+
201
+ # @return [Boolean] HTTP session persistent enabled
202
+ def persistent?
203
+ persistent
204
+ end
205
+
206
+ # Perform HTTP GET request to the SerpApi.com backend endpoint.
207
+ #
208
+ # @param [String] endpoint HTTP service URI
209
+ # @param [Symbol] decoder type :json or :html
210
+ # @param [Hash] params custom search inputs
211
+ # @return [String|Hash] raw HTML or decoded response as JSON / Hash
212
+ def get(endpoint, decoder = :json, params = {})
213
+ # execute get via open socket
214
+ response = if persistent?
215
+ @socket.get(endpoint, params: query(params))
216
+ else
217
+ HTTP.timeout(timeout).get("https://#{BACKEND}#{endpoint}", params: query(params))
218
+ end
219
+
220
+ # decode response using JSON native parser
221
+ case decoder
222
+ when :json
223
+ # read http response
224
+ begin
225
+ # user can turn on/off JSON keys to symbols
226
+ # this is more memory efficient, but not always needed
227
+ symbolize_names = params.key?(:symbolize_names) ? params[:symbolize_names] : true
228
+
229
+ # parse JSON response with Ruby standard library
230
+ data = JSON.parse(response.body, symbolize_names: symbolize_names)
231
+ if data.instance_of?(Hash) && data.key?(:error)
232
+ raise SerpApiError, "HTTP request failed with error: #{data[:error]} from url: https://#{BACKEND}#{endpoint}, params: #{params}, decoder: #{decoder}, response status: #{response.status} "
233
+ elsif response.status != 200
234
+ raise SerpApiError, "HTTP request failed with response status: #{response.status} reponse: #{data} on get url: https://#{BACKEND}#{endpoint}, params: #{params}, decoder: #{decoder}"
235
+ end
236
+ rescue JSON::ParserError
237
+ raise SerpApiError, "JSON parse error: #{response.body} on get url: https://#{BACKEND}#{endpoint}, params: #{params}, decoder: #{decoder}, response status: #{response.status}"
238
+ end
239
+
240
+ # discard response body
241
+ response.flush if persistent?
242
+
243
+ data
244
+ when :html
245
+ # html decoder
246
+ if response.status != 200
247
+ raise SerpApiError, "HTTP request failed with response status: #{response.status} reponse: #{data} on get url: https://#{BACKEND}#{endpoint}, params: #{params}, decoder: #{decoder}"
248
+ end
249
+
250
+ response.body
251
+ else
252
+ raise SerpApiError, "not supported decoder: #{decoder}, available: :json, :html"
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,14 @@
1
+ # Module includes SerpApi error handling.
2
+ # frozen_string_literal: true
3
+
4
+ module SerpApi
5
+ # SerpApiError wraps any errors related to the SerpApi client.
6
+ class SerpApiError < StandardError
7
+ # List the specific types of errors handled by the Error class.
8
+ # - HTTP response errors from SerpApi.com
9
+ # - Missing API key
10
+ # - Credit limit
11
+ # - Incorrect query
12
+ # - more ...
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ module SerpApi
2
+ # Current version of the gem
3
+ VERSION = '1.0.0'.freeze
4
+ end
data/lib/serpapi.rb ADDED
@@ -0,0 +1,16 @@
1
+ # Ruby client for SerpApi.com
2
+ # Scrape results for all major search engines from our fast, easy, and complete API.
3
+ module SerpApi
4
+ # see serpapi for implementation
5
+ end
6
+
7
+ # load HTTP
8
+ require 'http'
9
+
10
+ # load
11
+ require 'json'
12
+
13
+ # implementation
14
+ require_relative 'serpapi/version'
15
+ require_relative 'serpapi/error'
16
+ require_relative 'serpapi/client'
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: serpapi
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - victor benarbia
8
+ - Julien Khaleghy
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 13.2.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 13.2.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.9.28
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.9.28
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.75.7
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.75.7
83
+ description: "Integrate powerful search functionality into your Ruby application with
84
+ SerpApi. SerpApi offers official \nsupport for Google, Google Maps, Google Shopping,
85
+ Baidu, Yandex, Yahoo, eBay, App Stores, and more. \nAccess a vast range of data,
86
+ including web search results, local business listings, and product \ninformation."
87
+ email: victor@serpapi.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - lib/serpapi.rb
93
+ - lib/serpapi/client.rb
94
+ - lib/serpapi/error.rb
95
+ - lib/serpapi/version.rb
96
+ homepage: https://github.com/serpapi/serpapi-ruby
97
+ licenses:
98
+ - MIT
99
+ metadata: {}
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '3.1'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubygems_version: 3.6.7
115
+ specification_version: 4
116
+ summary: Official Ruby library for SerpApi.com
117
+ test_files: []