# Hacker News Trending Tracker - Track Rising Stories (`quaffable_mettle/hacker-news`) Actor

Track Hacker News front-page stories, rankings, scores, comments and ranking velocity. Identify fast-rising topics and monitor Hacker News trends for tech research, content discovery and market intelligence.

- **URL**: https://apify.com/quaffable\_mettle/hacker-news.md
- **Developed by:** [Emir Llonviche](https://apify.com/quaffable_mettle) (community)
- **Categories:** News, Developer tools, Social media
- **Stats:** 1 total users, 0 monthly users, 92.9% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

$20.00 / 1,000 page processeds

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?

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

## Hacker News Front Page Tracker — Rank & Real Velocity

Track the Hacker News front page and see **what actually moved since your last run**: rank changes, points gained, comments gained, new entrants, and how long each story has held its place.

*This is an unofficial scraper. It is not affiliated with, endorsed by, or sponsored by Y Combinator or Hacker News.*

No API key, no proxies, no login. A one-page run takes a couple of seconds.

### Two things no other Hacker News Actor gives you

**Rank.** The Hacker News API — Firebase and Algolia alike — exposes no position at all. A story's rank exists only in the page itself. This Actor reads it, remembers it, and tells you `rankChange`, `peakRank` and `hoursOnFrontPage`.

**Velocity that was measured rather than divided.** The usual "points per hour" is a story's total score divided by its age: a lifetime average. It cannot distinguish a story climbing hard right now from one that surged overnight and has since gone flat — both keep the same number.

This Actor stores each run and reports the difference:

| | A story that surged 6 hours ago | The same story, still on the page |
|---|---|---|
| Points | 500 | 500 |
| `scorePerHourAverage` (what others report) | 41.7 /h | 41.7 /h |
| **`scorePerHourObserved`** (what this reports) | — | **0 /h** |

Same row, same score. One number says it is on fire; the other says it stopped an hour ago.

### What you get

| Field | What it is |
|---|---|
| `rank`, **`rankChange`**, **`peakRank`** | Position now, positions gained since your last run (12 → 4 = `+8`), best rank ever held |
| **`isNew`** | The story was not on the page on your previous run |
| **`hoursOnFrontPage`** | How long it has held the page, uninterrupted |
| `score`, **`scoreChange`**, **`scorePerHourObserved`** | Points now, points gained since last run, and the rate that implies |
| `scorePerHourAverage` | The lifetime average, for comparison |
| `comments`, **`commentsChange`** | Discussion size and its growth |
| `title`, `url`, `domain`, `hnUrl` | The story, the link, its site, the discussion |
| `ageHours`, `postedAt`, `type`, `page`, `scrapedAt` | When it was posted, what kind of item, where it sat |

On the **first** run there is nothing to compare against, so every movement field is `null` — not `0`, not `false`. Reporting "no change" from a single observation would be a claim the data cannot support. Run it a second time and the movement appears.

### No personal data, by construction

The front page markup contains an author username on every row, and the API returns both `by` and the body text of self-posts. **None of it is read.**

The HTML parser extracts exactly two things: a story id and a rank number. Everything else comes from the API, where `by` and `text` are dropped at the boundary — not filtered downstream, never carried in the first place. There is a test that fails if an author name ever appears in the output.

That is a deliberate design choice, and it has a side effect worth knowing: because the HTML is only ever asked for a number and an id, a restyle of Hacker News cannot silently corrupt your data. At worst the run stops and tells you.

### Input

```json
{
  "pages": 1,
  "minScore": 0,
  "onlyMovers": false,
  "timeoutSecs": 30
}
```

- **pages** — 1 gives the 30-story front page, 3 gives the top 90. Pages after the first are spaced 30 seconds apart to honour the crawl delay Hacker News asks for in its robots.txt, so a 3-page run takes about a minute.
- **minScore** — keep only stories at or above a point threshold.
- **onlyMovers** — return only what entered, changed rank, or gained points. Ignored on the first run.

### Typical uses

- Catch a story climbing before it reaches the top — high `scorePerHourObserved` at a middling rank.
- Watch your own launch or your competitor's: rank, velocity, and the moment it stalls.
- Track which domains gain attention, in `DOMAIN_MOMENTUM`, ranked by points gained rather than by how many stories they have parked on the page.
- Feed front-page movement into a dashboard or a newsletter.

### Pricing

**$0.02 per page read.** A front-page run costs $0.02; three pages cost $0.06. Hourly for a month is about $14.

### Running it on a schedule

Movement needs a previous run, so this Actor is built for a schedule. **Hourly is the sweet spot**: the front page turns over fast enough that a daily run will miss most of the climbing, and much more often than hourly gives you deltas too small to read.

### Limits, stated plainly

- **`rankChange` compares against your previous run, whatever its age.** Two runs a week apart give you a week-long delta, not an hourly one. `hoursSincePreviousRun` in the summary tells you which you got.
- **A story that leaves the page and returns counts as new.** `hoursOnFrontPage` measures an uninterrupted stay.
- Hacker News asks for a 30-second crawl delay and will answer **503** if pushed. The Actor waits between pages and reports a 503 in plain language rather than as a crash.
- Beyond page 5 the front page thins out, so that is the cap.
- Deleted and dead items are dropped rather than published as empty rows.
- If more than half the stories come back without details, the run **fails** instead of publishing a half-empty dataset. A quiet, plausible-looking dataset is worse than a loud failure.

### Output

Alongside the dataset, each run writes `DOMAIN_MOMENTUM` (sites ranked by points gained) and `RUN_SUMMARY` (new entrants, climbers, hours since the previous run, stories missing details). Point a monitor at the summary and you will know something broke before your users tell you.

### Support

Found a bug, or want a field that is not here? Open an issue on the Actor page.

# Actor input Schema

## `pages` (type: `integer`):

Each page holds 30 stories: 1 gives the front page, 3 gives the top 90. Pages after the first are spaced 30 seconds apart to honour the crawl delay Hacker News asks for, so a 3-page run takes about a minute.

## `minScore` (type: `integer`):

Keep only stories with at least this many points. Leave at 0 to keep the whole page.

## `onlyMovers` (type: `boolean`):

Return only stories that entered the page, changed rank, or gained points since your previous run. Ignored on the first run, which has nothing to compare against.

## `timeoutSecs` (type: `integer`):

How long to wait for each request before retrying. Raise it only if you see timeout warnings in the log.

## Actor input object example

```json
{
  "pages": 1,
  "minScore": 0,
  "onlyMovers": false,
  "timeoutSecs": 30
}
```

# Actor output Schema

## `stories` (type: `string`):

One row per story: rank, what it moved since your last run, points and comments gained, and how long it has held the page.

## `domainMomentum` (type: `string`):

Which sites gained the most attention this run, ranked by points gained rather than by how many stories they have parked on the page.

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

New entrants, climbers, hours since the previous run, and any stories the API did not return. Point a monitor at this.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("quaffable_mettle/hacker-news").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("quaffable_mettle/hacker-news").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 '{}' |
apify call quaffable_mettle/hacker-news --silent --output-dataset

```

## MCP server setup

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

```

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/3biqAdZGAawmJXiiL/builds/dRIEp6Jiq3Z5i9IBE/openapi.json
