# Cross-Network Trend Radar - Rising Topics (`yzy001/cross-network-trend-radar`) Actor

Find what is rising right now across Google Trends, Bluesky, Mastodon, Wikipedia and Hacker News, and flag the few topics rising on more than one at once. Every row carries its own volume scale and the provenance of its series, so a spike ratio appears only where a real daily series was measured.

- **URL**: https://apify.com/yzy001/cross-network-trend-radar.md
- **Developed by:** [宗諭 楊](https://apify.com/yzy001) (community)
- **Categories:** Social media, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 88.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 trend records

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

## Cross-Network Trend Radar

Most trend scrapers hand you a leaderboard. A leaderboard is what the platform
already publishes, so every scraper of that platform returns the same thing.

This one computes the two things no platform publishes about its own list:

1. **Is it actually rising**, or has it been there all week.
2. **Is it rising on more than one network at once** — the one signal a
   single-platform scraper cannot produce no matter how deep it goes.

### Sources

| Source | What you get | Where the rise signal comes from |
|---|---|---|
| **Google Trends** | 10 rising searches per country across **95 countries measured to serve data** (of 103 codes tested), each with an approximate traffic bucket and the minute it started rising | the platform's own start time |
| **Bluesky** | up to 25 topics with post count, category, start time, and a lifecycle status the platform computes itself | the platform's own `trending` / `saturating` / `cooling` / `stale` |
| **Mastodon / fediverse** | trending hashtags across the largest servers, each with seven days of daily use counts | a real daily series |
| **Wikipedia** | articles whose pageviews jumped, any language edition | a real daily series, fetched per article |
| **Hacker News** | front-page stories with points per hour | the story's own age |
| **Douyin** | the public hot-search board | snapshot only, no rise signal |

Every row says which of those it is, in `seriesProvenance`. A `spikeRatio`
appears **only** on rows whose series was genuinely measured day by day.

### The thing most trend data gets wrong

A rank list and a measurement look identical in a data structure, and they are
not the same thing. Wikipedia's daily top-1000 is a rank list: an article that
is absent from it today did not get zero views, it just placed below 1000th.
Inferring a series from rank presence produced these, measured:

| Article | Inferred from rank | Actually measured |
|---|---|---|
| Susan Dell | 14.8× | **58.4×** |
| Carrie (miniseries) | 1.3× | **2.6×** |
| Other Mommy | 1.3× | **2.3×** |

Three of four candidates were wrong, **in both directions**. So the top list is
used here only to pick candidates, and each candidate's real daily series is
fetched per article. A gate in the code refuses to emit a spike ratio on any row
whose series did not come from a real measurement.

### Cross-platform confirmation is a selective signal

Measured on live runs: 374 rows across 5 platforms produced 3 cross-platform
groups; 1,053 rows produced 6. **Between half a percent and one percent.**

That is the point of it. A light that comes on for everything tells you nothing.
But it means this layer gives you a handful of rows per run, not a full page —
the bulk of the volume is in the per-row `trendSignal`. If you want a long list,
read the rows; if you want the few things several independent networks noticed
at the same time, read the cross-platform records.

Matching terms across networks is conservative on purpose. The rule is
containment: the smaller set of distinguishing words has to be fully inside the
larger one. A single shared common word is not enough, because that produced
these false matches on live data:

- `supreme court of the united states` ↔ `Arsenal vs. Leeds United` — on *united*
- `state fair of texas` ↔ `James Talarico's Texas Senate` — on *texas*

Both are rejected now, by containment alone: only a third of the smaller term's
words are shared. Terms are also diacritic-folded, so `beşiktaş – kocaelispor`
and `besiktas – kocaelispor` are one topic.

The rule does not depend on how much you sweep. An earlier version also demanded
that a shared word not be common across the run, and that made the same two
terms match in a five-country sweep and not match in a ninety-five-country one,
because a popular word appears in more phrasings the more you collect. It is
gone.

What remains: a one-word term joins any phrase containing it, so `liverpool`
groups with `liverpool vs man city`. That is right for a watchlist and
occasionally too eager for grouping. Every group reports `matchedOn`, so you can
see why two terms were joined and disagree if you want to.

### Geographic spread

A full sweep of all 95 verified countries returns about 950 rising searches, and
that is where the second derived layer lives: the same search rising in many
countries at once. Measured on one sweep, **44 topics were rising in three or
more countries**, distributed like this:

| Countries | Topics |
|---|---|
| 51 | 1 |
| 35, 32, 29 | 1 each |
| 21, 17, 16, 13, 12, 11, 10, 9 | 1 each |
| 7 | 3 |
| 6 | 5 |
| 5 | 4 |
| 4 | 9 |
| 3 | 11 |

China, Iceland, Luxembourg, Malta, Mongolia, Brunei, Laos and Macau return
nothing and are excluded from the verified list rather than reported as
failures. If a large share of the requested countries stops answering, the run
fails — all 95 answered when measured, so a shortfall means the endpoint
changed or is throttling, not that there is no news.

#### One limitation worth knowing

Terms are matched on their words, so **the same event written in a different
script forms its own group.** On the sweep above, `liverpool - manchester city`
covered 51 countries and `ливерпуль – манчестер сити` covered 7 more
(Armenia, Azerbaijan, Belarus, Georgia, Israel, Kazakhstan, Uzbekistan) as a
separate row. That is the same football match. No transliteration is attempted
here, because guessing at it would create false matches that are harder to spot
than this one; the script of the term tells you which language sphere the group
describes.

### Countries are not platforms

A search rising in nine Google Trends countries is **not** nine networks
confirming it; it is Google seeing it in nine places. Those are two different
claims and they are reported in two different fields: `platformCount` and
`geoCount`. `minPlatforms` counts platforms only.

### Or watch terms you already care about

Discovery is one job; watching is another. Give it a list of terms — a brand, a
product, a competitor, a topic — and it reports whether each one is rising
anywhere across all the sources and all 95 countries, using the same requests.

A term that is rising nowhere **still gets a row**, saying so. That is the
answer to the question, and a gate enforces it: returning one row per term is
required, so you can never confuse "not rising" with "the run failed".

Matching uses the same containment rule as everything else, so watching `tesla`
finds `tesla valve` (the whole watched term is in it) but not `nikola motors`.

**Absent means not rising, not unpopular.** A live run watching six terms found
`liverpool` on 2 platforms in 45 countries, and found `bitcoin`, `nvidia`,
`taiwan` and `openai` nowhere at all. Those four are not obscure — they are
permanently popular, and a rising list is by construction a list of things that
just changed. Watch a term to learn when it breaks out, not to measure how big
it usually is.

### It builds its own history

Google Trends tells you when a search started rising but not what its volume was
yesterday. Douyin's board has no history at all. So the run remembers: each
topic's volume is written to a key-value store on your own account, and later
runs compare against it.

That adds `runsSeen`, `firstSeenAt`, `hoursTracked`, `volumeChange`,
`volumeChangePct` and `runSpikeRatio` — a measured series for sources that
publish none.

Two things to know. The first run has no history, so those fields are `null`,
not `0`. And the series is paced by **your** schedule: `runSpikeRatio` compares
this run against your previous runs, so the same number means something
different hourly than it does weekly. Every row therefore carries
`observationGapHoursMedian`, and a gate refuses to emit the ratio without it.

Runs less than 15 minutes apart count as one point in time: a run started right
after another (or two started together) does not compare against it, and the
stored series keeps the later observation. Ratios are only ever computed across
observations at least 15 minutes apart.

The store is pruned: 24 observations per topic, topics dropped after 30 days
unseen, 4,000 topics maximum.

### Volume scales do not convert

`volume` means a different thing per source — a Google traffic bucket, a Bluesky
post count, fediverse tag uses, Wikipedia pageviews, Hacker News points, a
Douyin hot value. Each row states which in `volumeKind`. Adding or ranking
across sources is wrong, so cutoffs are per source, and every run reports
`observedScalePerSource` with the real min / p50 / p90 / max so you can set
thresholds against the actual distribution instead of a guess.

Worth knowing: the fediverse aggregate ceiling is around 7,000 uses a day, so a
cutoff of 10,000 there returns nothing at all.

### On the path it takes

Google Trends exposes two things: a published RSS feed of what is rising, and the
data API behind the Trends web interface. The second one is richer — twelve
months of weekly history, related queries marked `Breakout` — and it is
rate-limited per IP hard enough that it is not usable at product scale without a
proxy pool. Measured: twenty-five keyword lookups through it completed **zero**
times; after about thirty-five requests even the cheap first call starts
returning 429, and a fresh session forty-five seconds later still gets 429.

The RSS feed has a separate quota and does not throttle. Measured: four full
sweeps of all 95 countries, 95 of 95 answering every time, no failures, about
0.8 seconds per country.

So this Actor reads the feed, and spends its effort on what the feed does not
give you: whether something is rising, whether several independent networks
noticed it at once, and what it looked like on your previous runs.

**It is not a replacement for a full Trends scraper.** If you need five years of
interest-over-time for a keyword you have chosen, this does not do that, and the
tools that do are running proxy infrastructure to get it. What this does is find
the things you had not thought to ask about.

### What is not here, and why

No TikTok, Instagram, Weibo or Reddit. Not because they are hard — because they
say no in writing. TikTok's robots file names AI crawlers individually and
disallows everything, and its `*` rules do not include the API path. Weibo and
Reddit disallow everything for `*`. Those endpoints do return data if you ask
them, which is exactly why the test here is the written policy and not the
status code.

Douyin is the one source whose host publishes no robots file at all, rather than
permitting this explicitly like the others, so it is off by default and the
choice is left to you.

### Output

`recordType` tells the records apart: `TREND` (one per topic per source),
`CROSS_PLATFORM`, `GEO_SPREAD`, `WATCH` (one per watched term), and one
`SUMMARY` carrying which sources answered, the signal distribution, the observed
scale per source, the history statistics, and the caveats above.

If fewer than half the enabled sources answer, the run fails rather than quietly
presenting single-source results as cross-platform findings.

# Actor input Schema

## `geos` (type: `array`):

Two-letter country codes for Google Trends, for example US, GB, TW, JP, DE. Each country returns its ten currently rising searches with an approximate traffic bucket and the time it started rising. All 20 countries tested returned data. Leave empty to skip Google Trends.

## `allGeos` (type: `boolean`):

Ignore the country list above and sweep every country Google Trends was measured to serve: 95 of 103 codes tested, ten rising searches each, about 950 rows. Costs one request per country. China, Iceland, Luxembourg, Malta, Mongolia, Brunei, Laos and Macau return nothing and are excluded.

## `includeBluesky` (type: `boolean`):

Poll Bluesky's official trends endpoint. It returns up to 25 topics, each with a post count, a category, the time it started, and a lifecycle status the platform computes itself (trending, saturating, cooling, stale).

## `includeMastodon` (type: `boolean`):

Poll trending hashtags across the largest fediverse servers. Each tag arrives with seven days of daily use counts, so a spike ratio and an emergent flag can be computed from a real series.

## `includeWikipedia` (type: `boolean`):

Find articles whose pageviews jumped. The daily top list is only used to pick candidates; every candidate's actual daily series is then fetched per article, because absence from the top list does not mean zero views.

## `includeHackerNews` (type: `boolean`):

Poll the official Hacker News API for front-page stories, with points per hour as the rising measure. Useful for technology topics, noise for everything else.

## `includeDouyin` (type: `boolean`):

Poll Douyin's public hot-search board. This source returns a snapshot with no history, so its rows never carry a spike ratio. Its host publishes no robots.txt, unlike the other sources which all permit this explicitly, so it is off by default and left to you.

## `mastodonServers` (type: `integer`):

How many of the largest fediverse servers to poll, from the official directory. More servers means better cross-server confirmation and more requests.

## `minServerUsers` (type: `integer`):

Skip fediverse servers with fewer registered users than this.

## `tagsPerServer` (type: `integer`):

How many trending hashtags to take from each server.

## `wikipediaLanguages` (type: `array`):

Wikipedia language editions to scan, for example en, ja, de, zh. Each language costs one top-list request plus one request per candidate article.

## `wikipediaCandidates` (type: `integer`):

How many of the day's most-viewed articles to check for a jump. Each one costs a request for its real daily series.

## `hackerNewsStories` (type: `integer`):

How many front-page stories to fetch. Each one costs a request.

## `newWithinHours` (type: `integer`):

A topic whose source-reported start time is younger than this counts as rising, even without a spike ratio. Google Trends and Bluesky both report a start time.

## `risingSpikeRatio` (type: `number`):

For sources with a real daily series, today must be at least this many times the mean of the prior days to count as rising.

## `onlyRising` (type: `boolean`):

Drop everything that is steady, peaking, declining or stale.

## `minGoogleTraffic` (type: `integer`):

Google reports an approximate lower bound such as 2000+. Observed range across countries runs from 100 to a few thousand, so 1000 is already selective. Zero disables this cutoff.

## `minBlueskyPosts` (type: `integer`):

Observed post counts run from the low hundreds to about 100,000, with a median near 1,000.

## `minMastodonUses` (type: `integer`):

Aggregated across the polled servers. Observed median is about 23 and the ceiling about 7,000, so a cutoff of 10,000 returns nothing. 200 to 500 is the selective range.

## `minWikipediaViews` (type: `integer`):

Articles on the daily top list routinely clear 100,000 views, so cutoffs here are in the tens of thousands.

## `minHackerNewsPoints` (type: `integer`):

Front-page stories typically sit between 50 and 1,000 points.

## `minDouyinHotValue` (type: `integer`):

Douyin's own hot value, observed in the millions.

## `minPlatforms` (type: `integer`):

How many distinct platforms a topic must appear on to be reported as cross-platform confirmed. Countries within Google Trends do not count towards this; they are reported separately as geographic spread.

## `minGeos` (type: `integer`):

How many Google Trends countries a search must rise in to be reported as spreading geographically.

## `minSourceShare` (type: `number`):

If fewer than this share of the enabled sources return data, the run fails instead of quietly reporting single-source results as cross-platform findings.

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

Cap on individual trend rows. Cross-platform and geographic-spread records are always emitted in full.

## `minGeoShare` (type: `number`):

If fewer than this share of the requested countries return data, the run fails. All 95 verified countries answered when measured, so a large shortfall means the endpoint changed or is throttling, not that there is no news.

## `trackAcrossRuns` (type: `boolean`):

Remember what each topic's volume was on your previous runs, in a key-value store on your own account, so snapshot-only sources gain a real measured series. Adds runsSeen, firstSeenAt, volumeChange and runSpikeRatio. The first run has no history, so those fields are null rather than zero, and the series is paced by your schedule, not by days.

## `watchTerms` (type: `array`):

Instead of only discovering what is rising, check whether these specific terms are rising anywhere — a brand, a product, a competitor, a topic. One record per term is returned either way: a term that is rising nowhere still gets a row saying so, because that is the answer and not a failure. Costs no extra requests.

## Actor input object example

```json
{
  "geos": [
    "US",
    "GB",
    "TW",
    "JP",
    "DE"
  ],
  "allGeos": false,
  "includeBluesky": true,
  "includeMastodon": true,
  "includeWikipedia": true,
  "includeHackerNews": false,
  "includeDouyin": false,
  "mastodonServers": 12,
  "minServerUsers": 1000,
  "tagsPerServer": 20,
  "wikipediaLanguages": [
    "en"
  ],
  "wikipediaCandidates": 40,
  "hackerNewsStories": 40,
  "newWithinHours": 24,
  "risingSpikeRatio": 2,
  "onlyRising": false,
  "minGoogleTraffic": 0,
  "minBlueskyPosts": 0,
  "minMastodonUses": 0,
  "minWikipediaViews": 0,
  "minHackerNewsPoints": 0,
  "minDouyinHotValue": 0,
  "minPlatforms": 2,
  "minGeos": 2,
  "minSourceShare": 0.5,
  "maxItems": 500,
  "minGeoShare": 0.7,
  "trackAcrossRuns": true,
  "watchTerms": []
}
```

# Actor output Schema

## `trends` (type: `string`):

Topic, source, country, trend signal, volume with its scale, age in hours, spike ratio where a real series exists, and whether the topic is confirmed on another platform.

## `crossPlatform` (type: `string`):

The rare topics rising on two or more independent platforms at once, each with the platforms involved, what the terms matched on, and the youngest age among them. A selective signal: expect a handful per run, not a full page.

# 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 = {
    "geos": [
        "US",
        "GB",
        "TW",
        "JP",
        "DE"
    ],
    "mastodonServers": 12,
    "wikipediaLanguages": [
        "en"
    ],
    "maxItems": 500
};

// Run the Actor and wait for it to finish
const run = await client.actor("yzy001/cross-network-trend-radar").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 = {
    "geos": [
        "US",
        "GB",
        "TW",
        "JP",
        "DE",
    ],
    "mastodonServers": 12,
    "wikipediaLanguages": ["en"],
    "maxItems": 500,
}

# Run the Actor and wait for it to finish
run = client.actor("yzy001/cross-network-trend-radar").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 '{
  "geos": [
    "US",
    "GB",
    "TW",
    "JP",
    "DE"
  ],
  "mastodonServers": 12,
  "wikipediaLanguages": [
    "en"
  ],
  "maxItems": 500
}' |
apify call yzy001/cross-network-trend-radar --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yzy001/cross-network-trend-radar"
        }
    }
}
```

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/IkP9Df3KjfOTI1Yqg/builds/1xUx45M0chEZQc6dr/openapi.json
