On the first weekday of every month, the whoishiring account opens an "Ask HN: Who is hiring?" thread, and a few hundred companies post one comment each. It's one of the best job boards for developers, but it's a single long page of free text. If you only care about remote roles using Rust or Python that pay at least $150k, you have to read all of it.
This post covers:
- A standard-library Python script that fetches the latest thread from the Algolia HN API, filters remote posts by keyword and salary, and writes a CSV.
- The pitfalls: free-text posts, funding rounds that look like salaries, "rust" matching "trust", and hourly versus yearly pay.
No account, API key or third-party package is needed.
1. A minimal version (Python standard library)
The Algolia HN Search API is free and needs no key. Two requests are enough: search_by_date with tags=story,author_whoishiring finds the newest thread, and /items/<id> returns the whole comment tree in one response. Job posts are the top-level comments. Their text is HTML (<p>, /), so the script strips tags and unescapes entities first.
"""Latest Hacker News "Who is hiring?" thread -> remote jobs with a salary -> hn_jobs_stdlib.csv. Usage: python hn_hiring_stdlib.py python rust """ import csv import html import json import re import sys import urllib.request API = "https://hn.algolia.com/api/v1" keywords = [k.lower() for k in sys.argv[1:]] def get(url): req = urllib.request.Request(url, headers={"User-Agent": "hn-hiring-demo/1.0"}) return json.load(urllib.request.urlopen(req, timeout=30)) # 1. newest "Ask HN: Who is hiring?" story posted by the whoishiring bot account stories = get(f"{API}/search_by_date?tags=story,author_whoishiring&hitsPerPage=10")["hits"] thread = next(s for s in stories if s["title"].startswith("Ask HN: Who is hiring?")) print(thread["title"]) # 2. the whole comment tree in one request; job posts are the top-level comments posts = [c for c in get(f"{API}/items/{thread['objectID']}")["children"] if c.get("text")] SALARY = re.compile(r"[$€£]\s?\d[\d,.]*\s?[kK]?(?:\s?(?:-|–|to)\s?[$€£]?\s?\d[\d,.]*\s?[kK]?)?") rows = [] for p in posts: text = html.unescape(re.sub(r"<p>", "\n", p["text"])) text = re.sub(r"<[^>]+>", "", text) first = text.split("\n", 1)[0] if not re.search(r"\bremote\b", text, re.I): continue if keywords and not any(re.search(rf"(?<!\w){re.escape(k)}(?!\w)", text, re.I) for k in keywords): continue # whole words: "rust" must not match "trust" salaries = [m.group(0) for m in SALARY.finditer(text) if not re.match(r"\s?(?:m\b|mm\b|b\b|million|billion|in funding|series|raised|signing|bonus)", text[m.end():m.end() + 15], re.I)] # funding rounds are not salaries if not salaries: continue company = first.split("|")[0].strip() if "|" in first else "?" rows.append([company[:60], salaries[0].strip(), first[:200], f"https://news.ycombinator.com/item?id={p['id']}"]) with open("hn_jobs_stdlib.csv", "w", newline="", encoding="utf-8") as f: csv.writer(f).writerows([["company", "salary", "headline", "hnUrl"], *rows]) print(f"{len(posts)} job posts, {len(rows)} remote with a salary -> hn_jobs_stdlib.csv") for r in rows[:5]: print(f" {r[0][:28]:<28} {r[1]}") Output (03.11.2026):
$ python hn_hiring_stdlib.py Ask HN: Who is hiring? (November 2026) 157 job posts, 32 remote with a salary -> hn_jobs_stdlib.csv Simple Analytics €5,000 Bridge AI Consulting $60K–$100K NEO Holdings (stealth, regul $200K to $300K ORFF Maestra $70–95 FutureSearch $130k–$250k $ python hn_hiring_stdlib.py python rust Ask HN: Who is hiring? (November 2026) 157 job posts, 10 remote with a salary -> hn_jobs_stdlib.csv River $200K-$250K Paramark $199K–$249K Postscript (YC W19) $180K to $220K SadServers $55 - $75 Evaboot $60-120K That runs in under a second. Look at the salaries, though: "€5,000" is a monthly salary and "$70–95" is an hourly rate. A plain "salary ≥ 150k" filter on these strings won't work.
2. Pitfalls
- It's free text. Most posts follow the
Company | Role | Location | Remote | Salary | URLconvention, but many don't ("Acme is hiring engineers in Berlin…"). Only take the company from the first segment when the headline uses pipes, and accept that some fields stay empty. - Funding amounts look like salaries. "We raised $20M" and "$10k signing bonus" both match a naive currency regex. Check the words after the number (
M,million,series,raised,bonus) and drop those matches. - Pay comes in different units. "$55 - $75" is hourly, "€5,000" is monthly, "$150-200k" is a range with a single
k. Before you compare anything, parse each salary into min/max, guess the period (very small numbers are hourly, four-digit amounts are usually monthly), and annualise it (×2080 for hourly, ×12 for monthly). Currencies are a separate problem. Converting them with today's exchange rate is fine for filtering, but keep the original string. - Keywords need word boundaries. A substring search for "rust" matches "trust" and "go" matches "good". Match whole words (
(?<!\w)rust(?!\w)), and expect short names like "Go" or "R" to stay noisy anyway. - "Remote" can mean "not remote". "No remote", "remote: no", "remote (US only)" and "hybrid, remote possible" all contain the word. Read the headline first, look for negations, and keep the location next to the remote flag (e.g.
Remote (US only)). - Stay on the right thread. The same account also posts "Who wants to be hired?" (job seekers' personal details) and "Freelancer? Seeking freelancer?" threads. The script picks the "Who is hiring?" title explicitly. Don't collect the job seekers' thread into a database.
- API etiquette. The Algolia API is generous but not unlimited. One thread is one request. Cache it, because posts barely change after the first two days.
3. Running it every month
- Run the script on the 2nd or 3rd of the month (cron, a scheduled GitHub Action, or by hand); the thread is mostly complete by then.
- To cover several months, fetch the last few
whoishiringstories instead of only the newest one and deduplicate by company, because many companies repost every month. - Keep the raw comment text in the CSV next to the parsed fields, so you can fix parsing mistakes later without fetching again.
Wrap-up
- One thread, quick filter: two Algolia requests and the standard library. Use whole-word matching, drop funding amounts, and annualise salaries before comparing them.
- Every month: schedule the same script and deduplicate by company.
Questions and salary formats the script gets wrong are welcome in the comments.


