# Inkpluck — Article Extractor: URL to Text, Markdown & Metadata (`cybermax/article-extract`) Actor

Article extractor API for news, blogs and docs: URLs or HTML in, clean text + Markdown, title, authors, publish date, language, top image, videos and paywall flag out. newspaper3k-compatible fields. $0.001 per article; failures free.

- **URL**: https://apify.com/cybermax/article-extract.md
- **Developed by:** [CyberMax](https://apify.com/cybermax) (community)
- **Categories:** News, AI, Developer tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 article extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Inkpluck: Article Extractor API, URL to Text, Markdown & Metadata

**Article extractor: URL in, clean article out.** Give Inkpluck news articles, blog posts or docs pages and get back
the **clean article text** (paragraphs kept), **Markdown** (headings, lists, links, images and tables kept), the
**title, authors, publish and update dates, language, site name, section, tags, top image, images and embedded
videos**, plus a **paywall flag**, word count and reading time. No navigation, ads, cookie banners, "related stories"
or footers. A maintained successor to **newspaper3k** (last release 2018), with the same field names available.

**$0.001 per article extracted. Failures are free.** No start fee and no subscription. Pages that fail to load, bot
walls, PDFs, JavaScript-only pages, paywalled teasers and pages with too little text cost nothing.

### Why Inkpluck

- **Metadata from the source, not guesses.** Authors and dates come from the publisher's own schema.org JSON-LD
  (`NewsArticle`, `BlogPosting`, `@graph`), OpenGraph and `article:*` tags, Parsely/Sailthru/Dublin Core/citation
  meta tags and bylines, then the date in the URL (`/2024/05/12/…`). Dates are normalised to ISO 8601 UTC, and
  author lists are cleaned ("By Jane Doe and John Smith" becomes `["Jane Doe", "John Smith"]`; "Staff", "About the
  author" and profile URLs are dropped).
- **Text you can feed straight to an LLM.** Mozilla Readability (the engine behind Firefox Reader View) finds the
  article body; Inkpluck then writes plain text with blank lines between paragraphs, list items as `- …`, table rows
  as `a | b`, and captions left out, plus a separate Markdown version with links and tables. The headline is its own
  field and is not repeated in the text.
- **Knows what isn't an article.** Home pages, section pages (`/news`, `/tech`) and teaser grids get
  `looksLikeArticle: false`. Turn on `onlyArticles` and they come back as free failures, handy when you feed it
  crawled link lists.
- **Paywalls are flagged, not hidden.** `paywalled: true` when the publisher marks the article as subscriber content
  (`isAccessibleForFree: false`) or only a teaser is visible. A teaser with too little text is a free failure.
- **Fast and cheap because it's plain HTTP.** No headless browser: a typical article takes 0.5–3 s, and runs start in
  seconds. Charset detection handles old Windows-1252/ISO-8859 sites; CJK word counts are correct.
- **Blocked? One automatic retry through Apify Proxy** when a site answers 403/429/503 to a direct request, or send
  everything through your own proxy settings (e.g. residential).
- **Watch feeds, get each new article once.** Give it RSS/Atom feeds (`feedUrls`) and turn on `onlyNew`: every run
  extracts only the articles it hasn't extracted before. Put it on an Apify Schedule and you have a daily
  "new articles as Markdown" feed for your agent, newsletter or RAG index.
- **Already have the HTML?** Pass `htmlDocuments` and nothing is fetched.
- **newspaper3k drop-in.** `newspaper3kFields: true` adds `publish_date`, `top_image`, `movies`, `meta_lang`,
  `meta_description`, `meta_keywords`, `canonical_link` and `source_url` next to `title`, `authors`, `text`, `images`.

### Ready-made inputs (first try in one click)

Copy one into the Input tab (JSON view). Each finishes in seconds and costs a few tenths of a cent.

**Two articles to text + Markdown** (the Store's default input):

```json
{ "urls": ["https://techcrunch.com/2026/09/23/meta-introduces-camera-free-ai-glasses/", "https://blog.cloudflare.com/vary-support/"] }
```

**Latest posts from an RSS feed, only new ones on each run** (pair with a daily Schedule):

```json
{ "feedUrls": ["https://blog.cloudflare.com/rss/"], "maxArticlesPerFeed": 5, "onlyNew": true, "monitorName": "cloudflare-blog" }
```

**newspaper3k drop-in** (same field names as `newspaper.Article`):

```json
{ "urls": ["https://blog.cloudflare.com/vary-support/"], "newspaper3kFields": true, "onlyArticles": true }
```

### Example

Input:

```json
{
  "urls": [
    "https://blog.cloudflare.com/vary-support/",
    "https://www.theguardian.com/world/2026/sep/23/harvey-weinstein-sentencing-prison",
    "bbc.com/news"
  ],
  "onlyArticles": true,
  "newspaper3kFields": true
}
```

Output (one record per URL; a real record from a real run, text shortened here):

```json
{
  "url": "https://blog.cloudflare.com/vary-support/",
  "finalUrl": "https://blog.cloudflare.com/vary-support/",
  "ok": true,
  "title": "We just shipped support for the ugliest part of HTTP: Vary",
  "authors": ["Alex Krivit", "Zaidoon Abd Al Hadi"],
  "publishDate": "2026-09-22T14:04:31.803Z",
  "modifiedDate": "2026-09-22T14:04:31.803Z",
  "siteName": "Cloudflare Blog",
  "language": "en",
  "description": "Vary support is now available in Cache Rules on every plan. …",
  "section": null,
  "tags": [],
  "topImage": "https://blog.cloudflare.com/_emdash/api/media/file/01M34KKV86R35ZHS0THH5PGPVZ.01M34KKW3REWTTYD2ZHH5261DV.png",
  "images": [],
  "videos": [],
  "text": "The response header, Vary, has been called “the ugliest part of HTTP that we haven't yet improved.” …",
  "markdown": "The response header, `Vary`, has been called “[the ugliest part of HTTP that we haven't yet improved.](https://mnot.net/blog/2026/linting_the_web)” …",
  "wordCount": 2695,
  "readingTimeMinutes": 11,
  "looksLikeArticle": true,
  "paywalled": false,
  "canonicalUrl": "https://blog.cloudflare.com/vary-support/",
  "publish_date": "2026-09-22T14:04:31.803Z",
  "top_image": "https://blog.cloudflare.com/_emdash/api/media/file/01M34KKV86R35ZHS0THH5PGPVZ.01M34KKW3REWTTYD2ZHH5261DV.png",
  "movies": [],
  "meta_lang": "en",
  "canonical_link": "https://blog.cloudflare.com/vary-support/",
  "source_url": "https://blog.cloudflare.com",
  "httpStatus": 200,
  "fetchedVia": "direct",
  "extractedAt": "2026-09-23T23:45:10.863Z"
}
```

A home page with `onlyArticles` on (not charged):

```json
{ "url": "https://www.bbc.com/news", "ok": false, "looksLikeArticle": false, "wordCount": 1281,
  "error": "not an article (home, section or listing page) and onlyArticles is on" }
```

In the same test batch, articles from the BBC, The Guardian, The Verge, Smashing Magazine, fly.io and Japanese
Wikipedia all came back with their bylines and dates (full outputs in `examples/`).

### Input

| Field | Default | What it does |
|---|---|---|
| `urls` | | Article URLs, one per line (`https://` optional; duplicates skipped) |
| `feedUrls` | | RSS/Atom feeds; their newest items are extracted |
| `maxArticlesPerFeed` | `20` | Newest items taken from each feed |
| `onlyNew` | `false` | Monitor mode: skip URLs extracted by earlier runs (free, no record) |
| `monitorName` | `default` | Separate memory per watch list (kept in a named key-value store in your account) |
| `htmlDocuments` | | `[{"url", "html"}]`: extract HTML you already have, without fetching |
| `onlyArticles` | `false` | Home/section/listing pages become free failures |
| `outputMarkdown` | `true` | Add the `markdown` field |
| `includeHtml` | `false` | Add the cleaned article body as `html` |
| `newspaper3kFields` | `false` | Add newspaper3k's attribute names |
| `minWords` | `50` | Fewer words of article text = free failure |
| `maxConcurrency` | `10` | Pages processed in parallel |
| `timeoutSecs` | `20` | Per-page timeout (one automatic retry on network errors/5xx) |
| `retryBlockedWithProxy` | `true` | Retry 403/429/503 pages once through Apify Proxy |
| `proxyConfiguration` | none | Send every request through this proxy |
| `startUrls` | | Same as `urls`, in the `[{"url": …}]` format |

### Output

One dataset record per input, in the order they finish. `ok: true` records are charged and have the article fields
above; `ok: false` records are free and carry an `error` (`HTTP 403 (the site blocked the request)`,
`not an HTML page (application/pdf)`, `paywalled: only 38 words are visible…`, `no article text in the page HTML
(the page is built by JavaScript in the browser)`, `not a valid http(s) URL`…). Views: **Articles** (overview table)
and **Text**.

### Pricing

**$0.001 per article extracted** (event `article-extracted`), no start fee. 1,000 articles cost at most $1.
Everything with `ok: false` is free.

For comparison (Apify Store, 23 Sep 2026): the most-used article Actor is free but crawls whole sites with a browser
(you pay the compute, and ~5% of its public runs in the last 30 days failed or timed out); per-article Actors charge
from about $0.003 per article plus a start fee of up to $0.09 per run.

### Benchmark

Same 6 articles (The Guardian, BBC, The Verge, TechCrunch, Smashing Magazine, Cloudflare blog), each Actor with its
default settings, run on Apify on 23 Sep 2026. Rivals are the most-used article extractors on the Apify Store.

| | Articles returned | With author | With date | With top image | With language | Run time | Price for the 6 |
|---|---|---|---|---|---|---|---|
| **Inkpluck** | **6/6** | **6** | **6** | **6** | **6** | 6.5 s | **$0.006** |
| Rival A | 6/6 | 4 | 6 | 6 | 0 | 16.9 s | $0.19 (two start fees + per article) |
| Rival B | 4/6 (2 missing, 3 unrequested pages; run ended FAILED) | 4 | 4 | 4 | 0 | 10.6 s | $0.017 |
| Rival C | 6/6 (one article cut to 183 of ~1,240 words) | 5 | 6 | 3 | 6 | 4.2 s | $0.03 |

The most-used (free) rival could not be tested: it only runs after you grant it full access to your Apify account.
Inkpluck needs no special permissions.

### Run it on a schedule (monitor mode)

Watch feeds and get every new article exactly once, as Markdown, every morning:

1. Create a task from this Actor with this input (free to save):
   ```json
   { "feedUrls": ["https://techcrunch.com/feed/", "https://blog.cloudflare.com/rss/"], "onlyNew": true,
     "monitorName": "tech-news", "onlyArticles": true }
   ```
2. In Apify Console → **Schedules** → **Create**, pick the task and a cron such as `0 7 * * *` (07:00 daily).
3. Connect the output: a webhook, the Make/Zapier/n8n integration below, or read the task's last dataset with
   `GET https://api.apify.com/v2/actor-tasks/<task-id>/runs/last/dataset/items?token=<TOKEN>`.

Only new articles are charged, so a schedule on 20 feeds that publish 50 new posts a day costs about $0.05 a day.

### Integrations

- **Make:** add the *Apify → Run an Actor* module (Actor `cybermax/article-extract`, "Run synchronously" on), then
  *Apify → Get dataset items* and map `title`, `authors`, `publishDate`, `markdown` to Notion, Airtable or Slack.
- **Zapier:** *Apify → Actor Run Finished* trigger for your scheduled task, then *Apify → Find Last Dataset Items*;
  send each article to Google Docs, Slack or an email digest.
- **n8n:** the Apify node (*Run Actor and get dataset*) with `{"urls": {{ $json.links }}}`, then an AI Agent node to
  summarise each `markdown`.
- **Google Sheets:** `=IMPORTDATA("https://api.apify.com/v2/actor-tasks/<task-id>/runs/last/dataset/items?format=csv&fields=title,authors,publishDate,wordCount,url&token=<TOKEN>")`
  gives a live sheet of the latest articles. Use a separate, limited-permission Apify token here: anyone who can open
  the sheet can see it.
- **JavaScript:**
  ```js
  import { ApifyClient } from 'apify-client';
  const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
  const run = await client.actor('cybermax/article-extract').call({ urls: ['https://blog.cloudflare.com/vary-support/'] });
  const { items } = await client.dataset(run.defaultDatasetId).listItems();
  console.log(items[0].title, items[0].authors, items[0].markdown.slice(0, 200));
  ```
- **Python (newspaper3k migration):**
  ```python
  from apify_client import ApifyClient
  client = ApifyClient("<APIFY_TOKEN>")
  run = client.actor("cybermax/article-extract").call(run_input={"urls": urls, "newspaper3kFields": True})
  for a in client.dataset(run["defaultDatasetId"]).iterate_items():
      if a["ok"]:
          print(a["title"], a["authors"], a["publish_date"], a["text"][:200])
  ```
- **AI agents (MCP):** see below.

### Use it from an AI agent (MCP)

- **Apify MCP server:** connect your MCP client (Claude, Cursor, VS Code, LangChain, n8n…) to
  `https://mcp.apify.com/?tools=cybermax/article-extract` with your Apify token. The agent gets one tool that takes
  `{"urls": [...]}` and returns the records above.
- **API (synchronous):** `POST https://api.apify.com/v2/acts/cybermax~article-extract/run-sync-get-dataset-items?token=<TOKEN>`
  with the input JSON returns the records directly.
- **Typical agent prompts:** "Read these 20 links and summarise each", "Who wrote this article and when?",
  "Turn this blog post into Markdown for my notes", "Build a RAG corpus from these URLs".

### When NOT to use it

- **Crawling a whole site to discover articles.** Inkpluck extracts the URLs and feed items you give it; it doesn't
  follow links. Sites without a feed need their URLs from a sitemap or a search first.
- **Pages rendered only by JavaScript** (some single-page apps). Inkpluck reads the HTML the server sends, without a
  browser; such pages return a free failure that says so.
- **Hard bot walls and logins.** Sites like AP News, Reuters and the New York Times block data-centre IPs, including
  Apify's default proxy; use a residential proxy in `proxyConfiguration`, or expect a free failure. Inkpluck never
  logs in and never gets around paywalls.
- **PDFs, images, video transcripts.** HTML only.
- **Republishing.** Articles are the publishers' copyright. Use the output for reading, analysis, search, summaries
  and research, and link to the source.

### FAQ

**Is this a newspaper3k replacement?** For `Article(url).download(); .parse()`, yes: the same fields (`title`,
`authors`, `publish_date`, `text`, `top_image`, `images`, `movies`, `meta_*`, `canonical_link`), maintained,
with Markdown and paywall detection on top. It does not do newspaper3k's `nlp()` keywords/summary; pass the text
to your LLM instead.

**Am I charged for failures?** No. Only records with `"ok": true` are charged.

**How accurate are dates and authors?** They come from the publisher's own structured data first, so they are what
the site itself declares. Pages without any date markup get the date in their URL, or `null`; Inkpluck never
invents a date.

**Does it respect sites?** It fetches each URL once, like a browser's first visit, with no crawling, no login and
no personal data beyond the public byline.

### Also from CyberMax

- [Printwren: HTML to PDF & URL to PDF API](https://huggingface.co/spaces/CyberMax-tools/docs-pages-as-pdfs?soon=html-to-pdf): save the articles as PDFs
- [SnapScout: Website Screenshot API for AI Agents](https://apify.com/cybermax/website-screenshot)
- [Keyfern: Keyword Ideas & Autocomplete](https://apify.com/cybermax/keyword-ideas)
- [Swellmeter: Google Trends Scraper & API](https://apify.com/cybermax/google-trends)
- [DomainDNA: Tech Stack Detector, WHOIS & Domain Age API](https://apify.com/cybermax/domain-intel)
- [Boardroom Buys: SEC Form 4 Insider Trading Tracker](https://apify.com/cybermax/sec-insider-tracker)

All tools: [apify.com/cybermax](https://apify.com/cybermax).

© 2026 CyberMax. All rights reserved.

# Actor input Schema

## `urls` (type: `array`):

News articles, blog posts or docs pages to extract. One URL per line; the scheme is optional ("example.com/post" works). Duplicates are skipped.

## `feedUrls` (type: `array`):

Feed URLs (e.g. https://techcrunch.com/feed/). Inkpluck reads each feed and extracts its newest articles. Combine with "Only new articles" and an Apify Schedule to get each new article once, as Markdown, every day.

## `maxArticlesPerFeed` (type: `integer`):

Newest items taken from each feed.

## `onlyNew` (type: `boolean`):

Skip URLs this monitor already extracted in earlier runs (skips are free and produce no record). Memory is kept in a named key-value store per monitor name in your account.

## `monitorName` (type: `string`):

Separate memories for separate watch lists, e.g. "ai-news" or "competitor-blogs". Letters, digits and dashes.

## `htmlDocuments` (type: `array`):

Already have the HTML? Pass \[{"url": "https://site.com/post", "html": "<html>..."}] and Inkpluck extracts it without fetching. The URL is used to resolve relative links and dates.

## `onlyArticles` (type: `boolean`):

Home pages, section pages (/news, /tech) and link lists are returned as free failures instead of extracted pages. Turn on when feeding crawled link lists; leave off to extract any page's main text.

## `outputMarkdown` (type: `boolean`):

Add a `markdown` field (headings, lists, links, images and tables kept) next to the plain `text`. Ideal for LLM/RAG pipelines.

## `includeHtml` (type: `boolean`):

Add an `html` field with the article body only (no navigation, ads or footer).

## `newspaper3kFields` (type: `boolean`):

Also output newspaper3k's attribute names (publish\_date, top\_image, movies, meta\_lang, meta\_description, meta\_keywords, canonical\_link, source\_url) so existing newspaper3k code keeps working.

## `minWords` (type: `integer`):

Pages with fewer words of article text are reported as failures (free).

## `maxConcurrency` (type: `integer`):

How many pages are fetched and extracted at the same time.

## `timeoutSecs` (type: `integer`):

Give up on a page after this many seconds (one automatic retry on network errors and 5xx).

## `retryBlockedWithProxy` (type: `boolean`):

When a site answers 403/429/503 to a direct request, try once more through Apify Proxy (uses your account's proxy).

## `proxyConfiguration` (type: `object`):

Send every request through this proxy (e.g. residential for sites that block datacenters). Leave empty for direct requests with the automatic retry above.

## `startUrls` (type: `array`):

Same as Article URLs, in the \[{"url": ...}] format other Actors use.

## Actor input object example

```json
{
  "urls": [
    "https://techcrunch.com/2026/09/23/meta-introduces-camera-free-ai-glasses/",
    "https://blog.cloudflare.com/vary-support/"
  ],
  "maxArticlesPerFeed": 20,
  "onlyNew": false,
  "monitorName": "default",
  "onlyArticles": false,
  "outputMarkdown": true,
  "includeHtml": false,
  "newspaper3kFields": false,
  "minWords": 50,
  "maxConcurrency": 10,
  "timeoutSecs": 20,
  "retryBlockedWithProxy": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `full` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://techcrunch.com/2026/09/23/meta-introduces-camera-free-ai-glasses/",
        "https://blog.cloudflare.com/vary-support/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cybermax/article-extract").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": [
        "https://techcrunch.com/2026/09/23/meta-introduces-camera-free-ai-glasses/",
        "https://blog.cloudflare.com/vary-support/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("cybermax/article-extract").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://techcrunch.com/2026/09/23/meta-introduces-camera-free-ai-glasses/",
    "https://blog.cloudflare.com/vary-support/"
  ]
}' |
apify call cybermax/article-extract --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cybermax/article-extract"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/hHZi17gdWlNWt9nX6/builds/QSKk2XgFMc6ZWEYQ2/openapi.json
