This repository shows how to pull Product Hunt launches into Python through the Product Hunt Scraper on Apify. The Actor reads Product Hunt's RSS feed in the cloud and hands back one record per launch: the product, its one-line pitch, the maker credited in the feed, the topics it was filed under and when it went live. Your side of the job is a short script that starts the run and saves the records as results.json and results.csv. From there it is ordinary data work: a DataFrame, a notebook chart, or a growing archive that a scheduler appends to every morning.
Actor: Product Hunt Scraper — Daily Launch Data · Publisher: Devil Scrapes · Also in JavaScript: producthunt-scraper-javascript
- A row for every launch in the feed, up to your
maxResultscap, in the order Product Hunt lists them. - Name and tagline split for you: the raw
titleis kept, and the Actor also breaks it intonameandtaglinewhen it contains an em dash, a spaced hyphen or a colon. - Topics as a real list:
categoriesarrives as an array of strings, so filtering by topic needs no string parsing. - Normalised timestamps:
publishedis an ISO-8601 value, andscraped_atrecords when each row was captured. - Optional HTML body: set
includeContenttofalseandcontent_htmlstaysnull, which keeps rows small for monitoring jobs. - Any Product Hunt RSS address:
feedUrldefaults to the main launch feed, and the input schema accepts other Product Hunt RSS URLs, such as a topic feed.
An RSS feed only remembers its latest batch of items. Run this script once or twice a day and merge each result into a local file keyed on link, and after a few weeks you own a history of launches that you can query long after the feed has rolled over.
Explode the categories list in pandas and count launches per topic per week. A rising count in a topic such as developer tools or AI is a cheap leading signal for analysts, newsletter writers and anyone choosing where to build next.
Investors and market researchers can filter a day's launches by tagline keywords inside Jupyter, mark the interesting ones and export a short CSV for the weekly meeting. The link column takes reviewers straight to each launch page.
Product teams can run a keyword match over name and tagline after each scheduled run. Anything that looks like a competitor lands in a review list with its launch time attached, instead of being noticed weeks later.
You need an Apify account and an API token from Console → Settings → Integrations. Apify's free plan includes monthly usage credit; this Actor's event prices are listed under Pricing below.
git clone https://github.com/devilscrapes/producthunt-scraper-python.git
cd producthunt-scraper-python
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export APIFY_TOKEN=your_token_here
python main.py # uses input.json
python main.py my-input.json --out data --max-charge 2The script starts the Product Hunt Scraper — Daily Launch Data, waits for it to finish, prints a link to the run in Apify Console and saves every result to results.json and results.csv. It exits with code 1 and the run's status message if the run fails.
Stripped to its minimum, the integration fits in five lines. apify-client starts the Actor, blocks until the run ends, and then lets you iterate over the dataset it produced.
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("DevilScrapes/producthunt-launches-scraper").call(run_input={'feedUrl': 'https://www.producthunt.com/feed', 'maxResults': 10, 'includeContent': True, 'proxyConfiguration': {'useApifyProxy': True}})
for item in client.dataset(run.default_dataset_id).iterate_items():
print(item)main.py forwards input.json to the Actor without modifying it. The bundled input reads the main launch feed and stops after ten rows, so a trial run costs very little. Swap feedUrl for another Product Hunt RSS address to narrow the scope, or set includeContent to false when the HTML body is not needed.
{
"feedUrl": "https://www.producthunt.com/feed",
"maxResults": 10,
"includeContent": true,
"proxyConfiguration": {
"useApifyProxy": true
}
}| Field | Type | Required | Default | Allowed values |
|---|---|---|---|---|
feedUrl |
string | no | "https://www.producthunt.com/feed" |
— |
maxResults |
integer | no | 20 |
— |
includeContent |
boolean | no | true |
— |
proxyConfiguration |
object | no | {"useApifyProxy": true} |
— |
Expect one dataset item per launch. name and tagline are null when the title has no separator the Actor recognises, but the full title is always there, so a fallback is one line of pandas.
| Field | Type | Always present |
|---|---|---|
feed_url |
string | yes |
title |
string | yes |
name |
string or null | no |
tagline |
string or null | no |
link |
string | yes |
author |
string or null | no |
content_html |
string or null | no |
categories |
array | no |
published |
string or null | no |
scraped_at |
string | no |
| Event | Price (USD) |
|---|---|
| Actor start | $0.2 |
| Result emitted | $0.002 |
Prices as configured on 2026-08-20. The Actor page always shows the current price. Runs are billed to the Apify account that owns the token. Use --max-charge to cap what a single run can cost.
The recipe below (pip install pandas) turns single runs into a history. It appends each run to launches.jsonl, removes repeats by link, and then lists the topics with the most launches over the past seven days.
import json
from pathlib import Path
import pandas as pd
today = pd.DataFrame(json.load(open("results.json", encoding="utf-8")))
today = today.drop(columns=["content_html"], errors="ignore")
archive_path = Path("launches.jsonl")
frames = [pd.read_json(archive_path, lines=True, dtype=False)] if archive_path.exists() else []
archive = pd.concat([*frames, today], ignore_index=True).drop_duplicates(subset="link")
archive.to_json(archive_path, orient="records", lines=True, force_ascii=False)
archive["published"] = pd.to_datetime(archive["published"], utc=True, errors="coerce")
recent = archive[archive["published"] >= pd.Timestamp.now(tz="UTC") - pd.Timedelta(days=7)]
topic_counts = recent.explode("categories")["categories"].dropna().value_counts()
print(f"{len(archive)} launches archived, {len(recent)} in the last 7 days")
print(topic_counts.head(10).to_string())No. The Actor reads the RSS feed that Product Hunt publishes for everyone, so the only credential this script needs is your Apify API token.
Only as far as the feed does, which is its most recent batch of launches. For a longer history, schedule the script and keep appending results, as the pandas recipe shows. Older launches cannot be recovered from the feed later.
No. The feed does not carry them, so the rows contain none. If ranking by votes matters to you, Product Hunt's official API is the place to get those numbers.
The Actor logs a warning and finishes without rows when the feed request fails or returns a non-200 status, for example on a mistyped feedUrl. Check the run log through the Console link the script prints, and treat an empty dataset as worth a look in your scheduler.
Set APIFY_TOKEN first: export the token in the same shell, or put it in a local.envfile your shell loads. Never commit it.- Run finished with
FAILEDorABORTED: open the printed Console link; the run log says why. Input validation errors name the field. - Fewer rows than expected: a
--max-chargecap also limits how many results can be charged. Raise the cap or lower the limits in your input. - Something looks wrong in the data: open an issue on the Actor page so the maintainers see it.
- Product Hunt Scraper — Daily Launch Data on Apify Store
- JavaScript version of this example
- All Devil Scrapes Actors
- Apify web scraping cookbook
Related Actors:
- Hacker News Scraper — see which launches also get discussed on Hacker News.
- Y Combinator Companies API Scraper — match launching products to YC-backed companies.
- AppSumo SaaS Deals Scraper — watch for new SaaS tools reaching lifetime-deal marketplaces.
MIT licensed example code. The Actor itself runs on Apify and is maintained by Devil Scrapes. Not affiliated with Product Hunt.