Skip to content

Commit 548a166

Browse files
committed
rename oobt -> demo directory (more common naming)
improve all demo example add demo example in the README
1 parent a4a4c05 commit 548a166

File tree

9 files changed

+350
-210
lines changed

9 files changed

+350
-210
lines changed

.rubocop.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ Metrics/PerceivedComplexity:
3030
Style/SafeNavigation:
3131
Enabled: false
3232

33+
# buggus check in Rubocop.
34+
# SerpApiClient constructor is rated to 9
35+
# def initialize(params = {})
36+
Metrics/CyclomaticComplexity:
37+
Max: 10
3338

3439
AllCops:
3540
NewCops: enable

README.md

Lines changed: 158 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,16 @@
66
[![Gem Version](https://badge.fury.io/rb/serpapi.svg)](https://badge.fury.io/rb/serpapi) [![serpapi-ruby](https://github.com/serpapi/serpapi-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/serpapi/serpapi-ruby/actions/workflows/ci.yml)
77
</div>
88

9-
Integrate search data into your Ruby application. This library is the official wrapper for SerpApi (https://serpapi.com).
10-
9+
Integrate search data into your Ruby application. This library is the official wrapper for SerpApi (https://serpapi.com).
1110
SerpApi supports Google, Google Maps, Google Shopping, Baidu, Yandex, Yahoo, eBay, App Stores, and more.
11+
Fast query at scale a vast range of data, including web search results, flight schedule, stock market data, news headlines, and more.
12+
13+
## Features
14+
* `persistent`:Keep socket connection open to save on SSL handshake / reconnection (2x
15+
faster). [default: true] [Search at scale](#Search-At-Scale)
16+
* `async`: [Boolean] Support non-blocking job submission. [default: false] [Search Asynchronous](#Search-Asynchronous)
17+
* extensive documentations
18+
* real world examples
1219

1320
## Installation
1421

@@ -32,37 +39,45 @@ $ gem install serpapi
3239

3340
```ruby
3441
require 'serpapi'
35-
client = SerpApi::Client.new(api_key: "serpapi_api_key")
36-
results = client.search(q: "coffee", engine: "google")
42+
client = SerpApi::Client.new(engine: "google", api_key: "<SERPAPI_KEY>")
43+
results = client.search(q: "coffee")
3744
pp results
3845
```
3946

40-
This example runs a search for "coffee" on Google. It then returns the results as a regular Ruby Hash. See the [playground](https://serpapi.com/playground) to generate your own code.
47+
This example runs a search for "coffee" on Google. It then returns the results as a regular Ruby Hash.
48+
See the [playground](https://serpapi.com/playground) to generate your own code.
49+
50+
The `SERPAPI_KEY` should be replaced with your actual API key obtained from
51+
https://serpapi.com/users/sign_up?plan=free.
52+
53+
## Search API advanced Usage
4154

42-
## Advanced Usage
43-
### Search API
4455
```ruby
4556
# load gem
4657
require 'serpapi'
4758

4859
# serpapi client created with default parameters
49-
client = SerpApi::Client.new(engine: 'google', api_key: 'secret_key')
50-
51-
# We recommend that you keep your keys safe.
52-
# At least, don't commit them in plain text.
53-
# More about configuration via environment variables:
54-
# https://hackernoon.com/all-the-secrets-of-encrypting-api-keys-in-ruby-revealed-5qf3t5l
60+
client = SerpApi::Client.new(
61+
engine: 'google',
62+
api_key: ENV['SERPAPI_KEY'],
63+
# HTTP client behavior
64+
async: false, # non blocking HTTP request see [Search Asynchronous](#Search-Asynchronous)
65+
persistent: true, # leave socket connection open for faster response time [Search at scale](#Search-At-Scale)
66+
timeout: 5, # HTTP timeout in seconds on the client side only.
67+
)
5568

5669
# search query overview (more fields available depending on search engine)
5770
params = {
71+
# overview of parameter for Google search engine which one of many search engine supported.
5872
# select the search engine (full list: https://serpapi.com/)
5973
engine: "google",
6074
# actual search query
6175
q: "Coffee",
6276
# then adds search engine specific options.
6377
# for example: google specific parameters: https://serpapi.com/search-api
6478
google_domain: "Google Domain",
65-
location: "Location Requested", # example: Portland,Oregon,United States [ * doc: Location API](#Location-API)
79+
# example: Portland,Oregon,United States [ * doc: Location API](#Location-API)
80+
location: "Location Requested",
6681
device: "desktop|mobile|tablet",
6782
hl: "Google UI Language",
6883
gl: "Google Country",
@@ -71,22 +86,18 @@ params = {
7186
start: "Pagination Offset",
7287
tbm: "nws|isch|shop",
7388
tbs: "custom to be client criteria",
74-
# tweak HTTP client behavior
75-
async: false, # true when async call enabled.
76-
timeout: 60, # HTTP timeout in seconds on the client side only.
7789
}
7890

79-
# formated search results as a Hash
80-
# serpapi.com converts HTML -> JSON
91+
# formatted search results as a Hash
92+
# serpapi.com converts HTML -> JSON
8193
results = client.search(params)
8294

8395
# raw search engine html as a String
84-
# serpapi.com acts a proxy to provive high throughputs, no search limit and more.
85-
raw_html = client.html(parameter)
96+
# serpapi.com acts a proxy to provide high throughputs, no search limit and more.
97+
raw_html = client.html(params) # Corrected parameter reference
8698
```
8799

88100
[SerpApi documentation](https://serpapi.com/search-api).
89-
More hands on examples are available below.
90101

91102
#### Documentations
92103

@@ -96,6 +107,128 @@ More hands on examples are available below.
96107
* [Library GEM page](https://rubygems.org/gems/serpapi/)
97108
* [API health status](https://serpapi.com/status)
98109

110+
## Advanced search API usage
111+
### Search Asynchronous
112+
113+
Search API features non-blocking search using the option: `async=true`.
114+
- Non-blocking - async=true - a single parent process can handle unlimited concurrent searches.
115+
- Blocking - async=false - many processes must be forked and synchronized to handle concurrent searches. This strategy is I/O usage because each client would hold a network connection.
116+
117+
Search API enables `async` search.
118+
- Non-blocking (`async=true`) : the development is more complex, but this allows handling many simultaneous connections.
119+
- Blocking (`async=false`) : it's easy to write the code but more compute-intensive when the parent process needs to hold many connections.
120+
121+
Here is an example of asynchronous searches using Ruby
122+
```ruby
123+
require 'serpapi'
124+
125+
company_list = %w(meta amazon apple netflix google)
126+
client = SerpApi::Client.new(engine: 'google', async: true, persistent: true, api_key: ENV['API_KEY'])
127+
search_queue = Queue.new
128+
company_list.each do |company|
129+
result = client.search({q: company})
130+
if result[:search_metadata][:status] =~ /Cached|Success/
131+
puts "#{company}: search results found in cache for: #{company}"
132+
next
133+
end
134+
135+
search_queue.push(result)
136+
end
137+
138+
puts "wait until all searches are cached or success"
139+
while !search_queue.empty?
140+
result = search_queue.pop
141+
search_id = result[:search_metadata][:id]
142+
143+
search_archived = client.search_archive(search_id)
144+
if search_archived[:search_metadata][:status] =~ /Cached|Success/
145+
puts "#{search_archived[:search_parameters][:q]}: search results found in archive for: #{company}"
146+
next
147+
end
148+
149+
search_queue.push(result)
150+
end
151+
152+
search_queue.close
153+
puts 'done'
154+
```
155+
156+
* source code: [demo/demo_async.rb](https://github.com/serpapi/serpapi-ruby/blob/master/demo/demo_async.rb)
157+
158+
This code shows a simple solution to batch searches asynchronously into a [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)).
159+
Each search takes a few seconds before completion by SerpApi service and the search engine. By the time the first element pops out of the queue. The search result might be already available in the archive. If not, the `search_archive` method blocks until the search results are available.
160+
161+
### Search at scale
162+
The provided code snippet is a Ruby spec test case that demonstrates the use of thread pools to execute multiple HTTP requests concurrently.
163+
164+
```ruby
165+
require 'serpapi'
166+
require 'connection_pool'
167+
168+
# create a thread pool of 4 threads with a persistent connection to serpapi.com
169+
pool = ConnectionPool.new(size: n, timeout: 5) do
170+
SerpApi::Client.new(engine: 'google', api_key: ENV['API_KEY'], timeout: 30, persistent: true)
171+
end
172+
173+
# run user thread to search for your favorites coffee type
174+
threads = %w(latte espresso cappuccino americano mocha macchiato frappuccino cold_brew).map do |query|
175+
Thread.new do
176+
pool.with { |socket| socket.search({q: query }).to_s }
177+
end
178+
end
179+
responses = threads.map(&:value)
180+
```
181+
182+
The code aims to demonstrate how thread pools can be used to
183+
improve performance by executing multiple tasks concurrently. In
184+
this case, it makes multiple HTTP requests to an API endpoint using
185+
a thread pool of persistent connections.
186+
187+
**Benefits:**
188+
189+
* Improved performance by avoiding the overhead of creating and
190+
destroying connections for each request.
191+
* Efficient use of resources by sharing connections among multiple
192+
threads.
193+
* Concurrency and parallelism, allowing multiple requests to be
194+
processed simultaneously.
195+
196+
benchmark: (demo/demo_thread_pool.rb)
197+
198+
### Real world search without persistency
199+
200+
```ruby
201+
require 'serpapi'
202+
203+
raise 'API_KEY environment variable must be set' if ENV['API_KEY'].nil?
204+
205+
default_params = {
206+
engine: 'google_autocomplete',
207+
client: 'safari',
208+
hl: 'en',
209+
gl: 'us',
210+
api_key: ENV['API_KEY'],
211+
persistent: false,
212+
timeout: 2
213+
}
214+
client = SerpApi::Client.new(default_params)
215+
params = {
216+
q: 'coffee'
217+
}
218+
results = client.search(params)
219+
puts 'print suggestions'
220+
if !results[:suggestions] || results[:suggestions].empty?
221+
puts 'no suggestions found'
222+
exit 1
223+
end
224+
pp results[:suggestions]
225+
puts 'done'
226+
exit 0
227+
```
228+
229+
* source code: [demo/demo.rb](https://github.com/serpapi/serpapi-ruby/blob/master/demo/demo.rb)
230+
231+
## APIs supported
99232
### Location API
100233

101234
```ruby
@@ -162,8 +295,6 @@ It prints your account information.
162295

163296
## Basic example per search engine
164297

165-
Here is how to calls the APIs.
166-
167298
### Search google
168299
```ruby
169300
require 'serpapi'
@@ -786,102 +917,14 @@ Most notable improvements:
786917
- Reduce logic complexity in our implementation. (faster performance)
787918
- Better documentation.
788919

789-
## Advanced search API usage
790-
### Highly scalable batching
791-
792-
Search API features non-blocking search using the option: `async=true`.
793-
- Non-blocking - async=true - a single parent process can handle unlimited concurrent searches.
794-
- Blocking - async=false - many processes must be forked and synchronized to handle concurrent searches. This strategy is I/O usage because each client would hold a network connection.
795-
796-
Search API enables `async` search.
797-
- Non-blocking (`async=true`) : the development is more complex, but this allows handling many simultaneous connections.
798-
- Blocking (`async=false`) : it's easy to write the code but more compute-intensive when the parent process needs to hold many connections.
799-
800-
Here is an example of asynchronous searches using Ruby
801-
```ruby
802-
require 'serpapi'
803-
# The code snippet aims to improve the efficiency of searching using the SerpApi client async function. It
804-
# targets companies in the MAANG (Meta, Amazon, Apple, Netflix, Google) group.
805-
#
806-
# **Process:**
807-
# 1. **Request Queue:** The company list is iterated over, and each company is queried using the SerpApi client. Requests
808-
# are stored in a queue to avoid blocking the main thread.
809-
#
810-
# 2. **Client Retrieval:** After each request, the code checks the status of the search result. If it's cached or
811-
# successful, the company name is printed, and the request is skipped. Otherwise, the result is added to the queue for
812-
# further processing.
813-
#
814-
# 3. **Queue Processing:** The queue is processed until it's empty. In each iteration, the last result is retrieved and
815-
# its client ID is extracted.
816-
#
817-
# 4. **Archived Client Retrieval:** Using the client ID, the code retrieves the archived client and checks its status. If
818-
# it's cached or successful, the company name is printed, and the client is skipped. Otherwise, the result is added back
819-
# to the queue for further processing.
820-
#
821-
# 5. **Completion:** The queue is closed, and a message is printed indicating that the process is complete.
822-
#
823-
# * **Asynchronous Requests:** The `async: true` option ensures that search requests are processed in parallel, improving
824-
# efficiency.
825-
# * **Queue Management:** The queue allows requests to be processed asynchronously without blocking the main thread.
826-
# * **Status Checking:** The code checks the status of each search result before processing it, avoiding unnecessary work.
827-
# * **Queue Processing:** The queue ensures that all requests are processed in the order they were submitted.
828-
829-
# **Overall, the code snippet demonstrates a well-structured approach to improve the efficiency of searching for company
830-
# information using SerpApi.**
831-
832-
# load serpapi library
833-
require 'serpapi'
834-
835-
# target MAANG companies
836-
company_list = %w(meta amazon apple netflix google)
837-
client = SerpApi::Client.new(engine: 'google', async: true, persistent: true, api_key: ENV['API_KEY'])
838-
search_queue = Queue.new
839-
company_list.each do |company|
840-
# store request into a search_queue - no-blocker
841-
result = client.search({q: company})
842-
if result[:search_metadata][:status] =~ /Cached|Success/
843-
puts "#{company}: search results found in cache for: #{company}"
844-
next
845-
end
846-
847-
# add results to the client queue
848-
search_queue.push(result)
849-
end
850-
851-
puts "wait until all searches are cached or success"
852-
while !search_queue.empty?
853-
result = search_queue.pop
854-
# extract client id
855-
search_id = result[:search_metadata][:id]
856-
857-
# retrieve client from the archive - blocker
858-
search_archived = client.search_archive(search_id)
859-
if search_archived[:search_metadata][:status] =~ /Cached|Success/
860-
puts "#{search_archived[:search_parameters][:q]}: search results found in archive for: #{company}"
861-
next
862-
end
863-
864-
# add results to the client queue
865-
search_queue.push(result)
866-
end
867-
868-
# destroy the queue
869-
search_queue.close
870-
puts 'done'```
871-
872-
* source code: [oobt/demo_async.rb](https://github.com/serpapi/serpapi-ruby/blob/master/oobt/demo_async.rb)
873-
874-
This code shows a simple solution to batch searches asynchronously into a [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)).
875-
Each search takes a few seconds before completion by SerpApi service and the search engine. By the time the first element pops out of the queue. The search result might be already available in the archive. If not, the `search_archive` method blocks until the search results are available.
876-
877920
## Supported Ruby version.
878921
Ruby versions validated by Github Actions:
879922
- 3.1
880-
- 2.6
923+
- 3.4
881924
* doc: [Github Actions.](https://github.com/serpapi/serpapi-ruby/actions/workflows/ci.yml)
882925

883926
## Change logs
884-
* [2023-02-20] 1.0.0 Full API support
927+
* [2025-07-01] 1.0.0 Full API support
885928

886929
## Developer Guide
887930
### Key goals
@@ -892,7 +935,7 @@ Ruby versions validated by Github Actions:
892935
- Thread safe
893936
- Easy extension
894937
- Defensive code style (raise a custom exception)
895-
- TDD
938+
- TDD - Test driven development
896939
- Best API coding practice per platform
897940
- KiSS principles
898941

@@ -996,6 +1039,3 @@ open coverage/index.html
9961039
Open ./Rakefile for more information.
9971040

9981041
Contributions are welcome. Feel to submit a pull request!
999-
1000-
# TODO
1001-
- [] Release version 1.0.0

0 commit comments

Comments
 (0)