# 1688 Products Scraper: Wholesale Prices, MOQ, Suppliers (`arcadium/1688-products`) Actor

1688.com wholesale products scraper, $1.80 per 1,000 search rows and $4.49 with details: price tiers, MOQ, units sold, factory and supplier scores. 10% under the two most used 1688 actors; their input works as is. Chinese or English keywords; SKUs, stock and freight with details. Free error rows.

- **URL**: https://apify.com/arcadium/1688-products.md
- **Developed by:** [Arcadium Data](https://apify.com/arcadium) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.35 / 1,000 product (search result)s

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
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

## 1688 Products Scraper: Wholesale Prices, MOQ, Suppliers

Find factory suppliers and wholesale prices for a product idea, compare them with your retail price, or watch a supplier's offers. Type a keyword in Chinese or English, or paste 1688 offer links or ids, and get wholesale products from 1688.com as one JSON row per product: price tiers by quantity, minimum order, units sold, repurchase rate, supplier and factory data. $1.80 per 1,000 products on the free plan, and you pay only for the rows you get.

**Why this actor**

- **Drop-in input.** Input written for `zen-studio/1688-wholesale-scraper` (`keywords`, `offerIds`, `maxResults`, `sortBy`, `priceMin`, `priceMax`, `includeSkuDetails`) or `devcake/1688-com-products-scraper` (`queries`, `maxProducts`, `sortType`) works as it is.
- **Search and detail in one actor, 10% under each.** A search row costs $1.80 per 1,000 against devcake's $2; a row with details $4.49 against zen-studio's $4.99 per product (free plan, Apify Store, 2026-10-31).
- **Price tiers, MOQ and supplier scores in the search row**: price per quantity step, minimum order, units sold, repurchase rate, factory flag and supplier service scores, without opening each product page.

| Row | Free plan | Business (Gold) and up |
| --- | --- | --- |
| Product (search result) | $1.80 per 1,000 | $1.35 per 1,000 |
| Product with details (SKUs, stock, attributes, freight) | $4.49 per 1,000 | $3.59 per 1,000 |

- Seller login names and tracking data are dropped; contact details found in text are redacted. Free error rows with a machine-readable `code` (`not_found`, `blocked`, `unavailable`...), so an agent knows whether to retry.

Built by Arcadium Data: public data, dated and agent-ready, never personal data.

### Use with AI agents

Returns 1688.com wholesale products (price tiers, MOQ, sold count, supplier and factory data; with details SKUs, attributes, freight) for keywords, offer ids or links. Agents can call it through the Apify MCP server (`https://mcp.apify.com`, open source as `apify/actors-mcp-server`) as `arcadium/1688-products`: the input schema is read from the actor, the result is a dataset of JSON rows.

**Input example** (minimal):

```json
{ "keywords": ["保温杯"], "maxItemsPerKeyword": 50 }
```

Cost of the call above on the Free plan: 50 products at $1.80 per 1,000 plus the $0.001 start fee is about $0.09.

Each dataset row with `rowType: "product"` is one offer. For a sourcing answer read `title`, `priceTiers` (price by quantity in CNY), `moq`, `soldCount`, `supplier.isFactory`, `supplier.repurchaseRate3m` and `url`. Titles are in Chinese; translate them yourself. For one specific offer use `{"offerIds":["866095761513"]}` and read `skus`, `attributes` and `freight`. Rows with `rowType: "coverage"` say how many results 1688 reports for a keyword. Failures arrive as rows with `rowType: "error"` and a machine-readable `code` (`not_found`, `invalid_input`, `blocked`, `unavailable`, `no_match`); they are free.

### What you get

| Field | What it holds |
| --- | --- |
| `offerId`, `url`, `title` | The offer id (the number in the product link), its link and its title as the seller wrote it (Chinese). |
| `keyword`, `rank`, `isAd` | The search keyword and the position in the results (paid placements included), and whether the row is a paid placement. Empty for id and link lookups. |
| `priceMin`, `priceMax`, `priceTiers` | Prices in Chinese yuan (CNY). `priceTiers` lists the wholesale steps: `{ minQty, maxQty, price }`. |
| `moq`, `unit` | Minimum order quantity and the selling unit (个, 件, 套). |
| `soldCount`, `soldText`, `soldScope` | Units sold as a number and as the site shows it (`已售500+件`). `soldScope` is `offer` for this listing and `network` for the "全网" figure of the whole product. With details, `soldCount` is the exact total. |
| `bookedCount`, `repurchaseRate` | Number of orders on the search card, and the share of buyers who reordered this offer (0-100). |
| `images`, `videoUrl`, `category` | Image links, the main video and the category (id, and name with details). |
| `origin` | Province and city of the dispatch location. |
| `serviceTags` | Service badges such as `先采后付` (buy now, pay later), `深度验厂` (inspected factory), `退货包运费`. |
| `supplier` | `companyName`, `memberId`, `isFactory`, `isSuperFactory`, `factoryInspected`, `yearsOnPlatform`, `serviceScores` (0-5: composite, consultation, logistics, dispute, returns, goods), `repurchaseRate3m`, `shopUrl`. |
| `skus` | With details: every variant as `{ skuId, attrs, price, stock, sold }`. |
| `attributes` | With details: the product parameters (material, brand, model, ...) as `{ name, value }`. |
| `freight` | With details: dispatch promise (`text`), `shipsFrom`, `free` and an indicative `fee`. |
| `rating` | With details: `score`, `goodRatePct` and `reviewCount` of buyer reviews. |
| `hasDetails` | `true` when the row carries the detail fields. |

**Output example** (one product row, abridged):

```json
{
  "rowType": "product",
  "platform": "1688",
  "offerId": "866095761513",
  "url": "https://detail.1688.com/offer/866095761513.html",
  "title": "数据线保护套硅胶防折断充电线保护套咬线器防断裂神器",
  "keyword": "数据线",
  "rank": 2,
  "isAd": false,
  "currency": "CNY",
  "priceMin": 2.5,
  "priceMax": 2.6,
  "priceTiers": [
    { "minQty": 3, "maxQty": 4999, "price": 2.6 },
    { "minQty": 5000, "maxQty": 99999, "price": 2.55 },
    { "minQty": 100000, "maxQty": null, "price": 2.5 }
  ],
  "moq": 3,
  "unit": "件",
  "soldCount": 100000,
  "soldText": "已售10万+件",
  "soldScope": "offer",
  "repurchaseRate": 17,
  "origin": { "province": "广东", "city": "东莞市" },
  "supplier": {
    "companyName": "东莞市元拓橡塑制品有限公司",
    "memberId": "b2b-22072902285003c713",
    "isFactory": true,
    "yearsOnPlatform": 7,
    "serviceScores": { "composite": 4, "consultation": 4, "logistics": 3.57, "dispute": 5, "returns": 3.67, "goods": 3.33 },
    "repurchaseRate3m": 77,
    "shopUrl": "https://apetop.1688.com"
  },
  "hasDetails": false
}
```

### Input

**Input example:**

```json
{
  "keywords": ["保温杯", "water bottle"],
  "maxItemsPerKeyword": 120,
  "sort": "sales",
  "minPrice": 10,
  "maxPrice": 50
}
```

| Field | What it does |
| --- | --- |
| `keywords` | One or more search words, Chinese or English. Each is searched on its own. |
| `maxItemsPerKeyword` | Default 60, up to 2,000 (1688 shows at most 2,000 results per search). |
| `sort` | `relevance` (default), `sales` (most sold in 30 days), `priceAsc`, `priceDesc`. |
| `minPrice`, `maxPrice` | Price range in yuan. |
| `includeDetails` | Open the product page of every keyword result and add SKUs, attributes, freight and ratings. Off by default (cheaper and faster). |
| `offerIds`, `urls` | Look up specific offers. These always return details. Links from `detail.1688.com` and the mobile site work. |
| `includeAds` | Keep paid placements (default). Turn off to drop them; they still count for the rank. |
| `includeCoverageRows` | A free row per keyword with the total 1688 reports and why the search stopped. On by default. |
| `includeErrorRows` | A free row for every input that returned nothing. On by default. |

Leave the proxy empty: the actor picks the cheapest proxy that works.

Other 1688 actors' field names are read as ours: `queries` → `keywords`; `maxResults`, `maxProducts` → `maxItemsPerKeyword` (capped at 2,000); `sortBy` (`bestSelling`) and `sortType` (`normal`, `va_rmdarkgmv30`, `va_price_asc`, `va_price_desc`) → `sort`; `priceMin`, `priceMax` → `minPrice`, `maxPrice`; `includeSkuDetails`, `enrichDetails` → `includeDetails`; `offerUrls` → `urls`. Image search input (`imageUrls`) is not supported.

### Search results and details

A search row (`hasDetails: false`) comes straight from the search results: 60 products per request. It has everything in the table except `skus`, `attributes`, `freight`, `rating`, `videoUrl` and the category name.

A detail row (`hasDetails: true`) also reads the product page. It costs more because it is one more page per product. Offers you request by id or link always get details. For a keyword search turn on `includeDetails`; if a product page cannot be read for a product, you get its search row (charged as a plain product) and `hasDetails: false` rather than nothing.

`priceTiers` depends on how the seller prices: most offers have quantity steps and one price per step for all variants. Some offers price each variant on its own; then `priceTiers` is empty, `priceMin` and `priceMax` span the variants, and each `skus[].price` is the variant's price. For quantity-priced offers `skus[].price` repeats the first step.

### Errors and coverage are rows

| Code | Meaning |
| --- | --- |
| `invalid_input` | The text is not a 1688 offer id or link. |
| `not_found` | The offer was removed or never existed. |
| `no_match` | The keyword returned no products. |
| `blocked` | 1688 kept refusing after all retries. Run again later. |
| `unavailable` | Temporary problem or an unexpected answer. |

The coverage row has `stated` (the total 1688 reports, at most 2,000), `delivered`, `stopReason` (`limit` = your cap, `end` = no more results, `cap` = 1688's own 2,000 limit, `filter` = paid placements dropped, `error`) and `shortfall` (true when fewer were delivered than 1688 reports with no cap or filter to explain it).

### Use it from code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("arcadium/1688-products").call(run_input={
    "keywords": ["保温杯"],
    "maxItemsPerKeyword": 120,
    "sort": "sales",
})
for p in client.dataset(run["defaultDatasetId"]).iterate_items():
    if p["rowType"] == "product":
        print(p["rank"], p["priceMin"], p["moq"], p["supplier"]["companyName"], p["title"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('arcadium/1688-products').call({
  offerIds: ['866095761513'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].priceTiers, items[0].skus.length, 'variants');
```

### Pricing

You pay per product delivered, at the price of your Apify plan. A plain search product and a product with details have separate prices. Error and coverage rows are free.

| Apify plan | Per 1,000 products | Per 1,000 products with details |
| --- | --- | --- |
| Free | $1.80 | $4.49 |
| Starter (Bronze) | $1.80 | $4.31 |
| Scale (Silver) | $1.62 | $4.04 |
| Business (Gold) | $1.35 | $3.59 |
| Platinum | $1.35 | $3.59 |
| Diamond | $1.35 | $3.59 |

Plus a one-off $0.001 per GB of memory when a run starts.

Set a maximum cost per run in the Apify run options and the actor stops before it goes over.

### Related actors

- [Amazon Product & Best Sellers Scraper: Search, Buy Box](https://apify.com/arcadium/amazon-products)

### Use cases

- Sourcing: find factories for a product and compare price tiers, minimum orders and repurchase rates.
- Price tracking: run weekly on a fixed keyword list and compare `priceTiers` and `soldCount` over time.
- Margin check: take the first price tier and the minimum order into your landed-cost calculator.
- Category research: sort by sales and read what moves, at which price.

### Limitations

- Public data only. Nothing behind a login, and no contact details of sellers.
- 1688 shows at most 2,000 results per search. Use several keywords or price bands to go deeper.
- Prices are in yuan and titles, attributes and tags are in Chinese. Translation is not included.
- 1688 sometimes answers a search without the product list. The actor retries; if it still fails you get a free `blocked` row.
- 1688 sometimes refuses all searches for a few minutes. The actor then waits up to `searchGateWaitMinutes` (default 12, tries again every 3 minutes, nothing is charged while it waits) and carries on once searches work again. If they still fail, every keyword it could not search gets a free `blocked` row: run it again later. Set `searchGateWaitMinutes` to 0 to stop at once instead. Product pages work the same way: if they keep refusing, the remaining keyword results arrive as plain products, charged at the plain price.
- A search row has no variants. Add details for `skus`.
- `soldCount` of a search row is a lower bound ("500+"). With details it is the exact total of the listing. The "全网" figure (`soldScope: network`) covers the same product across the web, not only this listing.
- The freight `fee` is indicative; 1688 calculates the real freight from the destination.
- Titles and attributes are cleaned of e-mail addresses, phone numbers and messenger handles; a seller can still hide contact data in an image.

### Privacy

Only product and company listing data is collected. Seller login names and tracking data are dropped. Contact details found in text are redacted.

### FAQ

**Can I search in English?**
Yes. 1688 translates English keywords itself ("water bottle" returns water cups and bottles), but Chinese keywords usually match more precisely.

**Why does a product have no `priceTiers`?**
The seller prices each variant on its own. Read `priceMin`, `priceMax` and `skus[].price`.

**Why is `soldCount` the same for many products?**
Search rows show a lower bound such as 500+ or 10万+. Use details for the exact number.

**Something looks wrong in the output. Who do I tell?**
Open an issue on the actor page with the run link. We read them all.

# Actor input Schema

## `keywords` (type: `array`):

Words to search 1688.com for, Chinese or English, for example \["保温杯", "water bottle"]. Each keyword is searched on its own and every product keeps the keyword and its position in the results. Leave empty when you give offer ids or links. Also read from queries.

## `maxItemsPerKeyword` (type: `integer`):

How many products to return per keyword, for example 120. Default: 60. A results page holds 60 products and 1688 shows at most 2,000 per search, so 2,000 is the ceiling. Also read from maxResults or maxProducts (other 1688 actors' names).

## `sort` (type: `string`):

Order of the results: best match (default), most sold in the last 30 days, or price. Example: priceAsc. Also read from sortBy (bestSelling = sales) or sortType (va_rmdarkgmv30 = sales).

## `minPrice` (type: `number`):

Only products priced at or above this amount in Chinese yuan, for example 10. Leave empty for no lower limit. Also read from priceMin.

## `maxPrice` (type: `number`):

Only products priced at or below this amount in Chinese yuan, for example 50. Leave empty for no upper limit. Also read from priceMax.

## `includeDetails` (type: `boolean`):

Opens the product page of every keyword result and adds SKUs with stock, attributes, freight, ratings and the exact sold count. Costs more per product (the Product with details price) and runs slower. Offer ids and links always return details. Default: false. Also read from includeSkuDetails or enrichDetails.

## `offerIds` (type: `array`):

1688 offer ids (the number in the product link), for example \["866095761513"]. Each one returns a full product with details.

## `urls` (type: `array`):

1688 product page links, for example https://detail.1688.com/offer/866095761513.html. The offer id is read from the link; mobile links work too.

## `includeAds` (type: `boolean`):

Sponsored products are part of the search results. Each row has an isAd flag; turn this off to drop them (they still count towards the position). Example: true.

## `includeCoverageRows` (type: `boolean`):

Adds one free row per keyword with the total 1688 reports, how many products were delivered and why the search stopped, so incomplete results are visible.

## `includeErrorRows` (type: `boolean`):

Adds a free row to the dataset for every keyword or offer that returned nothing or could not be read, with a machine-readable code, so a failure is never silent.

## `maxRetries` (type: `integer`):

How often a request is retried after a temporary error, for example 3.

## `searchGateWaitMinutes` (type: `integer`):

1688 sometimes refuses all searches for a few minutes. The run then waits up to this many minutes, trying again every 3 minutes, before the remaining keywords end as free error rows. 0 = do not wait.

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

Leave empty. The actor picks proxies that work for this site (datacenter first, residential only when needed). Set your own proxy only if you must; it then replaces the automatic choice.

## Actor input object example

```json
{
  "keywords": [
    "保温杯"
  ],
  "maxItemsPerKeyword": 50,
  "sort": "relevance",
  "includeAds": true,
  "includeCoverageRows": true,
  "includeErrorRows": true,
  "maxRetries": 3,
  "searchGateWaitMinutes": 12
}
```

# Actor output Schema

## `results` (type: `string`):

All rows of this run in the default dataset.

## `view_overview` (type: `string`):

One line per product: title, price range, minimum order, sales and supplier.

## `view_pricing` (type: `string`):

Wholesale price steps per quantity for every product.

## `view_suppliers` (type: `string`):

Who sells the product: company, factory flags, years on 1688, store scores and repurchase rate.

## `view_details` (type: `string`):

SKUs with stock, attributes and freight, for rows that carry details.

## `view_coverage` (type: `string`):

Per keyword: how many results 1688 reports, how many were delivered and why the search stopped.

## `view_errors` (type: `string`):

Inputs that returned nothing, with a machine-readable code.

## `record_stats` (type: `string`):

Requests, data volume, retries and cost per result for this run.

# 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 = {
    "keywords": [
        "保温杯"
    ],
    "maxItemsPerKeyword": 50,
    "sort": "relevance",
    "includeDetails": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("arcadium/1688-products").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 = {
    "keywords": ["保温杯"],
    "maxItemsPerKeyword": 50,
    "sort": "relevance",
    "includeDetails": False,
}

# Run the Actor and wait for it to finish
run = client.actor("arcadium/1688-products").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 '{
  "keywords": [
    "保温杯"
  ],
  "maxItemsPerKeyword": 50,
  "sort": "relevance",
  "includeDetails": false
}' |
apify call arcadium/1688-products --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arcadium/1688-products"
        }
    }
}
```

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/DeetBt2vwKvb1g9V3/builds/L2OJgn3TFGAhTfJwv/openapi.json
