# Dezeen Articles Scraper (`scrapyx/dezeen-articles-scraper`) Actor

Searches Dezeen, the world's most-read architecture and design publication, and returns full article records — title, author, categories, tags, image, publish date — from its public WordPress API, with optional comment threads.

- **URL**: https://apify.com/scrapyx/dezeen-articles-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Categories:** News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 results

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Dezeen Articles Scraper

Searches **[Dezeen](https://www.dezeen.com)** — the world's most-read
architecture, interiors and design publication (62,000+ articles, 1,135
categories, 10,800+ tags, 535 authors) — and returns full article records:
title, author, categories, tags, image and publish date, straight from its
public WordPress API, with optional comment threads.

Public data only. No login, no cookies, no browser, no bot challenge on any
profile this actor uses.

### What you get

Four record types share one dataset, told apart by `recordType`.

#### `POST` — one row per article

Raw passthrough of Dezeen's own WordPress REST object (`id`, `date`,
`slug`, `link`, `title`, `excerpt`, `content`, `author`, `categories`,
`tags`, `featured_media`, `jetpack_featured_media_url`, …) plus flattened
`article*` convenience fields (plain-text title/excerpt, resolved category
and author **names** — not just numeric ids — publish/modified timestamps,
image URL).

#### `SEARCH_SUMMARY` — one row per query

Upstream's own `X-WP-Total` count for that exact filtered query, how many
rows this run pulled, how many pages it fetched, and every filter that was
actually applied (in both slug and resolved-id form) — so a row can be
audited without rerunning the query.

#### `COMMENT` — one row per comment (opt-in)

Only emitted when **Include comments** is on: comment author, text, date
and the parent comment id for threading, batched across articles rather
than one request per article. Dezeen carries a real comment corpus
(191,836 sitewide) — this is not a symbolic feature.

#### `ERROR` — one row per input that failed

So every query/URL maps to at least one row, even when it fails.

### Input

| Field | What it does |
| --- | --- |
| **Search queries** | one full-text search per entry — e.g. `sustainable architecture`, `3D printing` |
| **Direct article URLs** | fetch specific articles by exact URL instead of searching |
| **Filter by category / tag / author (slugs)** | narrow every query — the slug from the term's own URL on dezeen.com, not the display name |
| **Published after / before** | date window, `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS` (UTC) |
| **Max articles per query** | `0` = unlimited, up to `maxPagesPerQuery` |
| **Max pages per query** | caps how deep an unlimited crawl goes |
| **Include full article HTML** | adds the full rendered article body; on by default |
| **Include comments** | adds `COMMENT` rows, batched; off by default |

Leave every query field empty to fetch the **latest articles** instead —
a first-class mode, not an edge case, optionally still narrowed by the
category/tag/author/date filters.

Dezeen categorises far more granularly than most sites in this family:
alongside editorial sections (`architecture`, `interiors`, `design`,
`technology`) it has 1,135 categories total, including individual
product/material/studio names.

#### Example

```json
{
  "searchQueries": ["sustainable architecture"],
  "categorySlugs": ["architecture"],
  "maxItemsPerQuery": 30,
  "includeComments": true,
  "maxCommentsPerArticle": 20
}
```

### Filters are verified, not guessed — and refuse rather than lie

Every filter this actor exposes was checked against Dezeen's live API
before being offered, per this portfolio's trap checklist:

- **Category/tag/author slugs must resolve to a real term.** `?slug=` on
  Dezeen's taxonomy routes answers HTTP 200 with an **empty list** for a
  bogus slug — not an error. Silently dropping an unresolvable slug would
  serve the whole unfiltered 62,000-article archive under a filtered
  label, so this actor raises instead and the run ends with a named
  `ERROR` row (`unresolved_term`) telling you exactly which slug failed.
- **A combined category + tag filter narrows honestly.** Verified with a
  real intersection (`architecture` + `sustainable-architecture` →
  exactly 299 results, not the unfiltered count of either alone).
- **`sortBy` is a verified 5-value enum.** `relevance` is deliberately
  *not* offered as a free-standing choice — upstream only allows it
  alongside a search query (HTTP 400 otherwise) — so it is applied
  automatically whenever `searchQueries` is set.
- **Pagination ends honestly.** Asking past the last page answers a plain
  HTTP 400 `rest_post_invalid_page_number`, never a silent re-serve of
  page 1.

### Notes on reliability

- **No WAF on the profiles this actor uses.** `chrome124`, `firefox133`
  and `safari17_0` all return identical 200s, cold, on every JSON endpoint
  used. A mobile profile (`chrome99_android`) does get a reproducible HTTP
  403 — it is deliberately excluded from this actor's rotation pool.
- **`robots.txt`** disallows only `/wp-admin/` (with `admin-ajax.php`
  explicitly allowed back) — no ClaudeBot/anthropic-ai/CCBot/GPTBot group
  of any kind, no `Crawl-delay`, and `/wp-json/` is untouched. Policy-open.
- **Requests go to `www.dezeen.com` directly** — the bare domain 302s to
  `www`, so using it would add a redirect hop to every request.
