#!/usr/bin/env python3 """PR Newswire press releases for a list of companies or a topic, saved to a CSV (one row per release). Runs the Apify Actor nexgensignal/press-release-monitor-api once. export APIFY_TOKEN=your_apify_token (Windows: set APIFY_TOKEN=...) python prnewswire_to_csv.py --companies "Kimberly-Clark" "Gates Corporation" --per-company 3 python prnewswire_to_csv.py --topics mergers_acquisitions executive_appointments --max 10 python prnewswire_to_csv.py --topics venture_capital --keyword AI --since 2026-09-24 A watchlist keeps only releases the company issued itself (not ones that merely mention it). Topics are PR Newswire's own lists (mergers_acquisitions, earnings, executive_appointments, bankruptcy, contracts, venture_capital and 60 more; see the Actor's input page). PR Newswire only, English releases. Cost: $0.05 per release on Apify's Free plan ($0.0335 on Gold); --cap stops the run at that many dollars. Python 3.8+, nothing to install. """ import argparse, csv, json, os, sys, urllib.request ACTOR = "nexgensignal~press-release-monitor-api" COLS = ["date", "published_at", "company", "tickers", "title", "location", "topics", "watched_company", "summary", "url", "record_id"] def main(): p = argparse.ArgumentParser() p.add_argument("--companies", nargs="*", default=[], help="company names (watchlist, up to 50)") p.add_argument("--topics", nargs="*", default=[], help="PR Newswire topic ids") p.add_argument("--keyword", default=None, help="keyword (narrows topics/watchlist to headline or opening lines)") p.add_argument("--since", default=None, help="only releases on or after this date (YYYY-MM-DD)") p.add_argument("--per-company", type=int, default=3) p.add_argument("--max", type=int, default=10, help="most releases returned (each is charged)") p.add_argument("--cap", type=float, default=1.00, help="maximum charge for the run, USD") p.add_argument("--out", default="press_releases.csv") a = p.parse_args() token = os.environ.get("APIFY_TOKEN") or sys.exit("Set APIFY_TOKEN to your own Apify API token first.") if not (a.companies or a.topics or a.keyword): sys.exit("Give --companies, --topics or --keyword.") run_input = {"maxReleases": a.max} if a.companies: run_input.update(companies=a.companies, maxReleasesPerCompany=a.per_company) if a.topics: run_input["topics"] = a.topics if a.keyword: run_input["keyword"] = a.keyword if a.since: run_input["dateFrom"] = a.since url = (f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items" f"?maxTotalChargeUsd={a.cap}&clean=true") req = urllib.request.Request(url, data=json.dumps(run_input).encode(), method="POST", headers={"Content-Type": "application/json", "Authorization": "Bearer " + token}) try: rows = json.load(urllib.request.urlopen(req, timeout=320)) except Exception as e: # noqa: BLE001 sys.exit(f"No answer from Apify ({e}). A run may still have started: check Apify Console > Runs " f"before running this again.") with open(a.out, "w", newline="", encoding="utf-8") as f: w = csv.DictWriter(f, fieldnames=COLS, extrasaction="ignore") w.writeheader() for r in rows: r = dict(r) for k in ("tickers", "topics"): r[k] = "; ".join(r.get(k) or []) w.writerow(r) print(f"{len(rows)} release(s) saved to {a.out}") for r in rows[:10]: print(f" {r.get('date')} {r.get('company')}: {r.get('title')}") if __name__ == "__main__": main()