# POD Trend Scraper — Redbubble & TeePublic (`retrolabstudio/pod-trend-scraper`) Actor

Scrape public, robots.txt-allowed Redbubble and TeePublic listing pages for print-on-demand niche research: ranked titles, shops, prices, product types, tags and image URLs.

- **URL**: https://apify.com/retrolabstudio/pod-trend-scraper.md
- **Developed by:** [Furkan Yalçıntaş](https://apify.com/retrolabstudio) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 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.

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

## POD Trend Scraper — Redbubble niche research

Find out **which designs, tags and product types rank** on Redbubble, one of the largest print-on-demand
(POD) marketplaces. Give the Actor a list of niches or keywords and get one clean, ranked table of Redbubble
products with titles, shops, prices, product types and full tag lists.

> **TeePublic support is experimental** and off by default. TeePublic's anti-bot protection blocks many
> cloud / datacenter IP addresses (HTTP 403), so a TeePublic run can return 0 items. Turn it on with
> `"sources": "both"` or `"teepublic"` only if you can live with that (a proxy may help).

**Robots-compliant:** only public listing pages are read (Redbubble `/shop/<term>`, experimental TeePublic
`/t-shirts/<term>`), with no search query or sort parameters. Each URL is checked against the site's
live robots.txt before it is requested; a disallowed URL is skipped and reported.

Built for POD sellers, niche researchers and designers who want to answer questions like:

- Which "retro gaming" designs rank at the top right now, and on which products?
- Which tags do top-ranked "cat" designs use?
- What price points do sticker and t-shirt listings sit at?

### What it extracts

One dataset row per product listing, in the marketplace's own rank order:

| Field | Description |
|---|---|
| `source` | `redbubble` or `teepublic` |
| `query` | the search term that produced the row |
| `rank` | 1-based position in the results for that term and marketplace (after de-duplication) |
| `title` | listing title as shown on the marketplace |
| `artist` | public shop / artist handle |
| `price`, `currency` | current price as a number + ISO currency code |
| `originalPrice` | pre-discount price when a sale is running (Redbubble), otherwise `null` |
| `url` | absolute product URL |
| `imageUrl` | product preview image |
| `productType` | e.g. `Classic T-Shirt`, `Sticker`, `Essential T-Shirt` |
| `tags` | design tags (Redbubble: full list; TeePublic: main tag + tags shown on the search tile) |
| `rating`, `reviewCount` | always `null` for now — neither marketplace shows ratings on search pages |
| `sourceId` | marketplace item / design id |
| `sortBy`, `page`, `scrapedAt` | order used (`relevant`), result page number and UTC timestamp |

A `RUN_SUMMARY` record in the key-value store lists items per marketplace and per search term, how many
pages were fetched, blocked requests and any warnings.

### Input

| Field | Default | Notes |
|---|---|---|
| `queries` | — (required) | list of search terms, max 50 |
| `sources` | `redbubble` | `redbubble`, `both` or `teepublic` (TeePublic = experimental, see above) |
| `sortBy` | `relevant` | only `relevant` (the marketplace's default order); other values are ignored with a warning |
| `productType` | `all` | `all`, `t-shirt`, `sticker`, `hoodie`, `mug`, `phone-case`, `tank-top`, `kids-t-shirt`, `poster`, `tote-bag` |
| `maxItemsPerQuery` | `100` | unique products per term **per marketplace** (1-1000) |
| `maxPages` | `5` | hard cap on result pages per term and marketplace (1-20) |
| `proxyConfiguration` | Apify residential | marketplaces block datacenter IPs; proxy traffic is included in the price |

Example:

```json
{
  "queries": ["cat", "retro gaming"],
  "productType": "all",
  "maxItemsPerQuery": 100,
  "maxPages": 5
}
```

How the options map to each marketplace:

- **Sort** — both sites' robots.txt disallow sort parameters, so results come in the marketplace's
  default (relevance) order. `trending` / `top-selling` / `newest` are accepted but ignored with a warning.
- **Search term** — turned into the listing path: `retro gaming` becomes Redbubble `/shop/retro+gaming`
  and TeePublic `/t-shirts/retro-gaming` (TeePublic's tag pages).
- **Product type** — TeePublic has no "all departments" search, so `all` uses its t-shirt listing.
  TeePublic has no poster or tote-bag listing; for those types only Redbubble is scraped.
  On Redbubble `kids-t-shirt` lists kids clothing (there is no kids-tee listing path).
- Redbubble returns about 90 products per page, TeePublic 36.

### Output example

Real rows from runs on 2026-10-07 (tags shortened; the TeePublic row comes from the experimental source):

```json
[
  {
    "source": "redbubble",
    "query": "cat",
    "rank": 2,
    "title": "Everything’s good cat",
    "url": "https://www.redbubble.com/i/sticker/Everything-s-good-cat-by-blah707/43977882/7sgk",
    "price": 2.03,
    "currency": "USD",
    "originalPrice": 2.7,
    "artist": "blah707",
    "imageUrl": "https://ih1.redbubble.net/image.1030459700.7882/st,small,507x507-pad,600x600,f8f8f8.u1.jpg",
    "productType": "Sticker",
    "tags": [
      "cat",
      "crying",
      "funny",
      "white",
      "black",
      "meme"
    ],
    "rating": null,
    "reviewCount": null,
    "sourceId": "43977882_sticky-runners_EJUG5",
    "sortBy": "relevant",
    "page": 1,
    "scrapedAt": "2026-10-07T19:52:50Z"
  },
  {
    "source": "teepublic",
    "query": "cat",
    "rank": 1,
    "title": "Cat T-Shirt",
    "url": "https://www.teepublic.com/t-shirt/46060798-cat",
    "price": 24.0,
    "currency": "USD",
    "originalPrice": null,
    "artist": "theladyernestember",
    "imageUrl": "https://images.teepublic.com/derived/production/designs/46060798_2/1685673247/i_m:bi_production_blanks_h778z1f0n6g0xugjpxxm_1462829020,c_19_0_432x,s_313,q_90.jpg",
    "productType": "Classic T-Shirt",
    "tags": [
      "cat",
      "adorable",
      "animal",
      "aww",
      "awww",
      "cartoon"
    ],
    "rating": null,
    "reviewCount": null,
    "sourceId": "46060798",
    "sortBy": "relevant",
    "page": 1,
    "scrapedAt": "2026-10-07T19:54:01Z"
  }
]
```

The dataset has two views: **Products** (image, rank, title, shop, price, product type, tags, URL) and
**Tags** (title + tags only, handy for keyword research). Export as JSON, CSV, Excel or via the API.

### Pricing

Pay per event: **$0.003 per product** saved to the dataset (`product-scraped`, i.e. $3 per 1,000 products)
plus Apify's standard Actor start event ($0.00005 per run). A typical run of 2 keywords x 100 products costs
about $0.60. See the Pricing tab for the current prices. Set a maximum cost per run in the run options and
the Actor stops cleanly when it is reached.

### Limits and good to know

- Only **public listing pages** are read, and only URLs that robots.txt allows. No login, no account
  pages, no checkout, no search query or sort parameters.
- **No personal data** is collected: no e-mails, phone numbers or customer reviews — only catalogue data
  and the public shop handle that is part of each listing.
- Requests are sequential with a 1-2 second pause between pages and exponential backoff on HTTP 403/429.
  After two blocked pages in a row a marketplace is skipped for the rest of the run and a warning is
  shown in the run status. If that happens, enable a proxy in `proxyConfiguration`.
- TeePublic is **experimental**: its anti-bot protection blocks many networks, including most cloud IPs.
  If TeePublic returns 0 items, the run summary shows the blocked requests; you are only charged for
  products actually saved.
- TeePublic search tiles show a shortened tag list, so some TeePublic tags can be cut off mid-word.
- If a marketplace changes its page layout and nothing can be parsed, the run status says
  `0 items parsed — selector may be outdated`. Please open an issue so the parser can be updated.
- Prices are the ones shown to the scraper's location (normally USD).

### Legal

This Actor only accesses publicly available pages, at a modest request rate, and does not collect
personal data. You are responsible for using the data in line with the marketplaces' terms and
applicable law (for example copyright and trademark rules when creating designs). Do not copy other
artists' designs.

**Not affiliated with, endorsed by or sponsored by Redbubble or TeePublic.** Redbubble and TeePublic are
trademarks of their respective owners.

### Run locally and deploy

```bash
pip install -r requirements.txt
python -m unittest discover -s tests -t .       # offline tests (HTML fixtures)
APIFY_LIVE=1 python -m unittest tests.test_live  # optional live check
./local_run.sh '{"queries": ["cat"], "maxItemsPerQuery": 20}'       # apify run + copies results to cikti/
apify login          # once; paste your API token from Console > Settings > API & Integrations
apify push           # builds the Docker image on the Apify platform
```

`.actor/pay_per_event.json` is **not** read by the Apify platform. It only feeds the local pay-per-event
simulation in `local_run.sh` (`ACTOR_TEST_PAY_PER_EVENT`). The real event prices are entered in Apify Console
(Publication > Monetization > Pay per event) and must match the event names used in `src/main.py`.

# Actor input Schema

## `queries` (type: `array`):

Keywords or niches, e.g. 'cat', 'retro gaming'. Each term is turned into the marketplace's public listing page (Redbubble /shop/retro+gaming, TeePublic /t-shirts/retro-gaming).

## `sources` (type: `string`):

Which marketplaces to search. Redbubble is the supported source. TeePublic is EXPERIMENTAL: its anti-bot protection blocks many cloud/datacenter IPs (HTTP 403), so it may return 0 items without a residential proxy.

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

Only 'relevant' (the marketplaces' default order). Both sites' robots.txt disallow sort parameters, so 'trending', 'top-selling' and 'newest' are accepted for compatibility but ignored with a warning.

## `productType` (type: `string`):

Product category. TeePublic has no all-departments listing ('all' = TeePublic t-shirts) and no poster / tote-bag listing (TeePublic is skipped for those). On Redbubble 'kids-t-shirt' lists kids clothing.

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

Stop after this many unique products for each term on each marketplace.

## `maxPages` (type: `integer`):

Hard cap on search pages requested per term and marketplace (Redbubble ~90, TeePublic 36 products per page).

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

Residential proxy by default: Redbubble/TeePublic block datacenter IPs. Proxy traffic is included in the price; change only if you bring your own proxies.

## Actor input object example

```json
{
  "queries": [
    "cat",
    "retro gaming"
  ],
  "sources": "redbubble",
  "sortBy": "relevant",
  "productType": "all",
  "maxItemsPerQuery": 100,
  "maxPages": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `products` (type: `string`):

All scraped product listings (overview view).

## `tags` (type: `string`):

Title + tags view for niche research.

## `runSummary` (type: `string`):

Counts per marketplace and search term, blocked requests and warnings.

# 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 = {
    "queries": [
        "cat",
        "retro gaming"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("retrolabstudio/pod-trend-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 = {
    "queries": [
        "cat",
        "retro gaming",
    ],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("retrolabstudio/pod-trend-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 '{
  "queries": [
    "cat",
    "retro gaming"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call retrolabstudio/pod-trend-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,retrolabstudio/pod-trend-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/GxTQJdBcHtnkmjcVE/builds/Qx75qHXuUAPFX8AXi/openapi.json
