# Lazada Search Scraper (6 SEA Markets) (`xtracto/lazada-search-scraper`) Actor

Scrape Lazada product listings across Indonesia, Malaysia, Singapore, Thailand, Vietnam and the Philippines: price, discount, rating, units sold, seller and location. No login, no browser.

- **URL**: https://apify.com/xtracto/lazada-search-scraper.md
- **Developed by:** [Farhan Febrian Nauval](https://apify.com/xtracto) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.67 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Lazada Search Scraper — Products, Prices & Sales Across 6 SEA Markets

Scrape Lazada product listings from Indonesia, Malaysia, Singapore, Thailand, Vietnam and the
Philippines: price and original price, discount, rating, review and units-sold counts, seller,
brand and shipping location. HTTP-only, no account, no browser.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `searchQueries` | array | *required* | One search per entry, e.g. `laptop` |
| `market` | string | `ID` | `ID`, `MY`, `SG`, `TH`, `VN` or `PH` |
| `maxItemsPerQuery` | integer | `120` | 40 products per request |

### Output

```jsonc
{
  "_input": "laptop",
  "_source": "S1-catalog-ajax",
  "_scrapedAt": "2026-09-09T11:31:07Z",

  "itemId": "7743246861", "skuId": "…",
  "name": "Lenovo ThinkPad T460 Business Laptop I5-6300U…",
  "url": "https://www.lazada.co.id/products/pdp-i7743246861.html",
  "image": "https://id-live-01.slatic.net/p/….jpg",

  "price": 2722500.0,
  "originalPrice": 2750000.0,
  "currency": "IDR",
  "priceDisplay": "Rp2.722.500",     // the site's own localised string
  "discountLabel": "1% Off",         // the site's own claim
  "discountPercent": 1.0,            // computed from the two prices

  "ratingScore": 4.588235294117647,
  "reviewCount": 17,
  "soldDisplay": "46 sold",          // rounded by Lazada before we see it
  "soldCount": 46,

  "brandName": "Lenovo", "brandId": "…",
  "sellerName": "Marvel stores", "sellerId": "1000077365",
  "location": "Kota Jakarta Barat",
  "inStock": true, "isSponsored": false, "isAd": false,

  "market": "ID", "page": 1
}
```

### Three things worth knowing

**The reported total is an estimate, and the index goes past it.** `mainInfo.totalResults` drifted
between 3331, 3468, 3469 and 3501 for the same query seconds apart, and page 200 still returned 40
genuine laptops. So it is logged as "about N results" and never used as a stop condition. The actor
stops on an empty page or your own limit, and **de-duplicates by `itemId` across the whole query**
regardless — a crawl of ten sequential pages returned 400 products with zero repeats, but the
guard means a repeat could never reach your dataset anyway.

**Prices come from the numeric field, not the pretty one.** `price` is a bare number in every
market; `priceShow` is localised — `Rp1.200.000`, `RM5,843.00`, `₫1,000,000` — where the dot and
comma mean opposite things depending on the country. Parsing the display string is how a price ends
up wrong by three orders of magnitude, so this actor reads the numeric field and passes the display
string through untouched as `priceDisplay`.

**`soldCount` is approximate by the time Lazada publishes it.** The source is a display string like
`1.2K sold`, so 1200 here means "about 1200", not exactly. The original is kept as `soldDisplay`.

### Scope

This is the search/listing surface. Every field above comes from one request per 40 products —
there is no per-product page fetch, so a 120-product run costs 3 requests.

### Errors

| `_error` | Meaning |
|---|---|
| `invalid_input` | Empty search query |
| `no_results` | The search ran and Lazada returned nothing |
| `blocked` | Every TLS profile was refused by the server |
| `network_error` | The ladder never reached the server — DNS, timeout, reset |
| `unexpected_shape` | 200 without the `mods.listItems` structure — the API changed |

If every query fails, the run itself fails rather than reporting success over an empty dataset.

### Proxies

**Residential is required, and leave the country unset.** Lazada answers Apify's shared datacenter
pool with a JavaScript bounce page (`sessionStorage.x5referer = …`) carrying a `200` and a
`text/html` content type rather than JSON. Residential succeeds.

Counterintuitively, pinning the proxy country to the market's own country makes it worse: with
`market: ID` and residential exits in Indonesia, every request got the bounce page — reproduced
across two separate runs — while un-countried residential returned all 40 products.

### How it works

The listing page has an AJAX twin that returns the whole result set as JSON:

```
https://www.lazada.<market>/catalog/?ajax=true&q=<query>&page=<n>
```

The same path *without* `ajax=true` serves an 82 KB HTML shell. An earlier sweep of this catalogue
recorded Lazada as unscrapeable after testing that shell — it was the route that was closed, not
the site.

# Actor input Schema

## `searchQueries` (type: `array`):

One search per entry, e.g. "laptop" or "sepatu pria".

## `market` (type: `string`):

Which Lazada storefront to search. Prices are returned in that market's currency.

## `maxItemsPerQuery` (type: `integer`):

40 products per request, so this many costs roughly this number divided by 40 requests.

## `proxyConfiguration` (type: `object`):

Residential is required and is the default: Lazada answers Apify's shared datacenter pool with a JavaScript bounce page instead of JSON. Leave the proxy country unset -- pinning it to the market's own country made every request fail in testing, twice.

## Actor input object example

```json
{
  "searchQueries": [
    "laptop",
    "wireless earbuds"
  ],
  "market": "ID",
  "maxItemsPerQuery": 120,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Dataset items shown in the 'Products' view.

## `items` (type: `string`):

Every record this run produced, with all fields, as JSON.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQueries": [
        "laptop",
        "wireless earbuds"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("xtracto/lazada-search-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "searchQueries": [
        "laptop",
        "wireless earbuds",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("xtracto/lazada-search-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQueries": [
    "laptop",
    "wireless earbuds"
  ]
}' |
apify call xtracto/lazada-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,xtracto/lazada-search-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/gkH3EC9cETDiyuUCp/builds/Bt732o6K4i2Zkw0MR/openapi.json
