# Website Metadata Scraper: Meta Tags & Open Graph Extractor (`cluuny/web-metadata-extractor`) Actor

Extract title, meta description, Open Graph tags, language, canonical URL and link and image counts from a list of web pages. One clean row per URL.

- **URL**: https://apify.com/cluuny/web-metadata-extractor.md
- **Developed by:** [Vícente Matallana](https://apify.com/cluuny) (community)
- **Categories:** SEO tools, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 page analyzeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Web Page Metadata Extractor

Get the title, meta description, Open Graph tags, language, canonical URL and link and image counts of a list of web pages: one clean row per URL. Use it for SEO audits, link preview checks, content inventories or as a fast tool for AI agents that need to know what a page is about without reading all of it.

### What it does

- Takes a list of page URLs.
- Loads each page with a plain HTTP request (no browser, no login, no cookies) and follows redirects.
- Returns one row per URL, in the same order as the input, with the input `url` and:
  - `title`, `description` and `language` declared by the page;
  - `canonicalUrl`, always as an absolute URL;
  - `openGraph` tags used for link previews in social networks and chat apps (`title`, `description`, `image`, `type`, `siteName`);
  - `internalLinks` and `externalLinks`: unique links to the same site (ignoring `www.`) and to other sites;
  - `images`: number of `<img>` tags;
  - `finalUrl` after redirects and the HTTP `statusCode`.
- A page that fails (HTTP error, timeout, not an HTML page) gets a row with an `error` message and the run continues with the other URLs.
- It does not collect personal data: no authors, emails or phone numbers.

Pages that build their content with JavaScript after loading may return fewer tags than a browser shows. Most sites put these tags in the initial HTML, because search engines and link previews need them there.

### Input example

```json
{
    "urls": [
        "https://www.python.org/",
        "https://docs.python.org/3/",
        "https://example.com/"
    ],
    "maxConcurrency": 5
}
```

| Field | Required | Description |
|---|---|---|
| `urls` | Yes | Absolute http(s) URLs of the pages to analyze |
| `maxConcurrency` | No | Pages loaded in parallel, 1–20. Default 5 |

### Output example

```json
{
    "url": "https://docs.python.org/3/",
    "finalUrl": "https://docs.python.org/3/",
    "statusCode": 200,
    "title": "3.14.8 Documentation",
    "description": "The official Python documentation.",
    "language": "en",
    "canonicalUrl": "https://docs.python.org/3/index.html",
    "openGraph": {
        "title": "Python 3.14 documentation",
        "description": "The official Python documentation.",
        "image": "https://docs.python.org/3/_static/og-image.png",
        "type": "website",
        "siteName": "Python documentation"
    },
    "internalLinks": 45,
    "externalLinks": 13,
    "images": 3,
    "error": null
}
```

A failed URL looks like this:

```json
{
    "url": "https://www.python.org/missing",
    "finalUrl": "https://www.python.org/missing",
    "statusCode": 404,
    "title": null,
    "description": null,
    "language": null,
    "canonicalUrl": null,
    "openGraph": null,
    "internalLinks": null,
    "externalLinks": null,
    "images": null,
    "error": "The page answered with HTTP 404"
}
```

Any field the page does not declare is `null`. Export the dataset as JSON, CSV, Excel or HTML, or read the **Overview** and **Open Graph** views in the Console.

### Pricing

This Actor uses pay-per-event pricing. You pay only for pages analyzed successfully, and platform usage is included in the price. See the **Pricing** tab for the current price per event.

| Event | When it is charged |
|---|---|
| `page-analyzed` | Once per URL that returns a row without `error` |

URLs that fail are not charged. If you set a maximum charge for the run, the Actor stops cleanly when it is reached and keeps every row produced so far.

### Free plan limits

Users on the Apify Free plan can analyze up to **100 URLs per run**. The Actor analyzes the first 100, saves them and finishes with a clear message. Paid plans have no per-run limit.

### Use cases

- **SEO audits:** find pages with a missing title, description or canonical URL.
- **Link previews:** check that every page has an Open Graph title, description and image before a campaign.
- **Content inventories:** list the title and language of every URL in a sitemap or a spreadsheet.
- **AI agents:** give an agent a quick, cheap summary of what a list of pages is about before deciding which ones to read in full.
- **Monitoring:** schedule a weekly run to catch broken pages (4xx and 5xx) and changed titles.

### Comparison with alternatives

| Option | Good for | Trade-off |
|---|---|---|
| This Actor | Hundreds of URLs at once, from the Console, the API, a schedule or an AI agent, with a typed dataset | Reads the initial HTML only; no JavaScript rendering |
| Social network link preview debuggers | Checking how one URL looks in one network | One URL at a time, no export |
| Browser-based SEO crawlers | Full site crawls with rendering and dozens of checks | Heavier, slower and priced for full audits |
| Your own script | Full control | You host, run and maintain it, including retries and error handling |

You are responsible for having the right to access the pages you analyze. The Actor stores nothing outside the run's dataset.

# Actor input Schema

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

Absolute http(s) URLs of the web pages to analyze. The Actor returns one dataset row per URL, in the same order. A URL that fails gets a row with an error and does not stop the run.

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

How many pages to load in parallel. Higher is faster; lower is gentler on the target sites.

## Actor input object example

```json
{
  "urls": [
    "https://www.python.org/",
    "https://docs.python.org/3/",
    "https://example.com/"
  ],
  "maxConcurrency": 5
}
```

# Actor output Schema

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

No description

## `openGraph` (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://www.python.org/",
        "https://docs.python.org/3/",
        "https://example.com/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cluuny/web-metadata-extractor").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://www.python.org/",
        "https://docs.python.org/3/",
        "https://example.com/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("cluuny/web-metadata-extractor").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://www.python.org/",
    "https://docs.python.org/3/",
    "https://example.com/"
  ]
}' |
apify call cluuny/web-metadata-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cluuny/web-metadata-extractor"
        }
    }
}
```

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/1GGW76d1zyRNGgGDw/builds/eoCHE9aKuxmL89eXK/openapi.json
