AI & ML
A week of NVIDIA news is 5,718 articles. Our filter kept 161
APITube News API Dev.to (EN Zone)
4 views
We wanted a small script that pings us when something real happens to NVIDIA — an earnings surprise, an export-control change, a leadership move. Not a feed. A handful of lines a day we can actually read.
The first version was fifteen lines and it was wrong in a way we couldn't see from the output. So we pulled a full week of coverage three different ways and counted.
A company news watcher is a filter chain, not a query. Over seven days (2026-08-30 to 2026-09-05) there were 5,718 articles mentioning NVIDIA — about 817 a day. Five filtering stages got that to 161 alerts, or 23 a day. This post is the measurements behind each stage, and the finished script.
This is for developers who want a company news feed they can trust, and analysts who need to know what their alerts are silently dropping.
What the week showed, in four lines:
Searching headlines for the company name finds 29.8% of the coverage that mentions it.
Entity resolution alone isn't a fix — it missed 180 articles that had "nvidia" in the headline.
Only 31.6% of the coverage is in English.
12.5% of what survives filtering is the same story republished under a near-identical headline.
Measured 2026-09-06 against the APITube news API, over articles published 2026-08-30 to 2026-09-05. The pull script (pull.py) and the resulting counts (data.csv) sit alongside this post.
The fifteen-line version
Start with the obvious thing: search headlines for the company name.
curl -s -H "X-API-Key: $APITUBE_API_KEY" \
"https://api.apitube.io/v1/news/everything?title=nvidia&per_page=2&published_at.start=2026-08-30&published_at.end=2026-09-05"
Trimmed to the fields that matter, one article comes back like this:
{
"id": 3078309425,
"title": "Nvidia earnings highlight remarkable growth",
"published_at": "2026-09-02T17:56:16.000Z",
"language": "en",
"source": { "domain": "proactiveadvisormagazine.com", "type": "news" },
"entities": [
{
"id": 1280220,
"name": "Nvidia",
"type": "organization",
"frequency": 3,
"sentiment": { "score": 0.46, "polarity": "positive" },
"metadata": {
"aliases": ["NVIDIA", "nVidia", "NVDA"],
"is_public_entity": true
}
}
],
"sentiment": { "overall": { "score": 0.64, "polarity": "positive" } },
"is_duplicate": false,
"story": { "id": 3078309425 }
}
Two fields decide everything later: entities[].frequency (how many times the company is actually named in the piece) and entities[].id (the canonical company, independent of spelling).
The headline query finds 30% of the coverage
Here is the part that made us throw away version one. We ran the same week twice — once filtering on the headline, once on the canonical entity id.
Strategy
Unique articles, 7 days
Share of entity-matched coverage
title=nvidia
1,831
29.8%
entity.id=1280220
5,538
100%
union of both
5,718
—
The headline query misses 3,890 articles — a supplier's export licence, a hyperscaler's capex call, a competitor's benchmark. Those are the stories that move a position, and they rarely put "NVIDIA" in the headline.
The obvious fix is to drop the headline query and use entity resolution alone. That is also wrong. 180 articles had "nvidia" in the headline and no NVIDIA entity extracted at all — 9.8% of the headline set. Named-entity recognition misses things too, especially on smaller and non-English outlets.
Neither filter is a superset of the other, so the watcher queries both and unions the results. Unlike a single-strategy filter, the union costs one extra request per window, which means you stop choosing between the two failure modes and just pay for both.
Two thirds of it isn't in English
Language of all 5,718 articles mentioning NVIDIA over the seven days, from the language field on each article.
Of those 5,718 articles, 1,806 were in English — 31.6%. German was 9.4%, Spanish 7.4%, Chinese 6.4%, French 5.7%, Italian 4.9%, Japanese 4.2%, Portuguese 4.1%.
If you never set a language filter, you don't get a broader watcher — you get a mostly-unreadable one, and your theme keywords (written in English) silently fail against it. If you do set language=en, be honest that you just dropped two thirds of the world's coverage. We set it, because we can't read the rest. That's a choice, not a default.
The five stages, with the numbers
Each stage applied in order to the same 7-day pull. Counts are articles, not alerts sent.
Each stage below is one rule with one threshold, applied in order.
Stage
Rule
Articles / 7 days
Per day
1
Everything mentioning NVIDIA (union of both queries)
5,718
817
2
language == "en"
1,806
258
3
NVIDIA in the headline and frequency >= 2
679
97
4
Near-duplicates removed
594
85
5
Matched a watch theme
161
23
Stage 3 is the one worth explaining. Only 32.2% of entity-matched articles have NVIDIA in the headline; the other two thirds mention it in passing — a monitor review, a fund's holdings list, an unrelated AI story name-checking the chip. Requiring the headline and at least two mentions in the body is a cheap proxy for "this article is about NVIDIA", and it cut 258/day to 97/day.
Stage 4 removed 12.5% of what survived stage 3. Those 924 source domains republish each other constantly — one acquisition story appeared eight times under near-identical headlines. Normalising the title to a sorted set of its long words catches these without any similarity library.
Stage 5 splits the remainder into themes, which is also the alert budget: earnings 11.4/day, leadership 10.0/day, legal 2.6/day, export controls 2.1/day. Those sum to more than 23 because one article can match two themes — an earnings call that announces a CFO change lands in both.
The watcher
import json, os, re, urllib.parse, urllib.request
API = "https://api.apitube.io/v1/news/everything"
KEY = os.environ["APITUBE_API_KEY"]
NVIDIA = 1280220
THEMES = {
"earnings": r"earnings|revenue|quarter|guidance|\beps\b|results|forecast",
"leadership": r"\bceo\b|jensen huang|\bcfo\b|resign|steps down|appoint",
"export": r"export|sanction|china|\bban\b|restrict|licen[cs]e|blackwell|tariff",
"legal": r"lawsuit|sue[sd]?\b|antitrust|probe|investigat|settle|court",
}
THEME_RE = re.compile("|".join(THEMES.values()), re.I)
def fetch(**params):
params.setdefault("per_page", 200)
url = f"{API}?{urllib.parse.urlencode(params)}"
req = urllib.request.Request(url, headers={
"X-API-Key": KEY,
"User-Agent": "nvidia-watch/1.0", # urllib's default UA gets a 403
})
payload = json.load(urllib.request.urlopen(req, timeout=60))
for w in payload.get("meta", {}).get("warnings", []):
print("WARNING", w["code"], w["message"]) # a typo'd filter is ignored, not rejected
return payload
def mentions(article, entity_id):
for e in article.get("entities", []):
if e["id"] == entity_id:
return e.get("frequency", 0)
return 0
def normalise(title):
words = re.sub(r"[^a-z0-9 ]", " ", (title or "").lower()).split()
return " ".join(sorted(set(w for w in words if len(w) > 3)))
def watch(entity_id, keyword, start, end, language="en"):
seen, out = set(), []
for field in ("entity.id", "title"): # stage 1: union
page = 1
while True:
q = {"published_at.start": start, "published_at.end": end, "page": page,
field: entity_id if field == "entity.id" else keyword}
data = fetch(**q)
rows = data.get("results", [])
for a in rows:
if a.get("language") != language: # stage 2
continue
if not re.search(keyword, a.get("title") or "", re.I): # stage 3a
continue
if mentions(a, entity_id) < 2: # stage 3b
continue
key = normalise(a["title"])
if key in seen: # stage 4
continue
blob = f"{a.get('title','')} {a.get('description','')}"
if not THEME_RE.search(blob): # stage 5
continue
seen.add(key)
out.append(a)
if not rows or not data.get("has_next_pages"):
break
page += 1
return out
if __name__ == "__main__":
hits = watch(NVIDIA, "nvidia", "2026-08-30", "2026-09-05")
print(f"\n{len(hits)} alerts over 7 days ({len(hits)/7:.1f}/day)\n")
for a in sorted(hits, key=lambda x: x["published_at"]):
theme = next(t for t, p in THEMES.items()
if re.search(p, f"{a['title']} {a.get('description','')}", re.I))
print(f"[{theme:10s}] {a['published_at'][:10]} {a['title'][:70]}")
Standard library only. Running it just now returned 153 rather than the 161 in our archived pull — the index keeps ingesting, so a re-run of a past window is close but not byte-identical. Worth knowing before you write a test that asserts an exact count.
Piping it into Telegram is four more lines and a bot token; the filtering is the part that was hard.
Five things that cost us time
There is no ticker filter. ticker=NVDA returns HTTP 200 and quietly ignores you. NVDA lives in entities[].metadata.aliases alongside is_public_entity: true, so resolve ticker → entity id once, store the map, and filter on the id.
organization.name is case-sensitive. organization.name=Nvidia works. organization.name=NVIDIA returns HTTP 400, ER0220, "entity organization name 'NVIDIA' not found." The all-caps spelling most people type is the one that fails.
Unknown parameters are warnings, not errors. entity.name and ticker both come back 200 with meta.warnings[].code == "ER0368" and unfiltered results. A typo doesn't crash your watcher, it floods it. Print meta.warnings — that's the two lines in fetch() above.
Headline search defaults to a 31-day window (ER0366) if you don't pass published_at.start and published_at.end. Your "last 24 hours" alert is quietly a month.
Python's default User-Agent gets a 403. urllib sends Python-urllib/3.x and the edge rejects it. Set any real User-Agent and it works — this cost us twenty minutes of blaming our API key.
Frequently asked questions
How do I get news alerts for a specific company?
The reliable way to get news alerts for a specific company is a five-stage filter chain rather than a single query, because no single filter is both precise and complete. Query the company's canonical entity id, union it with a headline keyword search, then filter by language, require the company in the headline with two or more body mentions, drop near-duplicate titles, and match against theme keywords. In this measurement that chain reduced 817 articles a day to 23.
Why do my company news alerts return irrelevant articles?
Company news alerts return irrelevant articles because a mention is not a subject. Only 32.2% of articles that mention NVIDIA put it in the headline; the rest are passing references in unrelated stories. Requiring a headline match plus frequency >= 2 removes most of them.
Can I filter news by ticker symbol?
No — this API has no ticker parameter, and passing one is silently ignored rather than rejected. The ticker appears inside entities[].metadata.aliases, so build your own ticker-to-entity-id table once and filter on entity.id.
How do you deduplicate news articles from multiple sources?
The cheapest way to deduplicate news articles across sources is normalised-headline matching, because syndicated copies keep the same words while rewording punctuation and framing. Normalise each headline to a sorted set of its words longer than three characters and treat a repeat as a duplicate. Across 924 source domains that removed 12.5% of the surviving articles, with no similarity library.
Is entity resolution enough on its own?
No — entity resolution alone is not enough, because entity extraction has its own recall gap. It missed 180 articles that had the company name in the headline — 9.8% of the headline-matched set. Query both ways and union the results.
Making it yours
To point this at another company:
Find the entity id. Pull any article that mentions the company and read entities[] — the id, the aliases and is_public_entity are all in there. Store the mapping; it doesn't change.
Rewrite THEMES. The stage thresholds transfer between companies, the theme regexes don't — "export controls" matters for a chipmaker and means nothing for a bank.
Re-measure the funnel. Count what each stage drops for your company before you trust the output. A quiet mid-cap won't need stage 5 at all; a bank will need a different stage 3.
Mine only looked reasonable after we counted what it threw away.
Disclosure: we work on APITube, which is the API in the code above — free tier at apitube.io. The measurement approach works against any news API that exposes entity ids and per-article mention counts.
Resources
APITube news API documentation — endpoints, parameters, response schema
Google Alerts — the no-code baseline: no API, no deduplication, no entity resolution
Perigon's NVIDIA monitoring guide — good on entity resolution as a concept, no code or numbers
Read original: https://dev.to/apitube/a-week-of-nvidia-news-is-5718-articles-our-filter-kept-161-1eah
← Previous
IonQ Analysis Shows 20,000 Qubits Can Crack Bitcoin Security
Next →
Accelerate Server Threshold Verification with SelfCmd
Related
OpenAI can't rule out that it stole its most recent breakthrough
AI & ML
1
DEV Community
Does an LSP help a coding agent?
AI & ML
1
DEV Community
My 3B Model Found a Shortcut. It Took Me Three Fixes to Close It.
AI & ML
2
Dev.to (EN Zone)
How we made 2,000 customer conversations queryable in a few hours
AI & ML
2
Dev.to (EN Zone)
Comments0
No comments yet — be the first