# Bulk SEO Audit - Titles, Meta, Headings, Schema, Links (`thirdwatch/seo-data-extractor`) Actor

Run an SEO audit on hundreds of pages at once. For each URL you get the title, meta description, headings, canonical, Open Graph and Twitter cards, schema markup, image alt-text gaps, internal and external link counts, and word count. The fastest way to check on-page SEO across a whole site.

- **URL**: https://apify.com/thirdwatch/seo-data-extractor.md
- **Developed by:** [Thirdwatch](https://apify.com/thirdwatch) (community)
- **Categories:** SEO tools, AI
- **Features:** Limited permissions
- **Stats:** 22 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 results

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

## Bulk SEO Audit - Titles, Meta, Headings, Schema, Links

> **Thirdwatch portfolio:** 8,800+ users across 140+ public Actors, 2M+ records delivered, >95% run success. [Explore all Thirdwatch Actors](https://apify.com/thirdwatch).

> The fastest bulk SEO meta tags and headings extractor — audit titles, meta descriptions, headings, canonicals, social cards, and schema across hundreds of URLs in one pass.

### What you get

Paste a list of URLs and get a complete on-page SEO snapshot for each one: title, meta description, heading hierarchy, canonical, robots directive, Open Graph and Twitter social cards, schema markup, image alt-text gaps, internal/external link counts, and word count. Designed for SEO consultants, content auditors, and in-house SEO teams who need real on-page data across hundreds of pages without firing up a desktop crawler.

### Output fields

| Field | Description |
|-------|-------------|
| `url` | The URL you submitted. |
| `final_url` | The URL after following redirects. |
| `status_code` | HTTP status code (200, 301, 404, etc.). |
| `title` | The `<title>` tag text. |
| `title_length` | Character count of the title (Google typically truncates over ~60). |
| `meta_description` | The meta description tag. |
| `meta_description_length` | Character count (Google typically truncates over ~160). |
| `meta_keywords` | The meta keywords tag (rarely used today). |
| `canonical` | The canonical URL declared by the page. |
| `robots_meta` | The meta robots directive (e.g. `index,follow`, `noindex`). |
| `viewport` | The viewport meta tag (mobile-friendliness signal). |
| `lang` | The `lang` attribute on the `<html>` tag. |
| `charset` | Character set declared by the page. |
| `h1_tags` | List of unique H1 headings on the page. |
| `h2_tags` | List of unique H2 headings on the page. |
| `h1_count` | Total H1 tags (more than one is an SEO smell). |
| `h2_count` | Total H2 tags. |
| `h3_count` | Total H3 tags. |
| `h4_count` | Total H4 tags. |
| `og` | Open Graph tags (`og:title`, `og:description`, `og:image`, etc.) as a flat dict. |
| `twitter` | Twitter card tags (`twitter:card`, `twitter:title`, etc.) as a flat dict. |
| `json_ld` | Parsed schema markup blocks (Product, Article, FAQ, Breadcrumb, etc.). |
| `images_total` | Total `<img>` tags on the page. |
| `images_missing_alt` | Images with no `alt` attribute (accessibility + SEO gap). |
| `internal_links` | Links pointing back to the same domain. |
| `external_links` | Links pointing to other domains. |
| `nofollow_links` | Links marked `rel="nofollow"`. |
| `word_count` | Visible word count (script and style content excluded). |
| `text_to_html_ratio` | Word count divided by HTML byte size — low values flag thin or template-heavy pages. |
| `response_time_ms` | Time to fetch the page in milliseconds. |
| `content_length` | HTML byte size. |
| `error` | Error message on failure (`null` on success). |
| `checked_at` | ISO timestamp when the audit ran. |

### Example output

```json
{
  "url": "https://example.com",
  "final_url": "https://example.com/",
  "status_code": 200,
  "title": "Example Domain",
  "title_length": 14,
  "meta_description": "Illustrative example for documentation.",
  "meta_description_length": 39,
  "meta_keywords": "",
  "canonical": "https://example.com/",
  "robots_meta": "index,follow",
  "viewport": "width=device-width, initial-scale=1",
  "lang": "en",
  "charset": "utf-8",
  "h1_tags": ["Example Domain"],
  "h2_tags": ["More information"],
  "h1_count": 1,
  "h2_count": 1,
  "h3_count": 0,
  "h4_count": 0,
  "og": {
    "og:title": "Example Domain",
    "og:description": "Illustrative example for documentation.",
    "og:image": "https://example.com/og.png",
    "og:type": "website",
    "og:url": "https://example.com/",
    "og:site_name": "Example"
  },
  "twitter": {
    "twitter:card": "summary_large_image",
    "twitter:title": "Example Domain",
    "twitter:description": "Illustrative example for documentation.",
    "twitter:image": "https://example.com/og.png"
  },
  "json_ld": [
    {
      "@context": "https://schema.org",
      "@type": "WebSite",
      "name": "Example",
      "url": "https://example.com/"
    }
  ],
  "images_total": 4,
  "images_missing_alt": 1,
  "internal_links": 17,
  "external_links": 3,
  "nofollow_links": 2,
  "word_count": 412,
  "text_to_html_ratio": 0.0421,
  "response_time_ms": 184,
  "content_length": 9786,
  "error": null,
  "checked_at": "2026-05-04T12:00:00Z"
}
```

When a URL fails, the same shape is returned with `error` set and the numeric fields zeroed out.

### Input parameters

| Parameter | Required | Description |
|-----------|----------|-------------|
| `urls` | Yes | List of page URLs to audit. Each URL produces one result. |
| `timeoutSecs` | No (default `20`) | Maximum seconds to wait for each page (1–120). Slow pages report a `timeout` error. |
| `concurrency` | No (default `10`) | How many pages to fetch in parallel (1–50). |
| `userAgent` | No | The User-Agent header sent with each request. Override to mimic a specific bot or browser. |
| `proxyConfiguration` | No | Optional Apify proxy. Most public pages don't need one. |

### Use cases

- **SEO consultants** running on-page audits — surface every page missing a title, meta description, or canonical, and rank H1 issues across the whole site.
- **Content auditors** cleaning up legacy content — find thin pages by word count and text-to-HTML ratio, then prioritize rewrites.
- **In-house SEO teams** monitoring schema coverage — verify every product page has Product schema, every article has Article schema, and every FAQ page has FAQ schema.
- **Accessibility leads** finding images missing alt text — get a per-page count and prioritize the worst offenders.
- **Competitive researchers** comparing social previews — pull Open Graph and Twitter cards from a competitor's blog to see how they package shares.

### Limitations

- Captures the HTML the server returns. Pages that render their content entirely in the browser (heavy single-page apps) may show empty title, missing headings, or zero links — for those sites, use a browser-based scraper instead.
- Some sites rate-limit aggressive scraping. Use the proxy option if you start hitting 403s.
- Word count is computed from visible text after removing script and style content. Inline SVG and obscure HTML may slightly inflate or reduce the count.
- Schema markup blocks that are syntactically broken are skipped rather than failing the whole audit.
- Authenticated pages are not supported — this is for public URLs only.

### Compared to alternatives

| | Thirdwatch Bulk SEO Audit | typical "SEO On-Page Analyzer" actors on Apify Store |
|---|---|---|
| Open Graph + Twitter cards as structured dicts | Yes | Often title/description only |
| Parsed schema markup blocks | Yes (full JSON) | Often missing |
| Heading lists (not just counts) | Yes | Often only counts |
| Internal vs external vs nofollow link counts | Yes | Often "total links" only |
| Image alt-text gap count | Yes | Sometimes |
| Text-to-HTML ratio (thin-content signal) | Yes | Rarely included |
| Concurrency control | Up to 50 parallel | Often 5–10 |

### FAQ

**Does this respect robots.txt?**
This actor sends one request per URL and does not crawl. It uses a clearly identifiable User-Agent (`ThirdwatchSEO/1.0`) so site owners can recognize and rate-limit it if they want.

**What's the rate limit?**
You control it via `concurrency` (max 50). For sites you don't own, keep it low and enable the proxy option if you hit 429 or 403.

**Can I audit pages behind a login?**
No — this actor does not handle authentication, cookies, or session headers. It's designed for public URLs.

**Why are some fields empty on JavaScript-heavy sites?**
Single-page apps render content in the browser after the initial HTML loads. This actor reads the server-rendered HTML only. For React/Vue/Angular sites with no server-side rendering, use a browser-based audit tool instead.

**Does it capture every kind of schema markup?**
It returns every structured-data block the page declares in script tags (Product, Article, FAQ, Breadcrumb, and others) as structured data. Microdata and RDFa formats are not captured.

### SEO meta tags and headings extractor for site-wide audits

Run a full on-page SEO audit across an entire site in minutes. Pair it with the [Subdomain Finder](https://apify.com/thirdwatch/subdomain-finder?fpr=9m2cd6) to map a domain's full footprint before auditing, or with [Google Search Results Scraper](https://apify.com/thirdwatch/google-search-scraper?fpr=9m2cd6) to check how audited pages are actually ranking.

Built and maintained by [Thirdwatch](https://thirdwatch.dev).

Last verified: 2026-05

# Actor input Schema

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

List of page URLs to audit. Each URL produces one result with title, meta description, headings, canonical, Open Graph, Twitter cards, schema markup, link counts, and word count.

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

Maximum seconds to wait for each page to respond. Pages slower than this are reported with a timeout error.

## `concurrency` (type: `integer`):

How many pages to fetch in parallel (1–50).

## `userAgent` (type: `string`):

User-Agent header sent with every request. Override to mimic a specific browser or search-engine bot.

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

Optional Apify proxy. Off by default — most public pages don't need one. Turn on if you start hitting 403 or 429.

## Actor input object example

```json
{
  "urls": [
    "https://example.com"
  ],
  "timeoutSecs": 20,
  "concurrency": 10,
  "userAgent": "Mozilla/5.0 (compatible; ThirdwatchSEO/1.0; +https://thirdwatch.dev)",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (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://example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("thirdwatch/seo-data-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://example.com"] }

# Run the Actor and wait for it to finish
run = client.actor("thirdwatch/seo-data-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://example.com"
  ]
}' |
apify call thirdwatch/seo-data-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thirdwatch/seo-data-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/zqTa3uyYnffkR9M6g/builds/taOijNVWUw8aYV9f2/openapi.json
