# Proxy Page to Markdown scraper (`morph_coder/proxy-page-to-markdown`) Actor

Fetches pages through Apify proxy in your chosen country (residential or datacenter). Returns clean markdown per URL; optional unique outbound domains or CTA for brand checks. Cheerio first, Playwright fallback. Social URLs → blocked_social.

- **URL**: https://apify.com/morph_coder/proxy-page-to-markdown.md
- **Developed by:** [Morph Coder](https://apify.com/morph_coder) (community)
- **Categories:** SEO tools, Other
- **Features:** Limited permissions
- **Stats:** 9 total users, 4 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $5.00 / 1,000 markdown results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Proxy Page to Markdown scraper

Fetch web pages through **Apify Proxy in a country you choose** (residential or datacenter) and get **clean content** per URL in markdown, HTML, or plain text. Optional **CTA link** extraction for brand or affiliate leak checks. Uses fast HTTP (Cheerio) first, then **Playwright** when the page needs rendering.

**Actor ID:** `morph_coder~proxy-page-to-markdown`

> This Actor is **not affiliated** with any third-party website you scrape. You are responsible for complying with each site's terms of use and applicable laws.

### Features

- Geo-targeted requests via Apify Proxy (`country` + `RESIDENTIAL` / `DATACENTER`) — useful for geo-specific landing and affiliate redirect checks
- One dataset row per input URL (including failures and blocked social URLs)
- Readability-based main content in **markdown**, **html** (article fragment), or **text**
- **SEO metadata** object: title, meta description, canonical, Open Graph, Readability fields
- **CTA links**: scored outbound call-to-action URLs (excludes internal, social, and technical domains)
- Optional external links: one sample URL per external **domain** (noisier — use for broad scans)
- Social profile/post URLs → `blocked_social` (use dedicated social Actors instead)

### Input

| Field | Default | Description |
|-------|---------|-------------|
| `country` | `US` | Two-letter ISO proxy country (required) |
| `urls` | `https://example.com` | Page URLs to scrape (max `maxUrls` per run) |
| `contentFormat` | `markdown` | `markdown`, `html`, or `text` |
| `extractSeo` | `true` | Return `seo` object with meta/OG fields |
| `extractCtaLinks` | `false` | Scored outbound CTA links for brand/leak checks |
| `maxCtaLinks` | `20` | Max CTA links per page |
| `ctaScope` | `article` | `article` (Readability) or `full_page` |
| `ctaDomainAllowlist` | `[]` | Only return CTA links to these domains (optional) |
| `ctaDomainBlocklist` | `[]` | Extra domains to exclude from CTA |
| `batchSize` | `50` | Internal batch size (10–100) |
| `maxUrls` | `1000` | Hard cap on URLs per run |
| `extractExternalLinks` | `false` | Unique external domains (noisy on blogs) |
| `maxExternalLinks` | `100` | Max domains per page when links enabled |
| `usePlaywrightFallback` | `true` | Retry thin Cheerio results with Chrome |
| `minContentLength` | `200` | Min active-format content length before Playwright |
| `proxyType` | `RESIDENTIAL` | `RESIDENTIAL` or `DATACENTER` |

#### Brand / leak check preset

For landing pages and affiliate monitoring in a target country:

```json
{
  "country": "UA",
  "urls": ["https://example-landing.com/promo"],
  "contentFormat": "markdown",
  "extractSeo": true,
  "extractCtaLinks": true,
  "extractExternalLinks": false,
  "ctaScope": "article",
  "ctaDomainAllowlist": ["partner-brand.com", "affiliate-network.com"],
  "proxyType": "RESIDENTIAL"
}
```

See [`scripts/brand-check-UA.json`](scripts/brand-check-UA.json).

### Output

Each input URL produces **one dataset item**.

| `status` | Meaning |
|----------|---------|
| `success_static` | Content via HTTP/Cheerio |
| `success_rendered` | Content via Playwright |
| `blocked_social` | Social URL — not scraped |
| `failed_fetch` | HTTP or network error |
| `failed_dynamic` | Empty content after Playwright |
| `failed_timeout` | Request timeout |

#### Example (success with CTA + SEO)

```json
{
  "url": "https://landing.example.com/promo",
  "country": "UA",
  "status": "success_static",
  "contentFormat": "markdown",
  "title": "Promo Landing",
  "markdown": "# Welcome\n\n...",
  "html": null,
  "text": null,
  "seo": {
    "title": "Promo Landing",
    "metaDescription": "Best offer in UA",
    "h1": "Welcome",
    "canonical": "https://landing.example.com/promo",
    "ogTitle": "Promo Landing",
    "ogDescription": "Best offer in UA",
    "ogImage": null,
    "robots": "index, follow",
    "excerpt": null,
    "siteName": null,
    "byline": null
  },
  "externalLinks": null,
  "ctaLinks": [
    {
      "url": "https://partner-casino.com/register?ref=abc",
      "text": "Get bonus",
      "domain": "partner-casino.com",
      "score": 92,
      "reasons": ["class:cta_pattern", "rel:sponsored"]
    }
  ],
  "method": "cheerio",
  "httpStatus": 200,
  "errorMessage": null,
  "fetchedAt": "2026-05-23T12:00:00.000Z",
  "billable": true
}
```

#### CTA filtering

CTA extraction **always excludes**:

- Internal links that stay on the same registrable domain after redirects
- Social networks (Facebook, Instagram, Telegram, etc.)
- Technical domains (analytics, CDN, cookie consent, app stores)
- Share buttons and static asset URLs

Same-origin CTA buttons (e.g. `/go`, `PLAY NOW`) are followed through HTTP redirects. If they land on another brand domain, they appear in `ctaLinks` with `reasons` including `redirect:external`.

#### Example (`blocked_social`)

```json
{
  "url": "https://www.instagram.com/someprofile/",
  "country": "US",
  "status": "blocked_social",
  "platform": "instagram",
  "blockedReason": "social_network_not_supported",
  "message": "Social network URLs are blocked. Use a dedicated social scraper Actor.",
  "contentFormat": "markdown",
  "markdown": null,
  "html": null,
  "text": null,
  "seo": null,
  "ctaLinks": null,
  "billable": false
}
```

### Pricing

When the Actor uses **pay-per-event** pricing on Apify Store:

- Successful web scrapes (`success_static`, `success_rendered`) charge the **`scraped-url`** event (once per URL).
- `blocked_social` and failed statuses are **not** charged for that event.

You also pay standard **Apify platform usage** (compute, proxy traffic) according to your plan. Residential proxy traffic is typically higher than datacenter.

Check the **Pricing** tab on this Actor's Store page for current event prices.

### Run from API

```http
POST https://api.apify.com/v2/acts/morph_coder~proxy-page-to-markdown/runs?token=YOUR_TOKEN
Content-Type: application/json

{
  "country": "US",
  "urls": ["https://example.com"],
  "extractCtaLinks": true,
  "extractSeo": true,
  "contentFormat": "markdown"
}
```

Read results from the run's `defaultDatasetId` (one item per URL). Use webhooks on `ACTOR.RUN.SUCCEEDED` for automation.

### Limits and tips

- Up to **1000 URLs** per run; the Actor batches internally (`batchSize`, default 50).
- For brand/leak checks use **`extractCtaLinks: true`** and **`extractExternalLinks: false`** on landing pages.
- Use `extractExternalLinks: true` only when you need a broad domain inventory (blogs/news are noisy).
- Set `ctaDomainAllowlist` to your approved partner/brand domains for precise leak detection.
- Some sites block proxies or return 502 — item will be `failed_fetch`.
- For Instagram, Facebook, LinkedIn, TikTok, X, YouTube, Reddit URLs expect `blocked_social`, not markdown.

### Support

Open an issue from the Actor page or contact the publisher. For development and deployment notes, see [DEVELOPMENT.md](DEVELOPMENT.md) in the repository.

# Actor input Schema

## `country` (type: `string`):

Two-letter country code for Apify proxy geolocation (e.g. US, DE, UA). Use for geo-specific landing and affiliate redirect checks.

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

List of direct page URLs. Social network URLs are returned as blocked_social without scraping.

## `contentFormat` (type: `string`):

Primary content field in output: markdown (default), html (Readability article fragment), or plain text.

## `extractSeo` (type: `boolean`):

Return seo object with title, meta description, canonical, Open Graph fields, and Readability metadata.

## `extractCtaLinks` (type: `boolean`):

Detect outbound call-to-action links for brand/leak checks. Excludes internal, social, and technical domains.

## `maxCtaLinks` (type: `integer`):

Cap on scored CTA links when extractCtaLinks is true.

## `ctaScope` (type: `string`):

article = Readability main content only; full_page = entire page (excludes nav/footer/comments).

## `ctaDomainAllowlist` (type: `array`):

When set, only outbound links to these registrable domains are returned as CTA.

## `ctaDomainBlocklist` (type: `array`):

Additional domains to exclude from CTA results.

## `batchSize` (type: `integer`):

Number of URLs processed per wave (memory control).

## `maxUrls` (type: `integer`):

Hard cap on URLs accepted per run.

## `extractExternalLinks` (type: `boolean`):

One sample URL per external domain, filtered for brand/leak checks — noisy on blogs/news. Prefer extractCtaLinks for landing pages.

## `maxExternalLinks` (type: `integer`):

Cap on unique outbound domains when extractExternalLinks is true (one URL per domain).

## `usePlaywrightFallback` (type: `boolean`):

Retry thin/empty Cheerio results with headless Chrome.

## `minContentLength` (type: `integer`):

Active content format shorter than this triggers Playwright fallback (if enabled).

## `proxyType` (type: `string`):

Apify proxy group: RESIDENTIAL for geo-accurate checks, DATACENTER for lower cost.

## `blockHeavyResources` (type: `boolean`):

When Playwright fallback runs, block images and web fonts via Firefox prefs to cut residential proxy bandwidth. Scripts, XHR, CSS, and documents still load (needed for antibot). Does not use page.route (which can trip WAFs on Camoufox). Turn off if a site fails only with this enabled.

## Actor input object example

```json
{
  "country": "US",
  "urls": [
    "https://example.com"
  ],
  "contentFormat": "markdown",
  "extractSeo": true,
  "extractCtaLinks": false,
  "maxCtaLinks": 20,
  "ctaScope": "article",
  "batchSize": 50,
  "maxUrls": 1000,
  "extractExternalLinks": false,
  "maxExternalLinks": 100,
  "usePlaywrightFallback": true,
  "minContentLength": 200,
  "proxyType": "RESIDENTIAL",
  "blockHeavyResources": true
}
```

# Actor output Schema

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

Dataset with content, seo, ctaLinks, and status per URL.

# 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 = {
    "country": "US",
    "urls": [
        "https://example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("morph_coder/proxy-page-to-markdown").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 = {
    "country": "US",
    "urls": ["https://example.com"],
}

# Run the Actor and wait for it to finish
run = client.actor("morph_coder/proxy-page-to-markdown").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 '{
  "country": "US",
  "urls": [
    "https://example.com"
  ]
}' |
apify call morph_coder/proxy-page-to-markdown --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,morph_coder/proxy-page-to-markdown"
        }
    }
}
```

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/gtFgpbqWUpBWIVpfc/builds/8nV3yoI2JCxpUSHsX/openapi.json
