diff --git a/composer.json b/composer.json
index c29449e..e2774e9 100644
--- a/composer.json
+++ b/composer.json
@@ -19,6 +19,7 @@
],
"require": {
"php": "~8.3.1|~8.4.1",
+ "always-open/laravel-request-logger": "^3.0",
"illuminate/contracts": "~10.0||~11.0||~12.0",
"spatie/laravel-data": "~4.16",
"spatie/laravel-package-tools": "^1.16"
@@ -74,4 +75,4 @@
},
"minimum-stability": "dev",
"prefer-stable": true
-}
\ No newline at end of file
+}
diff --git a/config/oxylabs-api.php b/config/oxylabs-api.php
index b9350d8..0c8654d 100644
--- a/config/oxylabs-api.php
+++ b/config/oxylabs-api.php
@@ -5,4 +5,5 @@
'username' => env('OXYLAB_API_USERNAME'),
'password' => env('OXYLAB_API_PASSWORD'),
'auth_method' => env('OXYLAB_API_AUTH_METHOD', 'basic'),
+ 'request_logging_enabled' => env('OXYLABS_API_REQUEST_LOGGING_ENABLED', true),
];
diff --git a/config/request-logger.php b/config/request-logger.php
new file mode 100644
index 0000000..aca6145
--- /dev/null
+++ b/config/request-logger.php
@@ -0,0 +1,33 @@
+ '_request_logs',
+
+ /*
+ * This is the default suffix applied to models' class names.
+ *
+ * Example: The facebook object name would have a request log model of FacebookRequestLog.
+ */
+ 'model_suffix' => 'RequestLog',
+
+ 'model_path' => app_path('Models'),
+
+ 'model_stub' => __DIR__.'/../stubs/model.stub',
+
+ 'migration_path' => database_path('migrations'),
+
+ 'migration_stub' => __DIR__.'/../stubs/migration.stub',
+
+ /*
+ * Enable the process stamps (sub) package to log which process/url/job invoked the call
+ */
+ 'enable_process_stamps' => true,
+
+ /*
+ * Precision value used in generating occurred_at for request log
+ */
+ 'log_timestamp_precision' => 3,
+];
diff --git a/database/migrations/2000_01_01_000001_create_oxylabs_api_reqeust_logger_table.php b/database/migrations/2000_01_01_000001_create_oxylabs_api_reqeust_logger_table.php
new file mode 100644
index 0000000..d4f53e6
--- /dev/null
+++ b/database/migrations/2000_01_01_000001_create_oxylabs_api_reqeust_logger_table.php
@@ -0,0 +1,53 @@
+id();
+ // Will store the relative path of the request (e.g. /addresses/validate)
+ $table->string('path', 191)
+ ->index();
+ // What parameters were passed in (e.g. ?status=new)
+ $table->string('params', 512)
+ ->nullable()
+ ->fulltext();
+ // HTTP method (e.g. POST/PUT/DELETE)
+ $table->string('http_method', 10)
+ ->index();
+ // Status code (e.g. 200, 204, 429)
+ $table->smallInteger('response_code', autoIncrement: false, unsigned: true)
+ ->nullable()
+ ->index();
+ // The entire JSON encoded payload of the request
+ $table->json('body')
+ ->nullable();
+ // The headers that were part of the request
+ $table->json('request_headers')
+ ->nullable();
+ // The entire JSON encoded responses
+ $table->json('response')
+ ->nullable();
+ // The headers that were part of the response
+ $table->json('response_headers')
+ ->nullable();
+ // Internal exceptions that occurred during the request
+ $table->string('exception')
+ ->nullable();
+ // When the request was resolved to the millisecond
+ $table->timestamp('occurred_at', 3)->index();
+ $table->timestamps(precision: 3);
+ $table->processIds();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('oxylabs_api_request_logger');
+ }
+};
diff --git a/database/migrations/create_oxylabs_api_table.php.stub b/database/migrations/create_oxylabs_api_table.php.stub
deleted file mode 100644
index ce83abf..0000000
--- a/database/migrations/create_oxylabs_api_table.php.stub
+++ /dev/null
@@ -1,19 +0,0 @@
-id();
-
- // add fields
-
- $table->timestamps();
- });
- }
-};
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 229319e..0e69d55 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -10,5 +10,4 @@ parameters:
checkOctaneCompatibility: true
checkModelProperties: true
ignoreErrors:
- - '#Trait AlwaysOpen\\OxylabsApi\\Traits\\APIStatus is used zero times and is not analysed.#'
- - '#Call to function is_string\(\) with string will always evaluate to true.#'
\ No newline at end of file
+ - '#Trait AlwaysOpen\\OxylabsApi\\Traits\\APIStatus is used zero times and is not analysed.#'
\ No newline at end of file
diff --git a/phpunit.xml b/phpunit.xml
index 6c3dd5d..e218cdb 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,23 +1,30 @@
+ xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
+ bootstrap="vendor/autoload.php"
+ cacheDirectory=".phpunit.cache"
+ executionOrder="depends,defects"
+ shortenArraysForExportThreshold="10"
+ requireCoverageMetadata="false"
+ beStrictAboutCoverageMetadata="true"
+ beStrictAboutOutputDuringTests="true"
+ displayDetailsOnPhpunitDeprecations="true"
+ failOnPhpunitDeprecation="true"
+ failOnRisky="true"
+ failOnWarning="true">
- tests
+ tests
-
-
-
-
-
-
-
+
+
- src
+ src
+
+
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index ee68745..8096beb 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,31 +1,33 @@
-
+
-
+
tests
-
+
+
- ./src
+ src
+
+
+
+
\ No newline at end of file
diff --git a/src/DTOs/Amazon/AmazonSellerResponse.php b/src/DTOs/Amazon/AmazonSellerResponse.php
index 591fe4b..864adc3 100644
--- a/src/DTOs/Amazon/AmazonSellerResponse.php
+++ b/src/DTOs/Amazon/AmazonSellerResponse.php
@@ -14,7 +14,7 @@ class AmazonSellerResponse extends Data
public function __construct(
/* @var AmazonSellerResult[] $results */
#[DataCollectionOf(AmazonSellerResult::class)]
- public readonly array $results,
- public readonly PushPullJob $job,
+ public readonly ?array $results,
+ public readonly ?PushPullJob $job,
) {}
}
diff --git a/src/DTOs/Google/GoogleShoppingProductResponse.php b/src/DTOs/Google/GoogleShoppingProductResponse.php
index 006bd01..33c3b0b 100644
--- a/src/DTOs/Google/GoogleShoppingProductResponse.php
+++ b/src/DTOs/Google/GoogleShoppingProductResponse.php
@@ -15,7 +15,7 @@ class GoogleShoppingProductResponse extends Data
public function __construct(
/* @var GoogleShoppingProductResult[] $results */
#[DataCollectionOf(GoogleShoppingProductResult::class)]
- public readonly DataCollection $results,
- public readonly PushPullJob $job,
+ public readonly ?DataCollection $results,
+ public readonly ?PushPullJob $job,
) {}
}
diff --git a/src/DTOs/UniversalResponse.php b/src/DTOs/UniversalResponse.php
index 82b07a4..27d8798 100644
--- a/src/DTOs/UniversalResponse.php
+++ b/src/DTOs/UniversalResponse.php
@@ -13,7 +13,7 @@ class UniversalResponse extends Data
public function __construct(
/* @var UniversalResult[] $results */
#[DataCollectionOf(UniversalResult::class)]
- public readonly array $results,
- public readonly PushPullJob $job,
+ public readonly ?array $results,
+ public readonly ?PushPullJob $job,
) {}
}
diff --git a/src/Models/OxylabsApiRequestLogger.php b/src/Models/OxylabsApiRequestLogger.php
new file mode 100644
index 0000000..f8dcfdf
--- /dev/null
+++ b/src/Models/OxylabsApiRequestLogger.php
@@ -0,0 +1,24 @@
+ [
'Authorization' => 'Bearer '.$this->password,
],
- default => throw new \InvalidArgumentException('Invalid authentication method')
+ default => throw new InvalidArgumentException('Invalid authentication method')
};
}
@@ -77,97 +85,118 @@ protected function getBaseRequest(): Factory|PendingRequest
}
/**
- * @throws ConnectionException
+ * @throws RuntimeException
*/
- public function makeRequest(string $source, array $payload, ?int $allowedRetries = null, int $retries = 0): PushPullJob
+ public function makePostRequest(string $source, array $payload, ?int $allowedRetries = null): PushPullJob
{
- $response = $this->getBaseRequest()
- ->post($this->baseUrl.'/queries', [
- 'source' => $source,
- ...$payload,
- ]);
+ try {
+ $response = $this->makeRequest(
+ 'post',
+ $this->baseUrl.'/queries',
+ [
+ 'source' => $source,
+ ...$payload,
+ ],
+ $allowedRetries ?? 0,
+ );
+
+ if (! $response->successful()) {
+ throw new RuntimeException('API request failed: '.$response->getBody()->getContents(), $response->getStatusCode());
+ }
- if (! $response->successful()) {
- if (str_contains($response->body(), 'Too many requests') && $retries < ($allowedRetries ?? $this->defaultAllowedDispatchRetries ?? 0)) {
- sleep(1);
+ return PushPullJob::from(json_decode($response->getBody()->getContents(), true));
+ } catch (Throwable $e) {
+ throw new RuntimeException('API request failed: '.$e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * @throws GuzzleException|Throwable
+ */
+ protected function makeRequest(string $method, string $uri, ?array $payload = null, ?int $retryCount = null): Response
+ {
+ $request = new Request(
+ method: $method,
+ uri: $uri,
+ headers: $this->getAuthHeader(),
+ body: $payload ? json_encode($payload) : null,
+ );
+
+ if (config('oxylabs-api.request_logging_enabled', true)) {
+ $logger = OxylabsApiRequestLogger::makeFromGuzzle($request);
+ $logger->save();
+ }
- return $this->makeRequest($source, $payload, $allowedRetries, ++$retries);
+ /**
+ * @var Response $response
+ */
+ $response = retry($retryCount ?? 0, function () use ($request, $method, $payload): PromiseInterface|Response {
+ if (strtolower($method) === 'post') {
+ return Http::withHeaders($request->getHeaders())
+ ->post($request->getUri(), $payload)
+ ->throw();
+ } else {
+ return Http::withHeaders($request->getHeaders())
+ ->get($request->getUri())
+ ->throw();
}
- throw new RuntimeException('API request failed: '.$response->body());
+ }, 2000);
+
+ if (config('oxylabs-api.request_logging_enabled', true)) {
+ $logger->updateFromResponse($response->toPsrResponse());
}
- return PushPullJob::from($response->json());
+ return $response;
}
/**
- * @throws ConnectionException
* @throws RuntimeException
*/
public function getResult(
string $job_id,
?string $type = null,
- int $retryCount = 0,
): ?array {
try {
- $response = $this->getBaseRequest()
- ->get($this->baseUrl."/queries/$job_id/results".($type ? "?type=$type" : ''));
- } catch (\Exception $e) {
- if (
- $retryCount < 3
- && (
- str_contains($e->getMessage(), 'Too many request')
- || str_contains($e->getMessage(), 'SSL')
- )
- ) {
- sleep(1);
-
- return $this->getresult($job_id, $type, ++$retryCount);
- }
-
- throw $e;
+ $response = $this->makeRequest(
+ 'get',
+ $this->baseUrl."/queries/$job_id/results".($type ? "?type=$type" : ''),
+ null,
+ 3
+ );
+ } catch (Throwable $e) {
+ throw new RuntimeException('API request failed: '.$e->getMessage(), $e->getCode(), $e);
}
if (! $response->successful()) {
- throw new RuntimeException('API request failed: '.$response->body(), $response->getStatusCode());
+ throw new RuntimeException('API request failed: '.$response->getBody()->getContents(), $response->getStatusCode());
}
- return $response->json();
+ return json_decode($response->getBody()->getContents(), true);
}
/**
* @throws ConnectionException
+ * @throws Throwable
*/
- public function getPushPullJob(string $job_id, int $retryCount = 0): PushPullJob
+ public function getPushPullJob(string $job_id): PushPullJob
{
- try {
- $response = $this->getBaseRequest()
- ->get($this->baseUrl."/queries/$job_id");
- } catch (\Exception $e) {
- if (
- $retryCount < 3
- && (
- str_contains($e->getMessage(), 'Too many request')
- || str_contains($e->getMessage(), 'SSL')
- )
- ) {
- sleep(1);
-
- return $this->getPushPullJob($job_id, ++$retryCount);
- }
-
- throw $e;
- }
+ $response = $this->makeRequest(
+ 'get',
+ $this->baseUrl."/queries/$job_id",
+ retryCount: 3,
+ );
if (! $response->successful()) {
- throw new RuntimeException('API request failed: '.$response->body());
+ throw new RuntimeException('API request failed: '.$response->getBody()->getContents(), $response->getStatusCode());
}
- return PushPullJob::from($response->json());
+ return PushPullJob::from(json_decode($response->getBody()->getContents(), true));
}
/**
* @throws ConnectionException
* @throws RuntimeException
+ * @throws Throwable
*/
public function getPushPullResults(
string $job_id,
@@ -177,23 +206,25 @@ public function getPushPullResults(
?string $type = null,
): ?array {
if ($check_status) {
- $count = 0;
- do {
- $count++;
- $job = $this->getPushPullJob($job_id);
- if ($job->isPending()) {
- sleep($status_wait_seconds);
- }
- // @TODO handle faulted status
- } while ($job->isPending() && $count < $status_check_limit);
+ retry(
+ $status_check_limit,
+ function () use ($job_id): PushPullJob {
+ $job = $this->getPushPullJob($job_id);
+ if (! $job->isDone()) {
+ throw new Exception("Job $job_id not completed");
+ }
+
+ return $job;
+ },
+ $status_wait_seconds,
+ );
}
return $this->getResult($job_id, $type);
}
/**
- * @throws ConnectionException
- * @throws RuntimeException
+ * @throws ConnectionException|Throwable
*/
public function getAmazonProductResult(
string $job_id,
@@ -208,8 +239,7 @@ public function getAmazonProductResult(
}
/**
- * @throws ConnectionException
- * @throws RuntimeException
+ * @throws ConnectionException|RuntimeException|Throwable
*/
public function getAmazonResult(
string $job_id,
@@ -224,8 +254,7 @@ public function getAmazonResult(
}
/**
- * @throws ConnectionException
- * @throws RuntimeException
+ * @throws ConnectionException|Throwable
*/
public function getUniversalResult(
string $job_id,
@@ -240,8 +269,7 @@ public function getUniversalResult(
}
/**
- * @throws ConnectionException
- * @throws RuntimeException
+ * @throws ConnectionException|Throwable
*/
public function getAmazonPricingResult(
string $job_id,
@@ -257,7 +285,7 @@ public function getAmazonPricingResult(
/**
* @throws ConnectionException
- * @throws RuntimeException
+ * @throws Throwable
*/
public function getAmazonSellerResult(
string $job_id,
@@ -288,80 +316,72 @@ public function makeBatchRequest(BatchRequest $payload): PushPullBatchJobRespons
}
/**
- * @throws ConnectionException
* @throws RuntimeException
*/
public function amazon(AmazonRequest $request, ?int $allowedRetries = null): PushPullJob
{
- return $this->makeRequest(OxylabsApi::TARGET_AMAZON, $request->toArray(), $allowedRetries);
+ return $this->makePostRequest(OxylabsApi::TARGET_AMAZON, $request->toArray(), $allowedRetries);
}
/**
- * @throws ConnectionException
* @throws RuntimeException
*/
public function amazonProduct(AmazonProductRequest $request, ?int $allowedRetries = null): PushPullJob
{
- return $this->makeRequest(OxylabsApi::SOURCE_AMAZON_PRODUCT, $request->toArray(), $allowedRetries);
+ return $this->makePostRequest(OxylabsApi::SOURCE_AMAZON_PRODUCT, $request->toArray(), $allowedRetries);
}
/**
- * @throws ConnectionException
* @throws RuntimeException
*/
public function amazonSearch(AmazonSearchRequest $request, ?int $allowedRetries = null): PushPullJob
{
- return $this->makeRequest(OxylabsApi::SOURCE_AMAZON_SEARCH, $request->toArray(), $allowedRetries);
+ return $this->makePostRequest(OxylabsApi::SOURCE_AMAZON_SEARCH, $request->toArray(), $allowedRetries);
}
/**
- * @throws ConnectionException
* @throws RuntimeException
*/
public function amazonPricing(AmazonPricingRequest $request, ?int $allowedRetries = null): PushPullJob
{
- return $this->makeRequest(OxylabsApi::SOURCE_AMAZON_PRICING, $request->toArray(), $allowedRetries);
+ return $this->makePostRequest(OxylabsApi::SOURCE_AMAZON_PRICING, $request->toArray(), $allowedRetries);
}
/**
- * @throws ConnectionException
* @throws RuntimeException
*/
public function amazonSellers(AmazonSellersRequest $request, ?int $allowedRetries = null): PushPullJob
{
- return $this->makeRequest(OxylabsApi::SOURCE_AMAZON_SELLERS, $request->toArray(), $allowedRetries);
+ return $this->makePostRequest(OxylabsApi::SOURCE_AMAZON_SELLERS, $request->toArray(), $allowedRetries);
}
/**
- * @throws ConnectionException
* @throws RuntimeException
*/
public function googleSearch(GoogleSearchRequest $request, ?int $allowedRetries = null): PushPullJob
{
- return $this->makeRequest(OxylabsApi::SOURCE_GOOGLE_SHOPPING_SEARCH, $request->toArray(), $allowedRetries);
+ return $this->makePostRequest(OxylabsApi::SOURCE_GOOGLE_SHOPPING_SEARCH, $request->toArray(), $allowedRetries);
}
/**
- * @throws ConnectionException
* @throws RuntimeException
*/
public function universal(UniversalRequest $request, ?int $allowedRetries = null): PushPullJob
{
- return $this->makeRequest('universal', $request->toArray(), $allowedRetries);
+ return $this->makePostRequest('universal', $request->toArray(), $allowedRetries);
}
/**
- * @throws ConnectionException
* @throws RuntimeException
*/
public function googleShoppingProduct(GoogleShoppingProductRequest $request, ?int $allowedRetries = null): PushPullJob
{
- return $this->makeRequest(OxylabsApi::SOURCE_GOOGLE_SHOPPING_PRODUCT, $request->toArray(), $allowedRetries);
+ return $this->makePostRequest(OxylabsApi::SOURCE_GOOGLE_SHOPPING_PRODUCT, $request->toArray(), $allowedRetries);
}
/**
* @throws ConnectionException
- * @throws RuntimeException
+ * @throws Throwable
*/
public function getGoogleShoppingProductResult(
string $job_id,
@@ -376,17 +396,15 @@ public function getGoogleShoppingProductResult(
}
/**
- * @throws ConnectionException
* @throws RuntimeException
*/
public function googleShoppingPricing(GoogleShoppingPricingRequest $request, ?int $allowedRetries = null): PushPullJob
{
- return $this->makeRequest(OxylabsApi::SOURCE_GOOGLE_SHOPPING_PRICING, $request->toArray(), $allowedRetries);
+ return $this->makePostRequest(OxylabsApi::SOURCE_GOOGLE_SHOPPING_PRICING, $request->toArray(), $allowedRetries);
}
/**
- * @throws ConnectionException
- * @throws RuntimeException
+ * @throws ConnectionException|Throwable
*/
public function getGoogleShoppingPricingResult(
string $job_id,
@@ -401,14 +419,17 @@ public function getGoogleShoppingPricingResult(
}
/**
- * @throws ConnectionException
* @throws RuntimeException
*/
public function walmartProduct(WalmartProductRequest $request, ?int $allowedRetries = null): PushPullJob
{
- return $this->makeRequest(OxylabsApi::SOURCE_WALMART_PRODUCT, $request->toArray(), $allowedRetries);
+ return $this->makePostRequest(OxylabsApi::SOURCE_WALMART_PRODUCT, $request->toArray(), $allowedRetries);
}
+ /**
+ * @throws Throwable
+ * @throws ConnectionException
+ */
public function getWalmartProductResult(
string $job_id,
bool $check_status = false,
diff --git a/src/OxylabsApiServiceProvider.php b/src/OxylabsApiServiceProvider.php
index e738b7f..cea52a9 100644
--- a/src/OxylabsApiServiceProvider.php
+++ b/src/OxylabsApiServiceProvider.php
@@ -24,5 +24,11 @@ public function boot()
__DIR__.'/../config/oxylabs-api.php' => config_path('oxylabs-api.php'),
__DIR__.'/../config/data.php' => config_path('data.php'),
], 'config');
+
+ if (config('oxylabs-api.request_logging_enabled', true)) {
+ $this->publishesMigrations([
+ __DIR__.'/../migrations/*' => database_path('migrations/'),
+ ]);
+ }
}
}
diff --git a/tests/BaseTest.php b/tests/BaseTest.php
index ce4ad77..085b09e 100644
--- a/tests/BaseTest.php
+++ b/tests/BaseTest.php
@@ -22,15 +22,15 @@ protected function setUp(): void
$this->refreshApplication();
}
- protected function getFixtureJsonContent(string $name): array
+ protected function getFixtureJsonContent(string $name): string
{
$content = $this->getFixtureContent($name);
if ($content) {
- return json_decode($content, true);
+ return $content;
}
- return [];
+ return '{}';
}
protected function getFixtureContent(string $name): false|string
diff --git a/tests/Feature/OxylabsApiClientTest.php b/tests/Feature/OxylabsApiClientTest.php
index 7780cfe..7a7421a 100644
--- a/tests/Feature/OxylabsApiClientTest.php
+++ b/tests/Feature/OxylabsApiClientTest.php
@@ -15,10 +15,17 @@
use AlwaysOpen\OxylabsApi\OxylabsApiClient;
use AlwaysOpen\OxylabsApi\Tests\BaseTest;
use Illuminate\Http\Client\ConnectionException;
+use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Http;
class OxylabsApiClientTest extends BaseTest
{
+ protected function setUp(): void
+ {
+ parent::setUp();
+ Config::set('oxylabs-api.request_logging_enabled', false);
+ }
+
public function test_amazon_product()
{
Http::fake([
diff --git a/tests/Feature/OxylabsApiFacadeTest.php b/tests/Feature/OxylabsApiFacadeTest.php
index 2ef5820..fdfe1e9 100644
--- a/tests/Feature/OxylabsApiFacadeTest.php
+++ b/tests/Feature/OxylabsApiFacadeTest.php
@@ -5,6 +5,7 @@
use AlwaysOpen\OxylabsApi\DTOs\Amazon\AmazonProductRequest;
use AlwaysOpen\OxylabsApi\OxylabsApiFacade;
use AlwaysOpen\OxylabsApi\Tests\BaseTest;
+use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Http;
class OxylabsApiFacadeTest extends BaseTest
@@ -14,13 +15,14 @@ protected function setUp(): void
parent::setUp();
Http::preventStrayRequests();
+ Config::set('oxylabs-api.request_logging_enabled', false);
}
public function test_amazon_product()
{
Http::fake([
'data.oxylabs.io/v1/queries' => Http::response($this->getFixtureJsonContent('push_pull_job.json'), 200),
- 'data.oxylabs.io/v1/queries/7342973874281147393/results/?type=parsed' => Http::response($this->getFixtureJsonContent('amazon_product_result.json'), 200),
+ 'data.oxylabs.io/v1/queries/7342973874281147393/results?type=parsed' => Http::response($this->getFixtureJsonContent('amazon_product_result.json'), 200),
]);
$request = new AmazonProductRequest(
diff --git a/tests/Fixtures/amazon_product_listing2.json b/tests/Fixtures/amazon_product_listing2.json
new file mode 100644
index 0000000..8f6db68
--- /dev/null
+++ b/tests/Fixtures/amazon_product_listing2.json
@@ -0,0 +1,573 @@
+{
+ "results": [
+ {
+ "content": {
+ "ads": [
+ {
+ "pos": 1,
+ "asin": "B0CQRPQK5Q",
+ "type": "organic_also_viewed",
+ "price": 279.99,
+ "title": "Ironton Jobsite Box \u2014 36in. x 16in.",
+ "images": [
+ "https:\/\/m.media-amazon.com\/images\/I\/818am14BKqL._AC_SS57_.jpg"
+ ],
+ "rating": 4.2,
+ "location": "carousel",
+ "price_upper": 279.99,
+ "reviews_count": 42,
+ "is_prime_eligible": true
+ },
+ {
+ "pos": 2,
+ "asin": "B0D4QXHYRJ",
+ "type": "organic_also_viewed",
+ "price": 419.99,
+ "title": "Northern Tool Jobsite Storage Box, 5 Cu. Ft., 32in.W, Model# 2032-NTE",
+ "images": [
+ "https:\/\/m.media-amazon.com\/images\/I\/61ivHly3XqL._AC_SS57_.jpg"
+ ],
+ "rating": 5,
+ "location": "carousel",
+ "price_upper": 419.99,
+ "reviews_count": 50,
+ "is_prime_eligible": true
+ },
+ {
+ "pos": 3,
+ "asin": "B084XV6W7P",
+ "type": "organic_also_viewed",
+ "price": 579.99,
+ "title": "Crescent JOBOX 48\" Tradesman Steel Chest - CJB637990",
+ "images": [
+ "https:\/\/images-na.ssl-images-amazon.com\/images\/I\/61brwGWxQPL._AC_UL165_SR165,165_.jpg"
+ ],
+ "rating": 0,
+ "location": "carousel",
+ "price_upper": 579.99,
+ "reviews_count": 45,
+ "is_prime_eligible": false
+ },
+ {
+ "pos": 4,
+ "asin": "B0D4QXHYRJ",
+ "type": "organic_also_viewed",
+ "price": 419.99,
+ "title": "Northern Tool Jobsite Storage Box, 5 Cu. Ft., 32in.W, Model# 2032-NTE",
+ "images": [
+ "https:\/\/images-na.ssl-images-amazon.com\/images\/I\/61ivHly3XqL._AC_UL165_SR165,165_.jpg"
+ ],
+ "rating": 0,
+ "location": "carousel",
+ "price_upper": 419.99,
+ "reviews_count": 50,
+ "is_prime_eligible": false
+ },
+ {
+ "pos": 5,
+ "asin": "B0CQRPQK5Q",
+ "type": "organic_also_viewed",
+ "price": 279.99,
+ "title": "Ironton Jobsite Box \u2014 36in. x 16in.",
+ "images": [
+ "https:\/\/images-na.ssl-images-amazon.com\/images\/I\/818am14BKqL._AC_UL165_SR165,165_.jpg"
+ ],
+ "rating": 0,
+ "location": "carousel",
+ "price_upper": 279.99,
+ "reviews_count": 42,
+ "is_prime_eligible": false
+ },
+ {
+ "pos": 6,
+ "asin": "B0BXL5KRRV",
+ "type": "organic_also_viewed",
+ "price": 41.98,
+ "title": "WELKINLAND Heavy-Duty Canvas Tool Pouch - 4PCS, 12\" Waxed Canvas Zipper Tool Pouch",
+ "images": [
+ "data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' height='100%' width='100%' preserveAspectRatio='none' \/%3E",
+ "https:\/\/m.media-amazon.com\/images\/I\/416cNDgvK4L._AC_SR100,100_QL65_.jpg"
+ ],
+ "rating": 4.6,
+ "location": "carousel",
+ "price_upper": 58.68,
+ "reviews_count": 46,
+ "is_prime_eligible": false
+ },
+ {
+ "pos": 7,
+ "asin": "B0DSBP7QFB",
+ "type": "organic_also_viewed",
+ "price": 726.89,
+ "title": "Black Aluminum Contractor Top Side Tool Boxes with Two Lower Drawers, 60''X13.3''X21'' (Black)",
+ "images": [
+ "data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' height='100%' width='100%' preserveAspectRatio='none' \/%3E",
+ "https:\/\/m.media-amazon.com\/images\/I\/41nchtL5Y7L._AC_SR100,100_QL65_.jpg"
+ ],
+ "rating": 0,
+ "location": "carousel",
+ "price_upper": 726.89,
+ "reviews_count": 0,
+ "is_prime_eligible": false
+ },
+ {
+ "pos": 8,
+ "asin": "B0FL1TXVSB",
+ "type": "organic_also_viewed",
+ "price": 219.99,
+ "title": "OAKANDO 7-Drawer Metal Rolling Tool Chest with Wheels,Tool Storage Cabinet with Locking System\uff0cToolbox with Wheels for Garage,Workshop, Repair Shop(Black, 7-Drawer)",
+ "images": [
+ "data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' height='100%' width='100%' preserveAspectRatio='none' \/%3E",
+ "https:\/\/m.media-amazon.com\/images\/I\/41Qx0w4botL._AC_SR100,100_QL65_.jpg"
+ ],
+ "rating": 4.6,
+ "location": "carousel",
+ "price_upper": 219.99,
+ "reviews_count": 46,
+ "is_prime_eligible": false
+ },
+ {
+ "pos": 9,
+ "asin": "B09KGF9GD9",
+ "type": "organic_also_viewed",
+ "price": 24.68,
+ "title": "WORKPRO 16-inch Wide Mouth Tool Bag, Heavy Duty Cloth Tool Storage Bag with Water Proof Molded Base, Adjustable Shoulder Strap",
+ "images": [
+ "data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' height='100%' width='100%' preserveAspectRatio='none' \/%3E",
+ "https:\/\/m.media-amazon.com\/images\/I\/51fbVH+4YbL._AC_SR100,100_QL65_.jpg"
+ ],
+ "rating": 4.7,
+ "location": "carousel",
+ "price_upper": 25.98,
+ "reviews_count": 47,
+ "is_prime_eligible": false
+ },
+ {
+ "pos": 10,
+ "asin": "B0DZWZNZ83",
+ "type": "organic_also_viewed",
+ "price": 738.99,
+ "title": "BeoneYuu Black Heavy-duty Aluminum Contractor Top Side Tool Boxes with Two Lower Drawers, 60''X13.3''X21''",
+ "images": [
+ "data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' height='100%' width='100%' preserveAspectRatio='none' \/%3E",
+ "https:\/\/m.media-amazon.com\/images\/I\/31dGP4Bv46L._AC_SR100,100_QL65_.jpg"
+ ],
+ "rating": 0,
+ "location": "carousel",
+ "price_upper": 738.99,
+ "reviews_count": 0,
+ "is_prime_eligible": false
+ }
+ ],
+ "url": "https:\/\/www.amazon.com\/dp\/B084Y7CHX5?th=1&psc=1&language=en_US",
+ "asin": "B084Y7CHX5",
+ "page": 1,
+ "brand": "JOBOX",
+ "price": 0,
+ "stock": "",
+ "title": "Crescent JOBOX 36\" Site-Vault Heavy-Duty Chest - 2-652990",
+ "coupon": "",
+ "images": [
+ "https:\/\/m.media-amazon.com\/images\/I\/61aBCsKc6CL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/71ZKcGpihHL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/71SpvaqqVtL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/710Wtj95vFL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/61TdSGzI6zL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/716IyNJGt2L._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/616Mn8OeRNL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/6181A4vJv4L._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/612mgUyrRyL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/61e59bzI3VL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/61Rd3nKho4L._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/61b9RSokuoL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/71J+pe2Z8AL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/71jlEWYRO1L._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/71LjLd6fMXL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/71LjLd6fMXL._AC_SL1500_.jpg",
+ "https:\/\/m.media-amazon.com\/images\/I\/6150cjjXv0L._AC_SL1500_.jpg"
+ ],
+ "rating": 4.3,
+ "reviews": [],
+ "category": [
+ {
+ "ladder": [
+ {
+ "url": "\/Tools-and-Home-Improvement\/b\/ref=dp_bc_1?ie=UTF8&node=228013",
+ "name": "Tools & Home Improvement"
+ },
+ {
+ "url": "\/power-tools-hand-tools\/b\/ref=dp_bc_2?ie=UTF8&node=328182011",
+ "name": "Power & Hand Tools"
+ },
+ {
+ "url": "\/tool-organizer\/b\/ref=dp_bc_3?ie=UTF8&node=13400691",
+ "name": "Tool Organizers"
+ },
+ {
+ "url": "\/Tool-Boxes\/b\/ref=dp_bc_4?ie=UTF8&node=13400721",
+ "name": "Tool Boxes"
+ }
+ ]
+ }
+ ],
+ "currency": "USD",
+ "delivery": [],
+ "page_type": "Product",
+ "price_sns": 0,
+ "store_url": "\/stores\/Crescent+JOBOX\/page\/AE867E11-B7A1-440D-BE13-E57B19A5E27D?is_byline_deeplink=true&deeplink=72F53DBD-57A5-483D-8216-538240B5F05C&redirect_store_id=AE867E11-B7A1-440D-BE13-E57B19A5E27D&lp_asin=B084Y7CHX5&ref_=ast_bln&store_ref=bl_ast_dp_brandLogo_sto",
+ "variation": [
+ {
+ "asin": "B084Y7X5RG",
+ "selected": false,
+ "dimensions": {
+ "Size": "48\"",
+ "Style": "Heavy-Duty High Capacity"
+ }
+ },
+ {
+ "asin": "B084Y3VWTC",
+ "selected": false,
+ "dimensions": {
+ "Size": "42\"",
+ "Style": "Heavy-Duty"
+ }
+ },
+ {
+ "asin": "B084Y7P6D7",
+ "selected": false,
+ "dimensions": {
+ "Size": "48\"",
+ "Style": "Heavy-Duty"
+ }
+ },
+ {
+ "asin": "B084Y7CHX5",
+ "selected": true,
+ "dimensions": {
+ "Size": "36\"",
+ "Style": "Heavy-Duty"
+ }
+ },
+ {
+ "asin": "B084Y6M494",
+ "selected": false,
+ "dimensions": {
+ "Size": "60\"",
+ "Style": "Heavy-Duty"
+ }
+ },
+ {
+ "asin": "B084Y3N14N",
+ "selected": false,
+ "dimensions": {
+ "Size": "30\"",
+ "Style": "Heavy-Duty"
+ }
+ }
+ ],
+ "has_videos": true,
+ "sales_rank": [
+ {
+ "rank": 517300,
+ "ladder": [
+ {
+ "url": "\/gp\/bestsellers\/hi\/ref=pd_zg_ts_hi",
+ "name": "Tools & Home Improvement "
+ }
+ ]
+ },
+ {
+ "rank": 1515,
+ "ladder": [
+ {
+ "url": "\/gp\/bestsellers\/hi\/13400721\/ref=pd_zg_hrsr_hi",
+ "name": "Tool Boxes"
+ }
+ ]
+ }
+ ],
+ "asin_in_url": "B084Y7CHX5",
+ "description": "Crescent JOBOX is the leader in on-site storage security, organization, and safety with its high-strength, chamfered lid, commercial gas springs* for lid control and its Site-Vault\u2122 security system. 4-way EZ-Loader skid bolsters and slip-resistant textured powder paint help provide safety and handling ease (* On 48 and longer chests)\"",
+ "parent_asin": "B08TNZFC6Q",
+ "price_upper": 0,
+ "pricing_str": "",
+ "pricing_url": "https:\/\/www.amazon.com\/gp\/offer-listing\/B084Y7CHX5?startIndex=0",
+ "manufacturer": "Crescent JOBOX",
+ "price_buybox": -1,
+ "product_name": "Crescent JOBOX 36\" Site-Vault Heavy-Duty Chest - 2-652990",
+ "bullet_points": "Site-Vault security system is a patented system with 3 locking points and a reflective orange lock indicator that shows when the box is locked or unlocked\nHeavy-duty beveled edge lid eliminates sharp front lid corners and enhances lid strength\nDual built-in lid storage with integrated cargo nets is perfect for rain gear, PPE or small tools\nEZ-Lock support arm secures and locks lid in open position for safe use\nUpgraded lock system now allows usage of padlock models Master #1, #5, #175 and American #50\nIntegrated sidewall cargo bin provides convenient storage for small tools and parts\nErgonomic, lifting handles are protected inside deep recesses to prevent pinched knuckles and damage\nHeavy-duty EZ-Loader 4-way skid bolsters allow fork lift loading from any side for quicker and easier transport\nSkid bolsters are compatible with casters for easy mobility (1-320993 not included)\nHeavy-duty hinge is staked and welded to prevent the pin from being forced out for break in",
+ "price_initial": 0,
+ "pricing_count": 1,
+ "reviews_count": 16,
+ "sns_discounts": [],
+ "developer_info": [],
+ "price_shipping": 0,
+ "product_details": {
+ "upc": "\u200e043419957480",
+ "asin": "B084Y7CHX5",
+ "size": "\u200e36\"",
+ "brand": "\u200eJOBOX",
+ "color": "\u200eOrange",
+ "shape": "\u200eRectangular",
+ "style": "\u200eHeavy-Duty",
+ "usage": "\u200eProfessional",
+ "pattern": "\u200eSolid",
+ "material": "\u200eAlloy Steel",
+ "item_weight": "\u200e98 pounds",
+ "part_number": "\u200e2-652990",
+ "manufacturer": "\u200eApex Tool Group",
+ "customer_reviews": "4.3 4.3 out of 5 stars 16 ratings 4.3 out of 5 stars",
+ "number_of_pieces": "\u200e1",
+ "best_sellers_rank": "#517,300 in Tools & Home Improvement (See Top 100 in Tools & Home Improvement) #1,515 in Tool Boxes",
+ "item_model_number": "\u200e2-652990",
+ "batteries_required": "\u200eNo",
+ "product_dimensions": "\u200e35.91\"L x 19.97\"W x 27.44\"H",
+ "included_components": "\u200e(1) 36\" Site-Vault\u2122 Heavy-Duty Chest",
+ "date_first_available": "February 18, 2020",
+ "warranty_description": "\u200e1 year limited warranty",
+ "item_package_quantity": "\u200e1",
+ "number_of_compartments": "\u200e1",
+ "water_resistance_level": "\u200eWater Resistant"
+ },
+ "featured_merchant": [],
+ "is_prime_eligible": true,
+ "parse_status_code": 12000,
+ "technical_details": [
+ {
+ "name": "Brand",
+ "value": "JOBOX"
+ },
+ {
+ "name": "Material",
+ "value": "Alloy Steel"
+ },
+ {
+ "name": "Color",
+ "value": "Orange"
+ },
+ {
+ "name": "Product Dimensions",
+ "value": "35.91\"L x 19.97\"W x 27.44\"H"
+ },
+ {
+ "name": "Water Resistance Level",
+ "value": "Water Resistant"
+ },
+ {
+ "name": "Number of Compartments",
+ "value": "1"
+ },
+ {
+ "name": "UPC",
+ "value": "043419957480"
+ },
+ {
+ "name": "Manufacturer",
+ "value": "Apex Tool Group"
+ },
+ {
+ "name": "Part Number",
+ "value": "2-652990"
+ },
+ {
+ "name": "Item Weight",
+ "value": "98 pounds"
+ },
+ {
+ "name": "Item model number",
+ "value": "2-652990"
+ },
+ {
+ "name": "Size",
+ "value": "36\""
+ },
+ {
+ "name": "Style",
+ "value": "Heavy-Duty"
+ },
+ {
+ "name": "Pattern",
+ "value": "Solid"
+ },
+ {
+ "name": "Shape",
+ "value": "Rectangular"
+ },
+ {
+ "name": "Item Package Quantity",
+ "value": "1"
+ },
+ {
+ "name": "Number Of Pieces",
+ "value": "1"
+ },
+ {
+ "name": "Usage",
+ "value": "Professional"
+ },
+ {
+ "name": "Included Components",
+ "value": "(1) 36\" Site-Vault\u2122 Heavy-Duty Chest"
+ },
+ {
+ "name": "Batteries Required?",
+ "value": "No"
+ },
+ {
+ "name": "Warranty Description",
+ "value": "1 year limited warranty"
+ }
+ ],
+ "product_dimensions": "\u200e35.91\"L x 19.97\"W x 27.44\"H",
+ "warranty_and_support": {
+ "links": [
+ {
+ "url": "\/gp\/help\/customer\/display.html\/ref=orc_hp_s_retpol?ie=UTF8&nodeId=201819200",
+ "title": "here"
+ }
+ ],
+ "description": "Amazon.com Return Policy: Amazon.com Voluntary 30-Day Return Guarantee: You can return many items you have purchased within 30 days following delivery of the item to you. Our Voluntary 30-Day Return Guarantee does not affect your legal right of withdrawal in any way. You can find out more about the exceptions and conditions here."
+ },
+ "answered_questions_count": 0,
+ "rating_stars_distribution": [
+ {
+ "rating": 5,
+ "percentage": 75
+ },
+ {
+ "rating": 4,
+ "percentage": 8
+ },
+ {
+ "rating": 3,
+ "percentage": 0
+ },
+ {
+ "rating": 2,
+ "percentage": 8
+ },
+ {
+ "rating": 1,
+ "percentage": 9
+ }
+ ]
+ },
+ "created_at": "2025-08-15 15:44:04",
+ "updated_at": "2025-08-15 15:44:07",
+ "page": 1,
+ "url": "https:\/\/www.amazon.com\/dp\/B084Y7CHX5?th=1&psc=1&language=en_US",
+ "job_id": "7362147074835487745",
+ "is_render_forced": false,
+ "status_code": 200,
+ "type": "parsed",
+ "parser_type": "",
+ "parser_preset": null
+ }
+ ],
+ "job": {
+ "callback_url": null,
+ "client_id": 83306,
+ "context": [
+ {
+ "key": "force_headers",
+ "value": false
+ },
+ {
+ "key": "force_cookies",
+ "value": false
+ },
+ {
+ "key": "hc_policy",
+ "value": true
+ },
+ {
+ "key": "autoselect_variant",
+ "value": true
+ },
+ {
+ "key": "check_empty_geo",
+ "value": null
+ },
+ {
+ "key": "safe_search",
+ "value": true
+ },
+ {
+ "key": "currency",
+ "value": "USD"
+ }
+ ],
+ "created_at": "2025-08-15 15:44:04",
+ "domain": "com",
+ "geo_location": null,
+ "id": "7362147074835487745",
+ "limit": 10,
+ "locale": null,
+ "pages": 1,
+ "parse": true,
+ "parser_type": null,
+ "parser_preset": null,
+ "parsing_instructions": null,
+ "browser_instructions": null,
+ "render": null,
+ "xhr": false,
+ "markdown": false,
+ "url": null,
+ "query": "B084Y7CHX5",
+ "source": "amazon_product",
+ "start_page": 1,
+ "status": "done",
+ "storage_type": null,
+ "storage_url": null,
+ "subdomain": "www",
+ "content_encoding": "utf-8",
+ "updated_at": "2025-08-15 15:44:07",
+ "user_agent_type": "desktop",
+ "session_info": null,
+ "statuses": [],
+ "client_notes": null,
+ "_links": [
+ {
+ "rel": "self",
+ "href": "http:\/\/data.oxylabs.io\/v1\/queries\/7362147074835487745",
+ "method": "GET"
+ },
+ {
+ "rel": "results",
+ "href": "http:\/\/data.oxylabs.io\/v1\/queries\/7362147074835487745\/results",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content",
+ "href_list": [
+ "http:\/\/data.oxylabs.io\/v1\/queries\/7362147074835487745\/results\/1\/content"
+ ],
+ "method": "GET"
+ },
+ {
+ "rel": "results-html",
+ "href": "http:\/\/data.oxylabs.io\/v1\/queries\/7362147074835487745\/results?type=raw",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content-html",
+ "href_list": [
+ "http:\/\/data.oxylabs.io\/v1\/queries\/7362147074835487745\/results\/1\/content?type=raw"
+ ],
+ "method": "GET"
+ },
+ {
+ "rel": "results-parsed",
+ "href": "http:\/\/data.oxylabs.io\/v1\/queries\/7362147074835487745\/results?type=parsed",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content-parsed",
+ "href_list": [
+ "http:\/\/data.oxylabs.io\/v1\/queries\/7362147074835487745\/results\/1\/content?type=parsed"
+ ],
+ "method": "GET"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/Fixtures/amazon_product_listing_faulted2.json b/tests/Fixtures/amazon_product_listing_faulted2.json
new file mode 100644
index 0000000..2ac165d
--- /dev/null
+++ b/tests/Fixtures/amazon_product_listing_faulted2.json
@@ -0,0 +1,123 @@
+{
+ "results": [
+ {
+ "content": "",
+ "created_at": "2025-09-01 12:20:03",
+ "updated_at": "2025-09-01 12:20:04",
+ "page": 1,
+ "url": "https://www.amazon.com/gp/aod/ajax/ref=dp_aod_unknown_mbc?asin=B0DY98N6BR&pageno=1&language=en_US",
+ "job_id": "7368256325517601793",
+ "is_render_forced": false,
+ "status_code": 404,
+ "type": "parsed",
+ "parser_type": "",
+ "parser_preset": null
+ }
+ ],
+ "job": {
+ "callback_url": null,
+ "client_id": 83306,
+ "context": [
+ {
+ "key": "force_headers",
+ "value": false
+ },
+ {
+ "key": "force_cookies",
+ "value": false
+ },
+ {
+ "key": "hc_policy",
+ "value": true
+ },
+ {
+ "key": "condition",
+ "value": null
+ },
+ {
+ "key": "check_empty_geo",
+ "value": null
+ },
+ {
+ "key": "safe_search",
+ "value": true
+ },
+ {
+ "key": "currency",
+ "value": "USD"
+ }
+ ],
+ "created_at": "2025-09-01 12:20:03",
+ "domain": "com",
+ "geo_location": null,
+ "id": "7368256325517601793",
+ "limit": 10,
+ "locale": null,
+ "pages": 1,
+ "parse": true,
+ "parser_type": null,
+ "parser_preset": null,
+ "parsing_instructions": null,
+ "browser_instructions": null,
+ "render": null,
+ "xhr": false,
+ "markdown": false,
+ "url": null,
+ "query": "B0DY98N6BR",
+ "source": "amazon_pricing",
+ "start_page": 1,
+ "status": "faulted",
+ "storage_type": null,
+ "storage_url": null,
+ "subdomain": "www",
+ "content_encoding": "utf-8",
+ "updated_at": "2025-09-01 12:20:04",
+ "user_agent_type": "desktop",
+ "session_info": null,
+ "statuses": [],
+ "client_notes": null,
+ "_links": [
+ {
+ "rel": "self",
+ "href": "http://data.oxylabs.io/v1/queries/7368256325517601793",
+ "method": "GET"
+ },
+ {
+ "rel": "results",
+ "href": "http://data.oxylabs.io/v1/queries/7368256325517601793/results",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content",
+ "href_list": [
+ "http://data.oxylabs.io/v1/queries/7368256325517601793/results/1/content"
+ ],
+ "method": "GET"
+ },
+ {
+ "rel": "results-html",
+ "href": "http://data.oxylabs.io/v1/queries/7368256325517601793/results?type=raw",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content-html",
+ "href_list": [
+ "http://data.oxylabs.io/v1/queries/7368256325517601793/results/1/content?type=raw"
+ ],
+ "method": "GET"
+ },
+ {
+ "rel": "results-parsed",
+ "href": "http://data.oxylabs.io/v1/queries/7368256325517601793/results?type=parsed",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content-parsed",
+ "href_list": [
+ "http://data.oxylabs.io/v1/queries/7368256325517601793/results/1/content?type=parsed"
+ ],
+ "method": "GET"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/Fixtures/google_shopping_faulted_result.json b/tests/Fixtures/google_shopping_faulted_result.json
new file mode 100644
index 0000000..f62ff8f
--- /dev/null
+++ b/tests/Fixtures/google_shopping_faulted_result.json
@@ -0,0 +1,135 @@
+{
+ "results": [
+ {
+ "content": "",
+ "created_at": "2025-09-01 12:55:43",
+ "updated_at": "2025-09-01 12:56:01",
+ "page": 1,
+ "url": "https://www.google.com/shopping/product/9949842269271681420/offers?gl=us&hl=en",
+ "job_id": "7368265301332333570",
+ "is_render_forced": false,
+ "status_code": 200,
+ "type": "parsed",
+ "parser_type": "",
+ "parser_preset": null
+ }
+ ],
+ "job": {
+ "callback_url": null,
+ "client_id": 83306,
+ "context": [
+ {
+ "key": "force_headers",
+ "value": false
+ },
+ {
+ "key": "force_cookies",
+ "value": false
+ },
+ {
+ "key": "hc_policy",
+ "value": true
+ }
+ ],
+ "created_at": "2025-09-01 12:55:43",
+ "domain": "com",
+ "geo_location": "United States",
+ "id": "7368265301332333570",
+ "limit": 10,
+ "locale": null,
+ "pages": 5,
+ "parse": true,
+ "parser_type": null,
+ "parser_preset": null,
+ "parsing_instructions": null,
+ "browser_instructions": null,
+ "render": "png",
+ "xhr": false,
+ "markdown": false,
+ "url": null,
+ "query": "9949842269271681420",
+ "source": "google_shopping_pricing",
+ "start_page": 1,
+ "status": "faulted",
+ "storage_type": null,
+ "storage_url": null,
+ "subdomain": "www",
+ "content_encoding": "utf-8",
+ "updated_at": "2025-09-01 12:56:01",
+ "user_agent_type": "desktop",
+ "session_info": null,
+ "statuses": [],
+ "client_notes": null,
+ "_links": [
+ {
+ "rel": "self",
+ "href": "http://data.oxylabs.io/v1/queries/7368265301332333570",
+ "method": "GET"
+ },
+ {
+ "rel": "results",
+ "href": "http://data.oxylabs.io/v1/queries/7368265301332333570/results",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content",
+ "href_list": [
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/1/content",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/2/content",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/3/content",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/4/content",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/5/content"
+ ],
+ "method": "GET"
+ },
+ {
+ "rel": "results-html",
+ "href": "http://data.oxylabs.io/v1/queries/7368265301332333570/results?type=raw",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content-html",
+ "href_list": [
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/1/content?type=raw",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/2/content?type=raw",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/3/content?type=raw",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/4/content?type=raw",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/5/content?type=raw"
+ ],
+ "method": "GET"
+ },
+ {
+ "rel": "results-parsed",
+ "href": "http://data.oxylabs.io/v1/queries/7368265301332333570/results?type=parsed",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content-parsed",
+ "href_list": [
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/1/content?type=parsed",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/2/content?type=parsed",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/3/content?type=parsed",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/4/content?type=parsed",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/5/content?type=parsed"
+ ],
+ "method": "GET"
+ },
+ {
+ "rel": "results-png",
+ "href": "http://data.oxylabs.io/v1/queries/7368265301332333570/results?type=png",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content-png",
+ "href_list": [
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/1/content?type=png",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/2/content?type=png",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/3/content?type=png",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/4/content?type=png",
+ "http://data.oxylabs.io/v1/queries/7368265301332333570/results/5/content?type=png"
+ ],
+ "method": "GET"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/Fixtures/universal_failed_result.json b/tests/Fixtures/universal_failed_result.json
new file mode 100644
index 0000000..5b77d01
--- /dev/null
+++ b/tests/Fixtures/universal_failed_result.json
@@ -0,0 +1,743 @@
+{
+ "results": [
+ {
+ "content": {
+ "parse_status_code": 12002
+ },
+ "created_at": "2025-08-31 14:26:03",
+ "updated_at": "2025-08-31 14:29:28",
+ "page": 1,
+ "url": "https://www.homedepot.com/p/MOEN-Adler-Single-Handle-Pull-Down-Sprayer-Kitchen-Faucet-with-Reflex-and-Power-Clean-in-Matte-Black-87233BL/311933284?source=shoppingads&locale=en-US",
+ "job_id": "7367925645617159169",
+ "is_render_forced": false,
+ "status_code": 200,
+ "type": "parsed",
+ "parser_type": "ecommerce_product",
+ "parser_preset": null,
+ "_request": {
+ "cookies": [],
+ "headers": {
+ "Accept": "*/*",
+ "Referer": "https://www.homedepot.com/",
+ "Sec-Ch-Ua": "\"Chromium\";v=\"122\", \"Not(A:Brand\";v=\"24\", \"Brave\";v=\"122\"",
+ "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
+ "Sec-Fetch-Dest": "empty",
+ "Sec-Fetch-Mode": "cors",
+ "Sec-Fetch-Site": "cross-site",
+ "Accept-Encoding": "gzip, deflate, br",
+ "Accept-Language": "en-US,en;q=0.8",
+ "Sec-Ch-Ua-Mobile": "?0",
+ "Sec-Ch-Ua-Platform": "\"Linux\""
+ }
+ },
+ "_response": {
+ "cookies": [
+ {
+ "key": "HD_DC",
+ "path": "/",
+ "value": "origin",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": -1,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "AKA_A2",
+ "path": "/",
+ "value": "A",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1756654132.804429,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "akavpau_prod",
+ "path": "/",
+ "value": "1756650832~id=136376d5f21c3dc44d76c7df590109ff",
+ "domain": "www.homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": -1,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "akacd_usbeta",
+ "path": "/",
+ "value": "3934103331~rv=38~id=bed9d701f0cc5ed54bc1e38bf1696b84",
+ "domain": "www.homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": -1,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "bm_ss",
+ "path": "/",
+ "value": "ab8e18ef4e",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1756654132.804471,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "_bman",
+ "path": "/",
+ "value": "48d89825d0d0137a9354be80f9f3832b",
+ "domain": ".www.homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": -1,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "bm_so",
+ "path": "/",
+ "value": "0A581902335F26B8EDCC2699B9FE988796B7B9404DF5C82EA73374A0BC21876E~YAAQtP1IF1btlfGYAQAAm++HAAQJRl0I/cprJjXnHCyb0nGnbv3V4Nve41OQalP0LWwH9/XWWGrOhTu2QjQm28lns2L2maUGEfA8QZaN5w15KhROtQJgmOBsUTkp3EiDVZnhGYjZEu3oB7QKV53d7hW0naJ6cwor/tAn4gLUelgKuXpZWzW2dpXR4olKeTxgcsx4LahGLtac2PDL8dA/PkHAAnY2t4ajFJM2H1+SsHDnXmrjvC0i+3Qw5HTKGl+eL67G6t7Bzl/DaGtBNcEeeYzwvO+ux349VmXf6AiBdx05wCd24chDj1qkJPhUlTJqUohWRlehcuGiI+USPey3cmEleJMknKa3HAT53J8XeZoAlobP2/rpWQ1N36/9J8C9M1BmDmh1UMJT1m6SdwYVoDbUDh9wABnGp3NpOA/v7Fxn+SqdddisXE4uchimRQBIFiwsuTnRf+48WB7luaxv",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1756736932.804505,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "bm_sz",
+ "path": "/",
+ "value": "14CAEEE198D0C08E6ADA08E3B7C29F2E~YAAQtP1IF1ftlfGYAQAAm++HAByKgOxcUm1xGZNg9z8BpyjSf3/vDup4oiEjxj6eh0uW7QNiDjtiXs3yXqeQx0EnQ7sMGJczmiz/eSWAi+lUAuXVlw9vDtQv15xLEUcvHRAPTNELJzXSfRvOAcCnwpkgQxgdqUuxgzoylAmtcpyMettFMEStWD1Pty4/GmtseB49I/NaYft5im6nhblJdCetwkiqVewSainYkiy7QEJm3BUSKmu9prDfhpSAVCMb5w468D7tVp+muehAaI7uFtfreKnuXW5rRFc+G7AoE66PbmSyK7suHdZKqfecdFFfD28drcTks8DpjMKMw8oUCXQvhwHtKRPFJVl1IPy+ZIJjf1XPnl/rjKTBOIkIuZGftOB60kacWhqO8U+L+Ws=~3748165~4403768",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1756664932.804515,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "THD_NR",
+ "path": "/",
+ "value": "0",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1756736932,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "kndctr_F6421253512D2C100A490D45_AdobeOrg_cluster",
+ "path": "/",
+ "value": "irl1",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1756652335.019147,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "AMCV_F6421253512D2C100A490D45%40AdobeOrg",
+ "path": "/",
+ "value": "MCMID|69413148745427377631303856554730238980",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1757255334.089156,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "THD_PERSIST",
+ "path": "/",
+ "value": "",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1757255334.23317,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "THD_SESSION",
+ "path": "/",
+ "value": "",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1756736934,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "THD_CACHE_NAV_SESSION",
+ "path": "/",
+ "value": "",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1756736934,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "THD_CACHE_NAV_PERSIST",
+ "path": "/",
+ "value": "",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1757255334.233593,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "IN_STORE_API_SESSION",
+ "path": "/",
+ "value": "TRUE",
+ "domain": "www.homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1756650834,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "ak_bmsc",
+ "path": "/",
+ "value": "2E64E9412B1089CDD77CA4946A28913B~000000000000000000000000000000~YAAQtP1IF2ftlfGYAQAAvveHAByPUeLRz+UlWNhEoE4biHcpaSSGNifopja9yFHeZhnXxVzKKJK3aHBGLsQNVt/J4st8DUBAqvkl8lxw66KGOPAb4CPfN66f+IeIXUGiwnhL9Z4POeJYyUgXlxV8eCgoeNdMKHl37/eOQsb6AInG5a2w86voJsG50Uo2uq7afAsFUpWl1DTBUgZWTV1tL+pqAhIXLdJadnzcjjQLyt6ZMRBKPtDkIoMHqgH1SjITMFORz8H/MdRsNmcsatvEBD12nPMMQORwRlMn74MevIXH9rOke8kNIWuh5d5QzBq9dEb9DkDzi1k1sAg7b4BGkXK0ZTmwos4RITwqGCne85OQIJL9lAm+U/nD+l58SnUnuec8uHbh22pufM/Jl00vZT4wUQT2OnwwEPqrb8rgT+Uzk+/R6W7l5wsBlw6xYJYEBAatorcao4jfrTVWqqc=",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1756657733.911273,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "kndctr_F6421253512D2C100A490D45_AdobeOrg_identity",
+ "path": "/",
+ "value": "CiY2OTQxMzE0ODc0NTQyNzM3NzYzMTMwMzg1NjU1NDczMDIzODk4MFITCKnon4SQMxABGAEqBElSTDEwAPABmvCfhJAz",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1772202535.019199,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "x-tt-maa",
+ "path": "/",
+ "value": "flash25-0",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1756736935,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "x-ttsearch",
+ "path": "/",
+ "value": "plp_speed",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1756654135,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "THD_LOCALIZER",
+ "path": "/",
+ "value": "%7B%22WORKFLOW%22%3A%22LOCALIZED_BY_DEFAULT%22%2C%22THD_FORCE_LOC%22%3A%222%22%2C%22THD_LOCSTORE%22%3A%22121%2BCumberland%20-%20Atlanta%2C%20GA%2B%22%2C%22THD_STRFINDERZIP%22%3A%2230339%22%2C%22THD_STORE_HOURS%22%3A%221%3B8%3A00-19%3A00%3B2%3B6%3A00-21%3A00%3B3%3B6%3A00-21%3A00%3B4%3B6%3A00-21%3A00%3B5%3B6%3A00-21%3A00%3B6%3B6%3A00-21%3A00%3B7%3B6%3A00-21%3A00%22%2C%22THD_STORE_HOURS_EXPIRY%22%3A1756654135%7D",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": -1,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "DELIVERY_ZIP",
+ "path": "/",
+ "value": "30339",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1757255335.040024,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "DELIVERY_ZIP_TYPE",
+ "path": "/",
+ "value": "DEFAULT",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1757255335.040042,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "_abck",
+ "path": "/",
+ "value": "CC83132CEA9DCC246AD6F862827DDB08~-1~YAAQtP1IF2rtlfGYAQAAZviHAA6PVwtmWpokK7OprS5EssgapAlHFmjgP4kMtLfQz1jNwz/vIvCF9SzKy63mP88GXEZPBfhkZHS1EQ0A4jJbnESLRy5aylWhNJz0wa32Eb4euZnrtcdQKuym1ngbjLw5k0i8ZzGICf6Rlh7Gf6DOeqY4y5vW5HtQ7XPwOOEbBECSx86sJa+EtJr0/sQ/rncASGkWLQeAHevFYLCKK43nswWJbxlP8vF6/q4Oi+mnSMckCdVTOJ1d/p7mTCxsJjToKD6oHiwlFZwddG++y7k3rQMY6+c4nEhegF2nqzzX2Gr3yB03ogSVLGqNsl24HagXMqsM6Soi/G13/ukD5lroG2lqMG/cUX3+1SXbs9PeKj9NBpLMNF9/jyMl4d7yX66eBQiFyQiS8+rBATwQYLz+rcEvqVvQwx1FF1cPBz2KXckf718zAkYSUdaYtwNNLYkRRXFiDHVCTl8fEXZlYcGu4H36VY+h2E7x/D6diqRMlD0UYDhfEJOfLw18E96LGNlA7OkW5zxMqdu9h7zhyk8Exgsp3FuS3JBAiw6DphOUwPsgGK+kJjLQtuzu9htBvEoRRtUriij23Q==~-1~-1~-1~~",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1772202535.054271,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "pxcts",
+ "path": "/",
+ "value": "d2f3761e-8676-11f0-bd50-96c059590e9e",
+ "domain": "www.homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": -1,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "_pxvid",
+ "path": "/",
+ "value": "d2f36aaa-8676-11f0-bd4f-85b659faacbe",
+ "domain": "www.homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1757255335.35562,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "bm_lso",
+ "path": "/",
+ "value": "0A581902335F26B8EDCC2699B9FE988796B7B9404DF5C82EA73374A0BC21876E~YAAQtP1IF1btlfGYAQAAm++HAAQJRl0I/cprJjXnHCyb0nGnbv3V4Nve41OQalP0LWwH9/XWWGrOhTu2QjQm28lns2L2maUGEfA8QZaN5w15KhROtQJgmOBsUTkp3EiDVZnhGYjZEu3oB7QKV53d7hW0naJ6cwor/tAn4gLUelgKuXpZWzW2dpXR4olKeTxgcsx4LahGLtac2PDL8dA/PkHAAnY2t4ajFJM2H1+SsHDnXmrjvC0i+3Qw5HTKGl+eL67G6t7Bzl/DaGtBNcEeeYzwvO+ux349VmXf6AiBdx05wCd24chDj1qkJPhUlTJqUohWRlehcuGiI+USPey3cmEleJMknKa3HAT53J8XeZoAlobP2/rpWQ1N36/9J8C9M1BmDmh1UMJT1m6SdwYVoDbUDh9wABnGp3NpOA/v7Fxn+SqdddisXE4uchimRQBIFiwsuTnRf+48WB7luaxv^1756650535517",
+ "domain": ".www.homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1757255335.517132,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "thda.s",
+ "path": "/",
+ "value": "5fb535f1-d005-4453-619b-46733df5764e",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": -1,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "thda.u",
+ "path": "/",
+ "value": "0ca8b503-bc91-6740-b152-32faf83979ad",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1757255336.310845,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "_bman_api",
+ "path": "/",
+ "value": "bd5187e6b56676c71d7d5cbdd2b35def",
+ "domain": ".apionline.homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": -1,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "_px_f394gi7Fvmc43dfg_user_id",
+ "path": "/",
+ "value": "ZDQxNGEwZjEtODY3Ni0xMWYwLWIxMWEtNTU2NDljMjAyZGYy",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1757255337.087958,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "RT",
+ "path": "/",
+ "value": "\"z=1&dm=www.homedepot.com&si=fe224421-aad3-418c-b51b-1252a1d19c20&ss=mezsdd2d&sl=1&tt=46l&rl=1&ld=46m\"",
+ "domain": ".www.homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1757255337,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "QSI_HistorySession",
+ "path": "/",
+ "value": "https%3A%2F%2Fwww.homedepot.com%2Fp%2FMOEN-Adler-Single-Handle-Pull-Down-Sprayer-Kitchen-Faucet-with-Reflex-and-Power-Clean-in-Matte-Black-87233BL%2F311933284%3Fsource%3Dshoppingads%26locale%3Den-US~1756650538668",
+ "domain": "www.homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": -1,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "forterToken",
+ "path": "/",
+ "value": "53899ea4a3a04e26b5a44a76fe38398e_1756650537090__UDF43b-m4_23ck_",
+ "domain": ".homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1757255339.906151,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "bm_s",
+ "path": "/",
+ "value": "YAAQtP1IF33tlfGYAQAA6QuIAAM9pMr63Hvew+wLXe9D5hmNTj/fgoE+Ul9K5BETWuHE6OU0A0emLJWJIEMK6Z6gKW57Eod8qJbEv55CyrFfi1JsWdjlx5VgQk+b518Eeqe+LPmSJ5RqBZBjOzpBtvUfy1fl3X/VXiOt+sSRin/TUUYK0uR82wOJJdhuKpnOjlj+ZBVOnAJHQMRJ4E5myauoKCK8M35HfEBPVim+fNwf5l3qKt6oGGowEeS+2jPDgtmX+3G9Vs2EdA87Eq+TfynOvT/e2fSebMcTFv0HM2a8SQABbloioj4TDLHHeIcADFnVRCAYFGzojE4Ln2RRME8QjLMpDdMrt4dzD+AMKAwMjvMTUhhHi9T49u4NEXABuaOeTz/HMw9NjKxJ+bAuNjR2BSk2wpiDE6/3DG3shpcnwL2ss/arpGPHqXjL+M84IFhN2PpH9D6B/vVZj0DQBWz0p3KhKqLne24vBUR18Ipzpoksb4wHsbuBtI/zEQIH104ddci/uhZrbzHER84u5efPVhvvaZxU4POUVINZ+ZZqK5Wp5PC+",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1759328940.058189,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "QuantumMetricSessionID",
+ "path": "/",
+ "value": "401a3d2fcd34ee4f1128eb4661b47a03",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1756652354,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "QuantumMetricUserID",
+ "path": "/",
+ "value": "15d5edf9507141cc2c8bc9379920449b",
+ "domain": ".homedepot.com",
+ "secure": true,
+ "comment": "",
+ "expires": 1757255340.099928,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "_px",
+ "path": "/",
+ "value": "WrI53+zAOIOptz8CFPwxjQuPJw6v4Ij4D2FwrTS+BREXBIXWR4T+Jp+vHCs6nXrYDABPkR7fLBY/GC6oqqa3MA==:1000:qtxcKUjUAsCph6flPpxeyLpgP+FURctsxDk/GGQCoSNO/bFaWl1Xo0PBK2lmUUlOzmJ1mBin4zKplUJubqpCY7W1nLaklVeSuf8GCZ/b3FHDMKb7TOu28wCOLPQy0kqVHVELvtqBeJJLQ5bt/KibyJwN9uIS4TfjY5pguOeefwuwjdvLATpNdTXcz9kRv9s+n+UG+WG+G3xUJyZsolJMf8Mh/oC1HhOFYOvKOaY0KoYkFkEztOq/tI129c3efr26NLUOfolSMjXFb3q7f3g2bA==",
+ "domain": "www.homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1756823359,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ },
+ {
+ "key": "_pxde",
+ "path": "/",
+ "value": "2695944b12f6f0e06ec9cc754e5d999f8c4c000beeb8f74d4b5b58c00c92bbb1:eyJ0aW1lc3RhbXAiOjE3NTY2NTA1NTkxMDMsImluY19pZCI6WyJkZGE4ZTliYzMyNjI1MjhlMmVkZmIzYjAyNzg0MjZmMiIsIjVjZjdiMmFmZDQwYTBlNGFjMWRhMDMzMzZkYTc2YjRmIiwiYjhlZGY4Y2M4MmMxN2RmM2Q3YzFhZWFmYjAzZWNjYjQiXX0=",
+ "domain": "www.homedepot.com",
+ "secure": false,
+ "comment": "",
+ "expires": 1756823359,
+ "max-age": "",
+ "version": "",
+ "httponly": "",
+ "samesite": ""
+ }
+ ],
+ "headers": {
+ "date": "Sun, 31 Aug 2025 14:28:52 GMT",
+ "host": "www.homedepot.com",
+ "link": ";rel=\"preload\";as=\"font\";type=\"font/woff2\";crossorigin,;rel=\"preload\";as=\"font\";type=\"font/woff2\";crossorigin,;rel=\"preload\";as=\"font\";type=\"font/woff2\";crossorigin,;rel=\"preload\";as=\"font\";type=\"font/woff2\";crossorigin, ;rel=\"preconnect\"",
+ "vary": "Accept-Encoding",
+ "grace": "none",
+ "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
+ "pragma": "no-cache",
+ "server": "nginx",
+ "expires": "Sun, 31 Aug 2025 14:28:52 GMT",
+ "sec-gpc": "1",
+ "x-proto": "secure",
+ "priority": "u=0, i",
+ "expect-ct": "max-age=0",
+ "sec-ch-ua": "\"Not;A=Brand\";v=\"99\", \"Brave\";v=\"139\", \"Chromium\";v=\"139\"",
+ "x-varnish": "1020963198 1023945492",
+ "akamai-grn": "0.b4fd4817.1756650532.4061345",
+ "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
+ "content-type": "text/html; charset=utf-8",
+ "x-experiment": "{\"decision_engine\":{},\"adobe\":{}}",
+ "accept-ranges": "bytes",
+ "cache-control": "no-cache",
+ "server-timing": "cdn-cache; desc=REVALIDATE, edge; dur=137, origin; dur=44, ak_p; desc=\"1756650532220_390659508_67507013_18105_50926_15_291_255\";dur=1",
+ "x-device-type": "desktop",
+ "x-varnish-esi": "true",
+ "sec-fetch-dest": "document",
+ "sec-fetch-mode": "cors",
+ "sec-fetch-site": "none",
+ "sec-fetch-user": "?1",
+ "accept-encoding": "gzip, deflate, br",
+ "accept-language": "en-US,en;q=0.8;q=0.9",
+ "x-varnish-cache": "HIT(1)@vdir",
+ "content-encoding": "br",
+ "sec-ch-ua-mobile": "?0",
+ "x-xss-protection": "0",
+ "sec-ch-ua-platform": "\"Linux\"",
+ "x-download-options": "noopen",
+ "x-akamai-transformed": "9 - 0 pmb=mTOE,2mRUM,2",
+ "x-application-context": "render-c2-workers:undefined:fusion-hdh-pip-desktop:kitchen-faucets",
+ "x-content-type-options": "nosniff",
+ "x-dns-prefetch-control": "on",
+ "strict-transport-security": "max-age=63072000; includeSubDomains",
+ "upgrade-insecure-requests": "1",
+ "x-permitted-cross-domain-policies": "none",
+ "remove-dup-edge-ctrl-headers-rollout-enabled": "1"
+ }
+ },
+ "session_info": {
+ "id": null,
+ "expires_at": null,
+ "remaining": null
+ }
+ }
+ ],
+ "job": {
+ "callback_url": null,
+ "client_id": 83306,
+ "context": [
+ {
+ "key": "force_headers",
+ "value": false
+ },
+ {
+ "key": "force_cookies",
+ "value": false
+ },
+ {
+ "key": "hc_policy",
+ "value": true
+ },
+ {
+ "key": "successful_status_codes",
+ "value": []
+ },
+ {
+ "key": "follow_redirects",
+ "value": null
+ },
+ {
+ "key": "cookies",
+ "value": []
+ },
+ {
+ "key": "headers",
+ "value": []
+ },
+ {
+ "key": "session_id",
+ "value": null
+ },
+ {
+ "key": "http_method",
+ "value": "get"
+ },
+ {
+ "key": "content",
+ "value": null
+ },
+ {
+ "key": "store_id",
+ "value": null
+ },
+ {
+ "key": "proxy_location",
+ "value": null
+ },
+ {
+ "key": "delivery_location",
+ "value": null
+ },
+ {
+ "key": "fulfillment_type",
+ "value": null
+ }
+ ],
+ "created_at": "2025-08-31 14:26:03",
+ "domain": "com",
+ "geo_location": "United States",
+ "id": "7367925645617159169",
+ "limit": 10,
+ "locale": null,
+ "pages": 1,
+ "parse": true,
+ "parser_type": "ecommerce_product",
+ "parser_preset": null,
+ "parsing_instructions": null,
+ "browser_instructions": null,
+ "render": "png",
+ "xhr": false,
+ "markdown": false,
+ "url": "https://www.homedepot.com/p/MOEN-Adler-Single-Handle-Pull-Down-Sprayer-Kitchen-Faucet-with-Reflex-and-Power-Clean-in-Matte-Black-87233BL/311933284?source=shoppingads&locale=en-US&srsltid=AfmBOorXn9kENeFYSiVY1MuebuKYlSS4NTCiFb0gxtHHhzJioIbQiSmgy88",
+ "query": "",
+ "source": "universal",
+ "start_page": 1,
+ "status": "done",
+ "storage_type": null,
+ "storage_url": null,
+ "subdomain": "www",
+ "content_encoding": "utf-8",
+ "updated_at": "2025-08-31 14:29:28",
+ "user_agent_type": "desktop",
+ "session_info": null,
+ "statuses": [],
+ "client_notes": null,
+ "_links": [
+ {
+ "rel": "self",
+ "href": "http://data.oxylabs.io/v1/queries/7367925645617159169",
+ "method": "GET"
+ },
+ {
+ "rel": "results",
+ "href": "http://data.oxylabs.io/v1/queries/7367925645617159169/results",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content",
+ "href_list": [
+ "http://data.oxylabs.io/v1/queries/7367925645617159169/results/1/content"
+ ],
+ "method": "GET"
+ },
+ {
+ "rel": "results-html",
+ "href": "http://data.oxylabs.io/v1/queries/7367925645617159169/results?type=raw",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content-html",
+ "href_list": [
+ "http://data.oxylabs.io/v1/queries/7367925645617159169/results/1/content?type=raw"
+ ],
+ "method": "GET"
+ },
+ {
+ "rel": "results-parsed",
+ "href": "http://data.oxylabs.io/v1/queries/7367925645617159169/results?type=parsed",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content-parsed",
+ "href_list": [
+ "http://data.oxylabs.io/v1/queries/7367925645617159169/results/1/content?type=parsed"
+ ],
+ "method": "GET"
+ },
+ {
+ "rel": "results-png",
+ "href": "http://data.oxylabs.io/v1/queries/7367925645617159169/results?type=png",
+ "method": "GET"
+ },
+ {
+ "rel": "results-content-png",
+ "href_list": [
+ "http://data.oxylabs.io/v1/queries/7367925645617159169/results/1/content?type=png"
+ ],
+ "method": "GET"
+ }
+ ]
+ }
+}
\ No newline at end of file