Class: Heatwave::WebSearch

Inherits:
Object
  • Object
show all
Defined in:
app/services/heatwave/web_search.rb

Overview

Provider-agnostic web (SERP) search.

Complement to Crawler: search finds URLs, the crawler reads
them. Backed by Oxylabs' parsed +google_search+ source through the shared
Retailer::OxylabsApi account client, so search spend shares the same
subscription and Retailer::DailyCostGuard daily ceiling as the retailer
probes.

This class is the abstraction seam callers (the Sunny +web_search+ tool,
SEO jobs, anything else) depend on — a second backend (Apify actor,
Perplexity, provider-native search) slots in behind #search without
touching callers.

Examples:

response = Heatwave::WebSearch.search('radiant floor heating cost', limit: 5)
response.items.first.url if response.success?

Defined Under Namespace

Classes: Item, Response

Constant Summary collapse

DEFAULT_LIMIT =

Organic results returned when the caller doesn't ask for a count.

8
MAX_LIMIT =

Hard ceiling on returned results (one SERP page carries ~10 organic).

20
SNIPPET_MAX_CHARS =

Snippets are for relevance triage, not reading — cap them.

320

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client: Retailer::OxylabsApi.new) ⇒ WebSearch

Returns a new instance of WebSearch.

Parameters:



45
46
47
# File 'app/services/heatwave/web_search.rb', line 45

def initialize(client: Retailer::OxylabsApi.new)
  @client = client
end

Class Method Details

.search(query, geo_location: nil, limit: DEFAULT_LIMIT) ⇒ Response

Parameters:

  • query (String)
  • geo_location (String, nil) (defaults to: nil)

    Oxylabs geo string, e.g. "California,United States"

  • limit (Integer) (defaults to: DEFAULT_LIMIT)

    max organic results (1..MAX_LIMIT)

Returns:



40
41
42
# File 'app/services/heatwave/web_search.rb', line 40

def self.search(query, geo_location: nil, limit: DEFAULT_LIMIT)
  new.search(query, geo_location: geo_location, limit: limit)
end

Instance Method Details

#search(query, geo_location: nil, limit: DEFAULT_LIMIT) ⇒ Object

See Also:



50
51
52
53
54
55
56
57
58
59
60
61
# File 'app/services/heatwave/web_search.rb', line 50

def search(query, geo_location: nil, limit: DEFAULT_LIMIT)
  q = query.to_s.strip
  return Response.new(items: [], error: 'query is blank') if q.blank?

  payload = { source: 'google_search', query: q, parse: true }
  payload[:geo_location] = geo_location if geo_location.present?

  result = @client.request(payload)
  return Response.new(items: [], error: result.error.presence || 'search failed') unless result.success?

  Response.new(items: extract_items(result.data, limit: limit.to_i.clamp(1, MAX_LIMIT)), error: nil)
end