# SMZDM Scraper - 什么值得买 Deals & Search (`liver0914/smzdm-deals-scraper`) Actor

Scrape SMZDM (什么值得买): deal feed with prices, malls, brands, historical-low tags and votes, plus keyword search with built-in JS-challenge bypass. Filters for mall, price, category and price drops.

- **URL**: https://apify.com/liver0914/smzdm-deals-scraper.md
- **Developed by:** [Long Li](https://apify.com/liver0914) (community)
- **Categories:** E-commerce, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 deal scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## SMZDM Deals Scraper - 什么值得买 好价

Scrapes [SMZDM](https://www.smzdm.com) (什么值得买, "What's Worth Buying") - China's largest
shopping-deal community - straight from its public homepage deal feed API:

- 💰 **Deal feed** - latest deals with prices, malls (京东/天猫/拼多多/...), brands, category trees
- 🔍 **Keyword search** - query any keyword on SMZDM search (好价 channel); the built-in headless browser clears the site's JS challenge automatically, with residential-proxy retry on the Apify platform
- 🏷️ **Deal intelligence** - highlight tags (历史低价, 20天新低, 京东自营), worthy/unworthy votes, sold-out/expired flags
- 🎯 **Server-side filtering** - malls, price range, categories, price-drop tags and min votes, all matched before anything is saved
- 🛒 **Buy links** - every purchase entrance incl. coupon links, resolved target domains
- 💬 **Comments** - per-deal comments with authors, reply chains, IP locations and photos (detail mode)
- 🔁 **Monitoring mode** - remembers seen deals across runs, outputs only new ones (perfect for schedules)

### Input

| Field | Default | Description |
|---|---|---|
| `startUrls` | `[]` | Specific deal URLs for full detail + comments. When set, search and the feed are skipped. |
| `searchKeywords` | `[]` | Keywords to search on SMZDM (好价 channel). Search pages need a headless browser (JS challenge) - the actor handles this automatically. When set, the feed is skipped. |
| `searchMaxPages` | `3` | Result pages per keyword (~20 deals/page). |
| `maxItems` | `100` | Max deals from the homepage feed (20 per page). |
| `malls` | `[]` | Keep only deals from these malls. Substring match: `天猫` covers 天猫精选/天猫超市/天猫国际官方直营. Observed: 京东 (90%), 京东全球购, 天猫精选, 天猫超市, 天猫国际官方直营, 拼多多, 唯品会. |
| `categories` | `[]` | Keep only deals in these categories (substring match on any tree level). Observed: 食品生鲜 (52%), 日用百货, 服饰鞋包, 电脑数码, 运动户外, 家居家装, ... |
| `minPrice` / `maxPrice` | `0` | Numeric price bounds in CNY; 0 = unbounded. Setting a bound also drops unpriced posts (~9% of the feed is news). |
| `onlyPriceDrops` | `false` | Keep only deals tagged 历史低价 or any `N天新低` variant (~21% of the feed). |
| `requireTags` | `[]` | Keep only deals carrying at least one of these tags (substring). Frequent: 拼单 (29%), 京东自营 (28%), 每日白菜 (19%), 低于常卖价 (17%), 手慢无. |
| `minWorthy` | `0` | Min 值得 upvotes. Votes accumulate over hours - best for backfill runs, not fresh monitoring. |
| `minFavorite` | `0` | Min 收藏 favorites. Favorites track purchase intent (~3.5x stronger than votes); also grows over hours. |
| `dedupeByTitle` | `false` | Skip same-product reposts (~5% of the feed is evergreen deals re-published under new ids; marketing prefixes like 今日必买/值友专享 are stripped before comparing). Remembers titles across runs in monitoring mode. |
| `includeDetail` | `false` | Also open each deal page for exact price, breadcrumb categories, author and top comments. |
| `maxCommentsPerDeal` | `30` | Comment cap per deal in detail mode (0 disables). |
| `mallFilter` | `""` | Legacy single-mall exact filter; prefer `malls`. |
| `skipSeenItems` | `false` | Only output deals not seen in previous runs (incremental monitoring). |
| `maxConcurrency` | `2` | Parallel requests for detail pages. |
| `proxyConfig` | - | Optional proxy. If SMZDM's WAF blocks the run, residential proxy kicks in automatically. |

All filters combine with AND; multiple values inside one list are OR. They apply to feed mode
(`startUrls` mode ignores them). Feed percentages come from a 674-deal sample of the live feed.

### Output

Dataset items of two types (`itemType` field):

**`deal`** - `dealId`, `title`, `url`, `price`, `priceText`, `mall`, `mallUrl`, `brand`,
`topCategory`, `categories[]`, `tags[]`, `imageUrl`, `buyUrl`, `buyDomain`, `buyOptions[]`,
`worthyCount`, `unworthyCount`, `commentCount`, `favoriteCount`, `reporter`, `authorId`,
`authorName`, `summary`, `isSoldOut`, `isTimeout`, `publishedTimeText`, `source`

**`comment`** (detail mode) - `commentId`, `dealId`, `dealUrl`, `text`, `images[]`, `authorName`,
`authorId`, `authorUrl`, `replyTo`, `createdAt`, `createdAtText`, `authorLocation`

Example:

```json
{
  "itemType": "deal",
  "dealId": "183861924",
  "title": "今日必买：金典 伊利金典纯牛奶250ml*24盒整箱7月产",
  "price": 31.67,
  "mall": "京东",
  "categories": ["食品生鲜", "水饮冲调", "奶类制品", "常温牛奶"],
  "tags": ["手慢无", "历史低价", "价格低于618"],
  "worthyCount": 42,
  "publishedTimeText": "23:18"
}
```

### How it works

The actor talks directly to SMZDM's public feed endpoint (`/homepage/json_more`) which returns
fully structured JSON - no browser needed, so runs are fast and cheap. Search mode is the
exception: `search.smzdm.com` hides behind a JS challenge, so keyword searches run through a
shared headless Chromium that executes the challenge automatically (and retries through the
Apify residential proxy when the platform IP gets a captcha interstitial). Search result cards
carry the real publish timestamp (`publishedAt`), unlike feed items whose `timesort` is
request-anchored. Detail pages and comments are server-rendered HTML parsed with battle-tested
selectors. The site's WAF is handled by sending browser-like XHR headers and, when challenged,
retrying through the Apify residential proxy automatically.

Comment `createdAt` values are resolved from relative texts ("47分钟前") against scrape time and
are therefore approximate; `createdAtText` always carries the original.

### Legal note

Scrapes publicly available data only, without login. Use responsibly and in line with SMZDM's
terms of service.

### Changelog

See [CHANGELOG.md](CHANGELOG.md).

# Actor input Schema

## `startUrls` (type: `array`):

Specific SMZDM deal URLs to scrape in full detail, e.g. https://www.smzdm.com/p/183861924/. When provided, search and the homepage feed are skipped. Filters below apply to feed and search modes only.

## `searchKeywords` (type: `array`):

Search SMZDM for these keywords (好价 deals channel) and scrape the result pages. search.smzdm.com hides behind a JS challenge, so searches run through a built-in headless browser (with automatic residential-proxy retry on the platform). When provided, the homepage feed is skipped.

## `searchMaxPages` (type: `integer`):

Max result pages per keyword (about 20 deals per page).

## `maxItems` (type: `integer`):

Stop after this many deals scraped from the homepage feed (20 per page).

## `malls` (type: `array`):

Keep only deals from these malls. Substring match: 天猫 covers 天猫精选/天猫超市/天猫国际官方直营, 京东 covers 京东全球购. Observed values: 京东, 京东全球购, 天猫精选, 天猫超市, 天猫国际官方直营, 拼多多, 唯品会 (~90% of the feed is 京东). Empty = all malls.

## `categories` (type: `array`):

Keep only deals in these categories (substring match on any level of the category tree). Observed top-level values: 食品生鲜 (52%), 日用百货, 服饰鞋包, 电脑数码, 运动户外, 家居家装, 汽车消费, 游戏, 个护化妆, 医疗健康, 母婴用品. Empty = all categories.

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

Keep only deals with a numeric price of at least this many yuan. 0 = no lower bound. Setting any price bound also drops unpriced posts (news, ~9% of the feed). Median feed deal is ¥26, P75 is ¥49.

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

Keep only deals with a numeric price of at most this many yuan. 0 = no upper bound. Setting any price bound also drops unpriced posts.

## `onlyPriceDrops` (type: `boolean`):

Keep only deals tagged as historical lows: 历史低价 or any N天新低 variant (~21% of the feed).

## `requireTags` (type: `array`):

Keep only deals carrying at least one of these tags (substring match). Frequent tags: 拼单 (29%), 京东自营 (28%), 每日白菜 (19%), 低于常卖价 (17%), 手慢无. Empty = no tag requirement.

## `minWorthy` (type: `integer`):

Keep only deals with at least this many 值得 (worthy) upvotes. Note votes accumulate over hours, so on a fresh feed most items have 0-2 votes - best used for backfill/snapshot runs, not fresh monitoring.

## `minFavorite` (type: `integer`):

Keep only deals with at least this many 收藏 (favorites). Favorites track purchase intent (~3.5x the worthy votes in our sample); like votes they accumulate over hours.

## `dedupeByTitle` (type: `boolean`):

Skip reposts of the same product: ~5% of the feed is evergreen deals re-published under new article ids (same title after stripping marketing prefixes like 今日必买/值友专享). With skipSeenItems on, titles are remembered across runs too.

## `includeDetail` (type: `boolean`):

Additionally open each deal's page for the exact price, breadcrumb categories, author and top comments. Slower but richer.

## `maxCommentsPerDeal` (type: `integer`):

Upper limit of comments saved per deal when detail pages are fetched.

## `mallFilter` (type: `string`):

Legacy single-mall filter, exact name. Prefer the malls list above (substring matching). Empty = all malls.

## `skipSeenItems` (type: `boolean`):

Remember scraped deal IDs across runs and only output new deals. Ideal for scheduled monitoring runs.

## `maxConcurrency` (type: `integer`):

Parallel requests for detail pages (feed pagination is sequential by design).

## `proxyConfig` (type: `object`):

Apify Proxy or custom proxies. Try direct first; if SMZDM's WAF blocks the run, residential proxy is used automatically.

## Actor input object example

```json
{
  "startUrls": [],
  "searchKeywords": [],
  "searchMaxPages": 3,
  "maxItems": 100,
  "malls": [],
  "categories": [],
  "minPrice": 0,
  "maxPrice": 0,
  "onlyPriceDrops": false,
  "requireTags": [],
  "minWorthy": 0,
  "minFavorite": 0,
  "dedupeByTitle": false,
  "includeDetail": false,
  "maxCommentsPerDeal": 30,
  "mallFilter": "",
  "skipSeenItems": false,
  "maxConcurrency": 2,
  "proxyConfig": {}
}
```

# Actor output Schema

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

All scraped deals and comments in the default dataset.

# 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 = {
    "startUrls": [],
    "searchKeywords": [],
    "malls": [],
    "categories": [],
    "requireTags": [],
    "proxyConfig": {}
};

// Run the Actor and wait for it to finish
const run = await client.actor("liver0914/smzdm-deals-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 = {
    "startUrls": [],
    "searchKeywords": [],
    "malls": [],
    "categories": [],
    "requireTags": [],
    "proxyConfig": {},
}

# Run the Actor and wait for it to finish
run = client.actor("liver0914/smzdm-deals-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 '{
  "startUrls": [],
  "searchKeywords": [],
  "malls": [],
  "categories": [],
  "requireTags": [],
  "proxyConfig": {}
}' |
apify call liver0914/smzdm-deals-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,liver0914/smzdm-deals-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/a31M1Epl4SZV2ZoqJ/builds/VJLowjPnfyxheBoHA/openapi.json
