# Bulk PageSpeed & SEO Audit — Core Web Vitals & Lighthouse (`benelux-signals/bulk-pagespeed-seo-audit`) Actor

Bulk audit URLs or a sitemap for $0.005/URL: Google PageSpeed Insights (Lighthouse scores, Core Web Vitals LCP/INP/CLS, mobile + desktop) plus on-page SEO checks (title, meta description, H1, canonical, noindex, hreflang, OG, alt text, schema, redirects), a 0–100 SEO score and issues.

- **URL**: https://apify.com/benelux-signals/bulk-pagespeed-seo-audit.md
- **Developed by:** [Benelux Signals](https://apify.com/benelux-signals) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 audited urls

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 PageSpeed & SEO Audit: Core Web Vitals, Lighthouse and Meta Tags

**Audit a list of URLs or a whole sitemap in one run for $0.005 per URL.** For every page you get the **Google PageSpeed Insights** results: Lighthouse performance, accessibility, best-practices and SEO scores, plus **Core Web Vitals** (LCP, INP, CLS) for mobile and desktop. Each page also gets a full **on-page SEO check**: title, meta description, H1, canonical, noindex, hreflang, Open Graph, image alt text, word count, structured data, status code, redirect chain and response time. On top of that comes a **0–100 SEO score** and a prioritised list of issues. You can export to CSV, Excel or JSON, or pull the results through the API.

Failed URLs (404s, timeouts, pages blocked by robots.txt) show up in the dataset with a clear error and are **never charged**.

### Who is this bulk SEO audit tool for?

- **SEO agencies and freelancers** who need a quick technical SEO audit of a client's top pages or a whole sitemap before a pitch or monthly report.
- **Developers and site owners** who check Core Web Vitals and Lighthouse scores after a release, on a schedule, or in CI through the API.
- **Content and marketing teams** who need missing meta descriptions, titles that are too long, missing H1s, pages without alt text or accidental `noindex` tags.
- **AI agents and MCP clients**: every input has a safe default, so a bare call with just `urls` works.

### What does Bulk PageSpeed & SEO Audit do?

For every URL it:

1. **Fetches the page** like a polite crawler. It follows redirects and records the full chain, measures time to first byte, respects robots.txt and Crawl-delay, and keeps at most a few requests per host in flight.
2. **Runs an on-page SEO audit** on the HTML:
   - title and meta description, with their lengths
   - H1 count and texts, H2 count
   - canonical (self or elsewhere, duplicates)
   - meta robots, X-Robots-Tag, noindex/nofollow, and whether robots.txt allows Googlebot
   - hreflang (x-default, self-reference, invalid codes) and html lang
   - viewport
   - Open Graph and Twitter card
   - image alt-text coverage
   - visible word count, internal and external links
   - schema.org types from JSON-LD, microdata and RDFa, plus invalid JSON-LD
3. **Scores the page from 0 to 100** and lists every issue with a severity (error, warning or notice) and a plain-English message, for example *"Meta description is long (182 characters; Google usually cuts off around 160)."*
4. **Calls the official Google PageSpeed Insights API v5** for mobile and/or desktop. You get:
   - Lighthouse category scores
   - lab metrics: FCP, LCP, TBT, CLS, Speed Index, TTFB
   - real-user Chrome UX Report (CrUX) field data: LCP, INP, CLS, FCP and TTFB at p75, with good/needs-improvement/poor categories, falling back to origin data when the page has none
   - a Core Web Vitals PASSED/FAILED verdict
   - the top 5 performance opportunities with estimated savings

#### Google API key (free) for PageSpeed Insights

Google no longer reliably answers PageSpeed Insights requests without an API key: the shared keyless quota returns "quota exceeded". To get Lighthouse scores and Core Web Vitals, paste **your own free key** into **Google API key**:

1. Open [Google Cloud Console → APIs & Services](https://console.cloud.google.com/apis/library/pagespeedonline.googleapis.com) and enable the **PageSpeed Insights API**.
2. Go to **Credentials → Create credentials → API key**. You can restrict the key to the PageSpeed Insights API.
3. Paste the key into the Actor input. It is stored encrypted, sent to Google in a request header and never logged.

The free quota is 25,000 queries per day. One URL uses one query per strategy, so mobile + desktop uses two.

**Without a key**, the default `pageSpeed: "auto"` skips PageSpeed and you still get the complete on-page SEO audit and score. If you force `mobile`, `desktop` or `both` without a key and Google refuses the call, the row is marked `partial` and is **not charged**.

### How to use it

1. Paste your URLs (one per line), or a sitemap URL such as `https://example.com/sitemap.xml`. A plain site root works too: it is resolved via robots.txt or /sitemap.xml.
2. Set **Max URLs**. The default is 50, and this is also your cost cap.
3. Optional: add your Google API key for Lighthouse and Core Web Vitals.
4. Click **Start**. Open the **Audit overview** table, or export to CSV, Excel or JSON.

#### Input example

```json
{
  "urls": ["https://www.example.com/", "https://www.example.com/pricing"],
  "sitemapUrl": "",
  "maxUrls": 50,
  "pageSpeed": "auto",
  "googleApiKey": "YOUR_FREE_GOOGLE_API_KEY",
  "maxConcurrency": 3
}
```

| Field | Default | What it does |
|---|---|---|
| `urls` | 2 example URLs | Pages to audit. `example.com` is read as `https://example.com/`. Duplicates are removed. |
| `sitemapUrl` | empty | XML sitemap, sitemap index or `.xml.gz`, or a site root. Child sitemaps are followed. |
| `maxUrls` | 50 | Maximum URLs per run (list and sitemap together), between 1 and 5,000. |
| `pageSpeed` | `auto` | `auto` (mobile + desktop with a key, skipped without one), `mobile`, `desktop`, `both` or `off`. |
| `googleApiKey` | none | Your free Google API key for PageSpeed Insights. Secret. |
| `lighthouseCategories` | all 4 | performance, accessibility, best-practices, seo. |
| `includeOpportunities` | true | Adds the top 5 Lighthouse opportunities to each result. |
| `maxConcurrency` | 3 | URLs audited in parallel (1–10). |
| `requestDelayMs` | 1000 | Delay between requests to the same host. |
| `pageTimeoutSecs` | 30 | Timeout for fetching one page. |
| `respectRobotsTxt` | true | Skips disallowed pages and reports them as a free error row. |
| `proxyConfiguration` | off | Optional proxy for the page fetch. |

#### Output example

One record per URL. Shortened here; the `mobile` and `desktop` objects contain all Lighthouse and CrUX data.

```json
{
  "url": "https://www.example.com/",
  "finalUrl": "https://www.example.com/",
  "status": "ok",
  "charged": true,
  "statusCode": 200,
  "redirectCount": 0,
  "responseTimeMs": 182,
  "seoScore": 92,
  "issueCount": 3,
  "topIssues": [
    "[warning] Meta description is long (182 characters; Google usually cuts off around 160).",
    "[notice] 4 of 12 images have no alt text.",
    "[notice] No structured data (JSON-LD, microdata or RDFa) found."
  ],
  "title": "Example — Fast websites for small businesses",
  "titleLength": 44,
  "metaDescriptionLength": 182,
  "h1Count": 1,
  "canonical": "https://www.example.com/",
  "noindex": false,
  "hreflang": [{ "lang": "en", "href": "https://www.example.com/" }],
  "openGraph": { "title": "Example", "description": "…", "image": "https://www.example.com/og.png", "type": "website", "url": null },
  "imageAltCoverage": 66.7,
  "wordCount": 812,
  "structuredDataTypes": [],
  "pageSpeedStatus": "ok",
  "mobilePerformanceScore": 71,
  "desktopPerformanceScore": 96,
  "mobileLcpMs": 3120,
  "mobileCls": 0.02,
  "mobileTbtMs": 210,
  "mobileCoreWebVitals": "PASSED",
  "mobile": {
    "performanceScore": 71, "accessibilityScore": 92, "bestPracticesScore": 100, "seoScore": 92,
    "lab": { "fcpMs": 1650, "lcpMs": 3120, "tbtMs": 210, "cls": 0.02, "speedIndexMs": 3400, "ttfbMs": 120 },
    "field": { "source": "url", "lcpMs": 2100, "lcpCategory": "FAST", "inpMs": 140, "inpCategory": "FAST", "cls": 0.03, "clsCategory": "FAST" },
    "coreWebVitals": "PASSED",
    "opportunities": [{ "id": "unused-javascript", "title": "Reduce unused JavaScript", "savingsMs": 600 }],
    "lighthouseVersion": "13.0.0"
  }
}
```

**`status` values:**

- `ok`: complete audit, charged.
- `partial`: the on-page audit is done but a PageSpeed call failed. Not charged.
- `error`: the page could not be audited (HTTP 4xx/5xx, timeout, DNS error, not HTML, blocked by robots.txt). Not charged, and the `error` field says why.

A run summary (counts, average scores, charge-limit flags, PageSpeed problems, sitemap details) is saved as `RUN_SUMMARY` in the key-value store.

### What does the SEO score check?

The score starts at 100, and each issue subtracts a weighted penalty. Examples:

- `noindex`: −30
- robots.txt blocks Googlebot: −20
- missing title: −15
- missing meta description: −10
- missing H1: −10
- no HTTPS: −10
- missing viewport: −8
- less than 80% image alt coverage: −8
- missing canonical: −5
- redirect chain: −5
- thin content: −5
- slow TTFB: −5
- smaller warnings and notices for lengths, multiple H1s, hreflang, Open Graph and structured data

All issue codes are in the `issues` array, so you can filter or re-weight them yourself.

### Speed

- **On-page audit only** (no key, or `pageSpeed: "off"`): about 1–2 seconds per URL, faster when the URLs are on different hosts. 50 URLs take about one minute.
- **With PageSpeed Insights**: Google runs Lighthouse on its own servers, which takes 10–40 seconds per strategy. The Actor runs up to 4 PageSpeed calls in parallel, so 50 URLs × mobile + desktop take about 5–10 minutes.

### Pricing

This Actor uses **pay per event**: **$0.005 per audited URL**, with platform usage included. You get mobile + desktop PageSpeed, the on-page SEO audit and the score in one price. Failed and partial URLs are free.

| Event | Free plan | Bronze | Silver | Gold |
|---|---|---|---|---|
| Audited URL | $0.005 | $0.0045 | $0.004 | $0.0035 |
| Actor start (per run) | $0.005 | | | |

#### Cost table (Free plan prices)

| What you audit | URLs | Cost |
|---|---|---|
| Quick check of a few key pages | 10 | $0.055 |
| Default run | 50 | $0.255 |
| A mid-size sitemap | 500 | $2.505 |
| A large site | 5,000 | $25.005 |

**Bill-shock protection:**

- A run never audits more than `maxUrls` (default 50, so at most **$0.255 per run** on the Free plan).
- It stops cleanly at your *maximum cost per run*. URLs that did not fit are listed in `RUN_SUMMARY.notAuditedBecauseOfChargeLimit`.
- Failed URLs, partial results and invalid input lines are never charged.

### FAQ

#### Do I need a Google API key?

Only for Lighthouse scores and Core Web Vitals. The on-page SEO audit, the score and the issue list work without a key. A key is free and takes about two minutes to create (see above).

#### Is the data the same as on pagespeed.web.dev?

Yes. The Actor calls the same official PageSpeed Insights API v5. Lab scores vary a little from run to run, because Lighthouse simulates a device and network. Field data (CrUX) is a 28-day rolling p75 of real Chrome users and is only available for pages and origins with enough traffic. Otherwise you get `NO_FIELD_DATA`.

#### Does it render JavaScript?

The on-page checks read the HTML the server returns, which is what most crawlers see first. Tags that are injected only by client-side JavaScript are not seen in the on-page part. PageSpeed Insights does render the page fully.

#### Does it crawl my whole site?

It audits the URLs you give it, or the URLs in your sitemap up to `maxUrls`. It does not follow links. For link discovery, use a crawler and feed the URLs in.

#### Why was a URL marked as an error?

The `error` field tells you why: HTTP status, timeout, DNS or SSL error, a non-HTML response, or a page that robots.txt disallows for crawlers (set `respectRobotsTxt` to false for your own sites). These rows are free.

#### Can I schedule it or use it from an AI agent?

Yes. Save a task and schedule it daily or weekly, or call it via the Apify API, the Apify MCP server or any integration (Make, Zapier, n8n, Google Sheets). Every field has a default.

### Is it legal?

The Actor only reads publicly available pages, like a search engine crawler. It identifies itself with its own user agent, respects robots.txt and keeps requests polite. PageSpeed data comes from Google's official API under your own key. Only audit sites you are allowed to test, and respect their terms.

### Related actors by benelux-signals

- **EU Tender Radar**: new public tenders from TED and TenderNed.
- **Benelux Trade Fair Exhibitors**: exhibitor lists from Dutch and Belgian trade fairs.

# Actor input Schema

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

Pages to audit, one per line. "example.com" is read as https://example.com/. Duplicates are removed. Leave empty when you use a sitemap. Each fully audited URL is one billable event; failed URLs are free.

## `sitemapUrl` (type: `string`):

Audit the pages listed in an XML sitemap or sitemap index (.xml or .xml.gz). A site root such as https://example.com/ is resolved via robots.txt or /sitemap.xml. URLs are taken in sitemap order up to Max URLs.

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

Maximum number of URLs audited in this run (URL list + sitemap together). This is also your cost cap: max cost = Max URLs × the per-URL price + $0.005 start.

## `pageSpeed` (type: `string`):

Which Google PageSpeed Insights tests to run per URL. "auto" = mobile + desktop when you add an API key, skipped (on-page SEO only) without a key. "off" = on-page SEO audit only.

## `googleApiKey` (type: `string`):

Your own free Google API key (Google Cloud Console → APIs & Services → enable "PageSpeed Insights API" → Credentials → Create API key; 25,000 free queries/day). Google no longer serves keyless requests reliably, so without a key you get the on-page SEO audit only. The key is stored encrypted and is never logged.

## `lighthouseCategories` (type: `array`):

Lighthouse categories requested from PageSpeed Insights. Core Web Vitals and lab metrics always come with performance.

## `includeOpportunities` (type: `boolean`):

Add the top 5 Lighthouse opportunities (e.g. "Reduce unused JavaScript") with estimated savings in ms to each PageSpeed result.

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

URLs audited in parallel. Requests to the same host are still spaced by the request delay, so this mainly speeds up multi-site lists.

## `requestDelayMs` (type: `integer`):

Polite spacing between page requests to one host. A robots.txt Crawl-delay (max 30 s) is honoured when it is longer.

## `pageTimeoutSecs` (type: `integer`):

Timeout for fetching one page (the PageSpeed Insights call has its own 120 s timeout).

## `respectRobotsTxt` (type: `boolean`):

Skip pages that robots.txt disallows for this crawler (reported as an error row, not charged). Turn off only for your own sites.

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

Optional proxy for the on-page fetch (PageSpeed Insights runs on Google's servers). Not needed for most sites.

## Actor input object example

```json
{
  "urls": [
    "https://www.example.com/",
    "https://www.example.com/pricing"
  ],
  "sitemapUrl": "https://www.example.com/sitemap.xml",
  "maxUrls": 50,
  "pageSpeed": "auto",
  "lighthouseCategories": [
    "performance",
    "seo"
  ],
  "includeOpportunities": true,
  "maxConcurrency": 3,
  "requestDelayMs": 1000,
  "pageTimeoutSecs": 30,
  "respectRobotsTxt": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

All audited URLs in the 'overview' table view (status, SEO score, Lighthouse performance, Core Web Vitals, on-page checks, top issues).

## `runSummary` (type: `string`):

JSON summary (complete/partial/failed counts, charged URLs, average scores, PageSpeed status, sitemap info, charge-limit flags) stored under RUN_SUMMARY.

# 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://apify.com/",
        "https://crawlee.dev/"
    ],
    "maxUrls": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("benelux-signals/bulk-pagespeed-seo-audit").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://apify.com/",
        "https://crawlee.dev/",
    ],
    "maxUrls": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("benelux-signals/bulk-pagespeed-seo-audit").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://apify.com/",
    "https://crawlee.dev/"
  ],
  "maxUrls": 50
}' |
apify call benelux-signals/bulk-pagespeed-seo-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,benelux-signals/bulk-pagespeed-seo-audit"
        }
    }
}
```

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/bLArS8cFxYgfpwJu1/builds/xIjejIhSA2cX9oeRh/openapi.json
