Class: WebhookProcessors::OxylabsProcessor
- Inherits:
-
Object
- Object
- WebhookProcessors::OxylabsProcessor
- Defined in:
- app/services/webhook_processors/oxylabs_processor.rb
Overview
Processor for Oxylabs webhook callbacks
Handles price check results from Oxylabs Web Scraper API
Constant Summary collapse
- FAULT_RETRY_LIMIT =
A non-done job status (faulted, etc.) is a transient Oxylabs-side scraper
failure, not an unreachable page — one faulted batch run marked 55
reachable Ferguson Home items "unreachable online" (2026-07-24), poisoning
the compliance report and feeding ProbeAutoSkipper. Re-submit the probe,
capped per item/day, before recording a failed probe. 2
Class Method Summary collapse
-
.call(webhook_log) ⇒ Hash
Outcome summary, stored on the log's response_data.
Instance Method Summary collapse
-
#call ⇒ Hash
Outcome summary, stored on the log's response_data.
-
#initialize(webhook_log, api: nil) ⇒ OxylabsProcessor
constructor
A new instance of OxylabsProcessor.
Constructor Details
#initialize(webhook_log, api: nil) ⇒ OxylabsProcessor
Returns a new instance of OxylabsProcessor.
28 29 30 31 32 |
# File 'app/services/webhook_processors/oxylabs_processor.rb', line 28 def initialize(webhook_log, api: nil) @webhook_log = webhook_log @data = webhook_log.data @api = api || Retailer::OxylabsApi.new end |
Class Method Details
.call(webhook_log) ⇒ Hash
Returns outcome summary, stored on the log's response_data.
21 22 23 |
# File 'app/services/webhook_processors/oxylabs_processor.rb', line 21 def self.call(webhook_log) new(webhook_log).call end |
Instance Method Details
#call ⇒ Hash
Returns outcome summary, stored on the log's response_data.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'app/services/webhook_processors/oxylabs_processor.rb', line 35 def call job_id = @data['id'] status = @data['status'] catalog_item_id = @webhook_log.resource_id url_used = @data['url'] Rails.logger.info "[OxylabsProcessor] Processing job #{job_id} for CatalogItem #{catalog_item_id}" # Find catalog item first - needed for creating probe records catalog_item = CatalogItem.find_by(id: catalog_item_id) unless catalog_item Rails.logger.warn "[OxylabsProcessor] CatalogItem #{catalog_item_id} not found" return { status: 'error', reason: 'CatalogItem not found' } end # Handle non-done statuses: retry first (transient scraper fault), and # only record a failed probe once the daily retry budget is exhausted. unless status == 'done' Rails.logger.warn "[OxylabsProcessor] Job #{job_id} not done (status: #{status})" if retry_faulted_job?(catalog_item) RetailerProbeWorker.perform_async(catalog_item_id: catalog_item_id) return { status: 'retrying', reason: "Job status is #{status}, re-submitted probe", oxylabs_status: status, url_used: url_used, catalog_item_id: catalog_item_id, catalog_item_sku: catalog_item.sku } end # Retry budget exhausted — one last transport before recording the # failure: the Web Unblocker (opt-in retailers only, e.g. Wayfair). fallback = Retailer::WebhookResultProcessor.new.probe_via_unblocker(catalog_item, url_used) if fallback return { status: fallback.status, reason: "Job status is #{status}, recovered via Web Unblocker", probe_id: fallback.id, oxylabs_status: status, url_used: url_used, catalog_item_id: catalog_item_id, catalog_item_sku: catalog_item.sku } end # Retry budget exhausted — create a probe record documenting the failed attempt probe = CatalogItemRetailerProbe.create!( catalog_item_id: catalog_item_id, status: 'failed', url: url_used, error_message: "Oxylabs job #{status}: #{extract_error_reason}", page_accessible: false, geo_location: geo_location ) return { status: 'failed', reason: "Job status is #{status}, not done", probe_id: probe.id, oxylabs_status: status, url_used: url_used, catalog_item_id: catalog_item_id, catalog_item_sku: catalog_item.sku } end # Fetch results from Oxylabs API using job_id api_result = @api.job_results(job_id) unless api_result.success? Rails.logger.error "[OxylabsProcessor] Failed to fetch results for job #{job_id}: #{api_result.error}" # Create probe record for API fetch failure probe = create_failed_probe(catalog_item_id, url_used, "API fetch failed: #{api_result.error}") return { status: 'api_error', job_id: job_id, probe_id: probe.id, error: api_result.error, url_used: url_used, catalog_item_id: catalog_item_id, catalog_item_sku: catalog_item.sku } end results = api_result.data if results.blank? Rails.logger.warn "[OxylabsProcessor] Job #{job_id} returned empty results" # A contentless done-job gets the Web Unblocker fallback too (opt-in # retailers) before recording a failure. fallback = Retailer::WebhookResultProcessor.new.probe_via_unblocker(catalog_item, url_used) if fallback return { status: fallback.status, reason: 'Empty results, recovered via Web Unblocker', probe_id: fallback.id, job_id: job_id, url_used: url_used, catalog_item_id: catalog_item_id, catalog_item_sku: catalog_item.sku } end # Create probe record for empty results probe = create_failed_probe(catalog_item_id, url_used, 'Oxylabs returned empty results') return { status: 'empty_results', job_id: job_id, probe_id: probe.id, url_used: url_used, catalog_item_id: catalog_item_id, catalog_item_sku: catalog_item.sku } end # Delegate to the existing processor for extraction processor = Retailer::WebhookResultProcessor.new result = processor.process(@data.merge('_catalog_item_id' => catalog_item_id)) Rails.logger.info "[OxylabsProcessor] Completed processing job #{job_id}" # Capture content size from API response for troubleshooting api_content_size = results.first&.dig('content').to_s.size # Return detailed result hash for WebhookLog#response_data if result.is_a?(CatalogItemRetailerProbe) { status: result.status, probe_id: result.id, price: result.price&.to_f, currency: result.currency, page_accessible: result.page_accessible, product_available: result.product_available, url: result.url&.truncate(200), error_message: result., content_size_bytes: result.content_size_bytes || api_content_size, response_time_ms: result.response_time_ms, catalog_item_id: catalog_item_id, catalog_item_sku: catalog_item.sku }.compact else { status: 'processing_failed', job_id: job_id, content_size_bytes: api_content_size, catalog_item_id: catalog_item_id, catalog_item_sku: catalog_item.sku } end end |