Article scraper for news sites and blogs: headline, publish and update dates, language, lead image and full body text without menus, ads or comments. Paste article URLs, or a section page and it finds the articles. Markdown option. Fast HTTP, robots.txt respected. $0.50 per 1,000 articles.
Unreleased: invalid input no longer crashes (2026-11-04, local, not built or pushed; based on cloud build 1.0.9)
Fix: input that is not a JSON object (a string, number, null or list) crashed the run with exit 91. It now ends with exit 0 and one free status row: Invalid input, nothing was fetched and no results were charged: the input must be a JSON object, got <type>.
Fix: wrong-type or unusable values that crashed with exit 91 before anything was fetched now end the same way (exit 0, one free status row with charged: false, no RUN_SUMMARY, no charged event) with the reason <field> has the wrong type (<error>). Covered: articleUrls and sectionUrls (a number, boolean or a list with numbers), maxArticlesPerSection, minWords, maxArticles, maxConcurrency.
The check repeats the exact conversions the run makes, before any request; valid input behaves exactly as before. Network, HTTP and upstream errors are unchanged.
The existing free "Not charged: no article was extracted" status row is unchanged.
2026-11-02 (actor.json version unchanged: 1.0): private-address (SSRF) guard
On the Apify platform, every request (article URLs, section pages, article links found on them, each redirect hop,
and robots.txt) is refused when the host resolves to a private, loopback, link-local, carrier-grade NAT, reserved,
multicast or unspecified address (including IPv4-mapped IPv6), or when any of the host's addresses is one. Same
rules as Bulk HTTP Request.
Such an article URL is skipped like other unreadable URLs: no row, not charged, counted in failed, logged as
not fetched: private, loopback and link-local addresses are not allowed. A section page with such an address is
skipped with a warning. When no article is extracted, the free status row's reason now reads "could not be read
(HTTP error, robots.txt, a private address or not an HTML page)".
A host the guard cannot resolve fails as before ("DNS or connection error").
A robots.txt that redirects to a private address counts as unavailable (no robots.txt rules).
Local runs (APIFY_IS_AT_HOME not set) are unchanged.
Files: my_actor/common.py (guard helpers; Http._robots_for checks the site first), my_actor/main.py (hook
on the httpx client, Blocked handled), tests/test_ssrf_guard_1102t.py (new), README Limits (one line).
Unreleased
Fixed tags on sites that declare tags in a parsely-tags meta (for example The Verge). trafilatura 2.x returns tags as a list whose element is the whole comma-separated meta value, and the old code only split plain strings, so the output was one long string with internal tracking tokens (theverge,pagetype:story,...,stream-…). Every tag and category value is now split on commas and semicolons, trimmed and de-duplicated (case-insensitive), and tokens containing :, starting with stream- or equal to the site name are dropped. Categories given alone (without tags) are no longer lost. Regression test: tests/test_tags.py with a Verge-style fixture.
The free status row also carries statusRow: true.
1.0.4
A run that extracts no article (every URL failed, was blocked by robots.txt or was under Minimum words) now writes one free status row (recordType: "status", status: "warning", message starting "Not charged: no article was extracted…") instead of leaving the dataset empty. It is not charged and is not written when at least one article was saved.