- **Comment fetches are batched**, not one request per article.
- **Residential proxy, no country pin** is the cloud default — no geo-gate
  observed in recon.

### Known limits

- **Full-text search is relevance-based, not exact-phrase.**
- **`resultsPerRequest` is hard-capped at 100** by WordPress core.

# Actor input Schema

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

One search per entry, each with its own SEARCH\_SUMMARY row — e.g. `sustainable architecture`, `3D printing`. This is WordPress full-text search across title/content, not exact-phrase matching. Leave empty (with 'Post URLs' also empty) to fetch the latest articles instead, optionally narrowed by the category/tag/author/date filters below.

## `postUrls` (type: `array`):

Full dezeen.com article URLs (e.g. https://www.dezeen.com/2026/08/19/some-slug/) to fetch by exact match instead of searching. Each becomes its own POST row (plus COMMENT rows if enabled below). Combine freely with 'Search queries'.

## `categorySlugs` (type: `array`):

Restrict every query to these categories, e.g. `architecture`, `interiors`, `design`, `technology` — the slug from the category's own URL on dezeen.com, not the display name. Dezeen also categorises by material/product/studio name (1,135 categories total), not just editorial section. A slug that does not exist on the site fails the run with an ERROR row naming the bad slug, rather than silently returning the unfiltered archive.

## `tagSlugs` (type: `array`):

Restrict every query to these tags, e.g. `sustainable-architecture`, `sustainable-design`, `house` — the slug from the tag's own URL. Same fail-loud behaviour as category slugs on an unresolvable value.

## `authorSlugs` (type: `array`):

Restrict every query to these authors, e.g. `aaronbetsky` — the slug from /author/<slug>/ on dezeen.com. Same fail-loud behaviour as category/tag slugs.

## `publishedAfter` (type: `string`):

Only articles published on or after this date/time. Format YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS (UTC). Leave empty for no lower bound.

## `publishedBefore` (type: `string`):

Only articles published before this date/time. Format YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS (UTC). Leave empty for no upper bound.

## `sortBy` (type: `string`):

How results are ordered. Ignored (upstream forces relevance ordering) whenever a search query is non-empty — set here it only affects filter-only / latest-articles runs.

## `sortOrder` (type: `string`):

Ascending or descending for the 'Sort by' field above.

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

Stop after this many articles per search query / URL / latest-articles run. Set to 0 for unlimited (walks every page 'Max pages per query' allows).

## `maxPagesPerQuery` (type: `integer`):

Caps how deep the crawl walks per query, even when 'Max articles per query' is unlimited. Each page holds up to 'Results per request' articles.

## `resultsPerRequest` (type: `integer`):

Articles fetched per HTTP request (upstream's own per\_page parameter). Hard-capped at 100 by the API — any higher value fails the run.

## `includeArticleContent` (type: `boolean`):

The full rendered article body HTML, in addition to title/excerpt/author/categories/tags/image. On by default -- full article content is the main reason to use this actor. Set to false only to save payload weight when you genuinely just need headlines/metadata.

## `includeComments` (type: `boolean`):

Fetch each article's comments as separate COMMENT rows (batched, not one request per article). Off by default since most articles carry zero-to-dozens of comments and many use cases only need the articles themselves.

## `maxCommentsPerArticle` (type: `integer`):

Only used when 'Include comments' is on. Set to 0 for unlimited (all comments on the article).

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

Upper bound on requests in flight at once, across all queries, taxonomy lookups and comment fetches.

## `minRequestInterval` (type: `number`):

Shared pacing floor across all requests this run makes, independent of 'Max concurrent requests'. robots.txt sets no Crawl-delay for this host; the default is this portfolio's own politeness baseline, not a site requirement.

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

Apify Proxy on the shared datacenter pool. This is the default because it is included in your plan at no extra cost and this target works through it. If you start seeing blocks, challenges or empty results, switch the group here to Residential -- it uses real consumer IPs and gets through more, but Apify bills residential traffic per gigabyte, so leave it off unless you need it.

## Actor input object example

```json
{
  "searchQueries": [
    "sustainable architecture"
  ],
  "postUrls": [],
  "categorySlugs": [],
  "tagSlugs": [],
  "authorSlugs": [],
  "publishedAfter": "",
  "publishedBefore": "",
  "sortBy": "date",
  "sortOrder": "desc",
  "maxItemsPerQuery": 25,
  "maxPagesPerQuery": 10,
  "resultsPerRequest": 25,
  "includeArticleContent": true,
  "includeComments": false,
  "maxCommentsPerArticle": 50,
  "maxConcurrency": 5,
  "minRequestInterval": 0.25,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per scraped record. See the dataset's default view for field definitions.

# 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": [
        "sustainable architecture"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/dezeen-articles-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": ["sustainable architecture"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/dezeen-articles-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": [
    "sustainable architecture"
  ]
}' |
apify call scrapyx/dezeen-articles-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/dezeen-articles-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/1V239UzwPi10JtDVK/builds/ejRKPqi6YC6DNgCqo/openapi.json
