Class: Retailer::WebUnblockerApi
- Inherits:
-
Object
- Object
- Retailer::WebUnblockerApi
- Defined in:
- app/services/retailer/web_unblocker_api.rb
Overview
Oxylabs Web Unblocker client — AI-powered proxy for the hardest bot-walls.
Separate product from the Web Scraper API: proxy-style GET through
unblock.oxylabs.io:60000 with header-driven control, separate credentials
(oxylabs_unblocker in credentials), per-GB billing. Used as the
last-resort transport for pages that fault (613) at the Scraper API — see
the WEB_UNBLOCKER_FALLBACK extractor flag and the crawler's web_unblocker
tier.
Defined Under Namespace
Classes: Result
Constant Summary collapse
- PROXY_HOST =
Web Unblocker proxy endpoint (header-driven control).
'unblock.oxylabs.io'- PROXY_PORT =
Web Unblocker proxy port.
60_000- AUTH_REJECTION_STATUSES =
Statuses that mean the proxy refused our credential rather than attempting
the fetch. They cost no budget and signal a configuration fault. [401, 403, 407].freeze
- CREDENTIAL_ALERT_KEY =
Throttle for the dead-credential alert — see #report_credential_failure.
'retailer:web_unblocker:credential_alert'- CREDENTIAL_ALERT_INTERVAL =
1.hour
- DEFAULT_TIMEOUT =
Read timeout in seconds (render waits can be long).
120- US_GEO_LOCATION_POOL =
Pool of US states for geo_location rotation. Diversifies egress geography
per request instead of hammering a target from a single state. Also
referenced by Retailer::Extractors::Wayfair for the same purpose on the
Scraper API. [ 'Illinois,United States', 'California,United States', 'Texas,United States', 'Florida,United States', 'New York,United States', 'Georgia,United States', 'Colorado,United States', 'Washington,United States' ].freeze
Instance Method Summary collapse
-
#configured? ⇒ Boolean
Whether the unblocker credentials are configured — callers treat a false here as "fallback unavailable" and must never see an exception instead.
-
#fetch(url, geo_location: nil) ⇒ Result
Fetch a URL through the unblocker with JS rendering.
-
#initialize(options = {}) ⇒ WebUnblockerApi
constructor
A new instance of WebUnblockerApi.
Constructor Details
#initialize(options = {}) ⇒ WebUnblockerApi
Returns a new instance of WebUnblockerApi.
66 67 68 69 70 71 |
# File 'app/services/retailer/web_unblocker_api.rb', line 66 def initialize( = {}) @username = [:username] || Heatwave::Configuration.fetch(:oxylabs_unblocker, :username) @password = [:password] || Heatwave::Configuration.fetch(:oxylabs_unblocker, :password) @timeout = [:timeout] || DEFAULT_TIMEOUT @logger = [:logger] || Rails.logger end |
Instance Method Details
#configured? ⇒ Boolean
Whether the unblocker credentials are configured — callers treat a false
here as "fallback unavailable" and must never see an exception instead.
76 |
# File 'app/services/retailer/web_unblocker_api.rb', line 76 def configured? = @username.present? && @password.present? |
#fetch(url, geo_location: nil) ⇒ Result
Fetch a URL through the unblocker with JS rendering.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'app/services/retailer/web_unblocker_api.rb', line 84 def fetch(url, geo_location: nil) return Result.new(success: false, error: 'Web Unblocker not configured') unless configured? # Parse/validate first so a malformed URL never consumes daily budget. uri = target_uri(url) # Shares the daily scrape-spend ceiling with the Scraper API, and bills the # same way it does (Retailer::OxylabsApi#submit_job): a pre-flight ceiling # check, then record the spend only once the request actually returns # something. Incrementing up front charged us for every rejection — with the # unblocker credential dead (a 401 on every call, found 2026-08-08) that # silently drained the daily budget on Wayfair's fallback path without ever # fetching a page. A runaway is still bounded, because the ceiling check # counts the spend already recorded. Retailer::DailyCostGuard.ensure_within_budget! response = http(uri).request(build_request(uri, geo_location)) body = response.body.to_s status = response.code.to_i # A rejected credential fetched nothing, so it costs nothing. Anything else # — including a 550 faulted target — means the unblocker did go and try. Retailer::DailyCostGuard.record! unless auth_rejection?(status) if status == 200 && body.present? Result.new(success: true, html: body, status: 200) else report_credential_failure("Web Unblocker returned #{status}") if auth_rejection?(status) Result.new(success: false, status: status, error: "Web Unblocker returned #{response.code}") end rescue Retailer::DailyCostGuard::BudgetExceeded => e Result.new(success: false, error: e.) rescue StandardError => e @logger.error "[WebUnblocker] #{e.class}: #{e.}" report_credential_failure(e.) if (e.) Result.new(success: false, error: "#{e.class}: #{e.}") end |