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 +7 -0
- data/lib/serpapi/client.rb +256 -0
- data/lib/serpapi/error.rb +14 -0
- data/lib/serpapi/version.rb +4 -0
- data/lib/serpapi.rb +16 -0
- metadata +117 -0
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
|
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: []
|