# Bulk PageSpeed Insights for a Sitemap (`bjgomes/bulk-pagespeed-insights-for-a-sitemap`) Actor

Run PageSpeed Insights on every URL in a sitemap or a list. Get the performance score and Core Web Vitals for mobile and desktop, one row per URL and device. Uses your own free Google API key. A test that fails comes back as a row that says why. Export to CSV, JSON or Excel.

- **URL**: https://apify.com/bjgomes/bulk-pagespeed-insights-for-a-sitemap.md
- **Developed by:** [Brian Gomes](https://apify.com/bjgomes) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.75 / 1,000 pagespeed tests

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 Insights for a Sitemap runs Google PageSpeed Insights on every URL in a sitemap or a list, and returns the performance score and Core Web Vitals for mobile and desktop.

### What it does

Give it a website, a sitemap URL or a list of URLs. It reads the sitemap, then runs a PageSpeed Insights test on each URL, once for mobile and once for desktop (you can pick one). You get one row per URL and device, with the performance score and the metrics behind it.

It uses your own Google API key. That takes about two minutes to get, and the steps are below.

When a test fails, the run doesn't fail with it. A page that times out, a quota limit or a bad key gives you a row that says what happened.

Here's what five rows look like:

| url | device | performanceScore | lcpMs | cls | fieldInpMs |
|---|---|---|---|---|---|
| https://example.com/ | mobile | 62 | 4,100 | 0.12 | 240 |
| https://example.com/ | desktop | 91 | 1,300 | 0.02 | 90 |
| https://example.com/pricing | mobile | 78 | 2,900 | 0.04 | (no field data) |
| https://example.com/blog/launch | mobile | 45 | 6,200 | 0.31 | 380 |
| https://example.com/docs/start | desktop | 97 | 800 | 0.00 | (no field data) |

(Example data.)

### Get your Google API key (about two minutes)

The Actor needs your own key. Here's why. Google's API works without one, but it's meant for occasional use, and Google recommends a key for frequent, automated queries. A bulk run is exactly that. With your own key, the daily quota is yours and nobody else's run can use it up. The key is free.

1. Open Google's [PageSpeed Insights API: Get Started](https://developers.google.com/speed/docs/insights/v5/get-started) page and follow "Acquiring and using an API key". It takes you to the [Credentials page](https://console.cloud.google.com/apis/credentials) in Google Cloud.
2. Pick or create a project, then click **Create credentials** and **API key**.
3. Turn on the **PageSpeed Insights API** for that project, if Google asks.
4. Optional but a good idea: restrict the key to the PageSpeed Insights API only.
5. Paste the key into the Actor's **Google API key** field.

The key is stored as a secret input. It's sent only to Google, in a request header, and it isn't shown in the run log, the dataset or any error message.

### Who it's for

- **SEO audits.** Find the slowest pages on a site, not just the homepage.
- **Before and after a release.** Run it on the same sitemap twice and compare the scores.
- **Agencies.** Score every page of a client's site in one run and hand them the spreadsheet.
- **Monitoring.** Run it weekly on a schedule and catch a page that got slower.

### Input

| Field | What it does | Default |
|---|---|---|
| Websites or sitemap URLs | Homepages or sitemap URLs. Each is expanded to every URL in its sitemap. | none |
| Page URLs to test | Single pages, tested as given, before any sitemap pages. | none |
| Google API key | Your PageSpeed Insights API key (see above). Without one, a run tests at most 2 URLs on Google's shared keyless quota, which is often used up already. | none |
| Devices | Mobile, desktop or both. | both |
| Maximum URLs | Stops after this many URLs. Use it to test on a big site first. | 100 |
| Opportunities per test | How many of Lighthouse's improvement suggestions to list, biggest time saving first. | 5 |
| Tests in flight at once | Tests running at once. Keep it low if your key has a small quota. | 4 |
| Test timeout (seconds) | The longest one test may take, start to finish, retries included. | 120 |

Give at least one website, sitemap or page URL. Input field names in the API: `startUrls`, `urls`, `apiKey`, `devices`, `maxUrls`, `maxOpportunities`, `maxConcurrency`, `requestTimeoutSecs`.

The form starts at 2 URLs on mobile. That runs without a Google API key and costs $0.005. Add your own free key to test more.

Example input:

```json
{
  "startUrls": [{ "url": "https://example.com/sitemap.xml" }],
  "apiKey": "YOUR_GOOGLE_API_KEY",
  "devices": ["mobile", "desktop"],
  "maxUrls": 100
}
```

### Output

One row per URL and device. Export it from the Console as CSV, JSON, Excel, XML or HTML, or read it through the Apify API.

| Field | What it holds |
|---|---|
| `type` | `result` for a test that ran, `error` for one that didn't. |
| `url` | The page tested. |
| `device` | `mobile` or `desktop`. |
| `performanceScore` | Lighthouse performance score, 0 to 100. |
| `lcpMs` | Largest Contentful Paint from the lab test, in milliseconds. |
| `cls` | Cumulative Layout Shift from the lab test. |
| `tbtMs` | Total Blocking Time from the lab test. The lab stand-in for INP. |
| `fcpMs` | First Contentful Paint from the lab test. |
| `speedIndexMs` | Speed Index from the lab test. |
| `fieldInpMs` | Interaction to Next Paint from real Chrome users, if Google has enough data for the page. Empty otherwise. |
| `fieldLcpMs` | Largest Contentful Paint from real Chrome users, if available. |
| `fieldCls` | Cumulative Layout Shift from real Chrome users, if available. |
| `fieldFcpMs` | First Contentful Paint from real Chrome users, if available. |
| `fieldCategory` | Google's overall rating from real users: `FAST`, `AVERAGE` or `SLOW`, if available. |
| `fieldDataScope` | `url` when the real-user data is for this page, `origin` when Google only has it for the whole site. |
| `topOpportunities` | Lighthouse's biggest improvement opportunities: `id`, `title`, `savingsMs`, `savingsBytes`. |
| `topOpportunitiesText` | The same, as one line for spreadsheets, e.g. `render-blocking-resources (~1210 ms)`. |
| `finalUrl` | The URL Lighthouse ended on after redirects. |
| `lighthouseVersion`, `fetchTime` | Which Lighthouse ran the test, and when. |
| `error` | Empty when the row is fine. Otherwise it says what went wrong, like "quota exceeded" or "invalid API key". |

Example row (JSON):

```json
{
  "type": "result",
  "url": "https://example.com/",
  "device": "mobile",
  "performanceScore": 62,
  "lcpMs": 4100,
  "cls": 0.12,
  "tbtMs": 350,
  "fcpMs": 1800,
  "fieldInpMs": 240,
  "fieldLcpMs": 3200,
  "fieldCls": 0.08,
  "fieldCategory": "AVERAGE",
  "fieldDataScope": "url",
  "topOpportunitiesText": "render-blocking-resources (~1210 ms); unused-javascript (~450 ms, ~120 KiB)",
  "error": null
}
```

### Pricing

You pay per test: one URL on one device. There's no monthly fee, and Google doesn't charge for the API.

- **Each test:** $0.0025. Mobile and desktop are two tests, so a page on both devices is $0.005.
- A test that fails (quota, bad key, timeout, a page Lighthouse can't load) isn't charged.

A worked example. A site has 200 pages in its sitemap. Both devices: 400 tests × $0.0025 = $1.00. Mobile only: 200 tests × $0.0025 = $0.50.

Apify also charges its standard start fee for each run. Set Maximum URLs, or a maximum cost per run, for a hard ceiling: when the next test wouldn't fit under your maximum cost, the run stops and says so.

### How it handles the hard cases

- **No sitemap.** You get one row with the error "no sitemap found" and nothing else is charged. The run finishes normally.
- **Nested sitemap indexes and gzipped sitemaps.** It reads them the same way as Sitemap URL Extractor & Status Checker.
- **Quota limit (429 from Google).** Retrying won't help until the quota resets, so the run stops at once. You keep every result so far, plus one `error` row with Google's own message and how many tests weren't run. If no test ran at all, the run is marked failed so you can't miss it. Nothing is charged for the tests that didn't run.
- **Bad key.** If Google refuses the key, the run stops on the first test with Google's message, so you aren't charged for a run of errors.
- **Google's own hiccups.** A 503 from Google is tried up to 3 times in all, with a pause between, as Google's API guidance says, and all within the test timeout. Any other error from Google comes back as an error row for that URL, not retried.
- **Slow pages.** A PageSpeed test usually takes 10 to 30 seconds. Each test gets 2 minutes by default (Test timeout), start to finish, retries and pauses included; past that it comes back as an error row.
- **robots.txt.** It reads robots.txt to find sitemaps and follows its rules for reading them. The page tests themselves are run by Google's servers, not by this Actor.
- **Nothing but failures.** If 20 tests in a row fail (or 10 minutes go on failed tests), the run stops and says so, instead of spending your time on a site Google can't test. Failed tests are never charged.
- **Private addresses.** Sitemaps on private or internal network addresses are refused with an error row.

### Compared with the free tools

You can do this for free, one page at a time. Paste a URL into [PageSpeed Insights](https://pagespeed.web.dev/) and you get the same scores this Actor returns. Google Search Console has a Core Web Vitals report that groups your pages by real-user data. Lighthouse runs in Chrome's developer tools. For a handful of pages, that's all you need.

This Actor fits when you have dozens or hundreds of pages, want them in one spreadsheet, or want the same test every week. It reads the sitemap for you, runs the tests in the cloud and hands the results to your code through the API.

Compared with other PageSpeed Actors in the store, the difference is the sitemap input and how it fails. You give it a site, not a list you built by hand. A test that fails gives you a row that says why, not a failed run.

Not made by or affiliated with Google.

### FAQ

**How do I run PageSpeed Insights on many URLs at once?**
Put your sitemap URL or homepage in the input, add your Google API key and run it. You get one row per URL and device. Export it as CSV.

**Why do I need my own API key?**
Google recommends a key for frequent, automated queries, and a bulk run is that. Your own key means the quota is yours alone. It's free, and the steps are in "Get your Google API key" above.

**Why is INP empty for some pages?**
PageSpeed Insights only reports INP from real Chrome users. A page without enough visitors has no field data, so INP is empty. Use `tbtMs` from the lab test as the stand-in.

**Why are my scores different from last time?**
Lab scores move a few points between runs, even on the same page. Compare trends across several runs, not one number.

**What happens when I hit Google's quota?**
The run stops, keeps the results so far and adds one error row with Google's message. The tests that didn't run aren't charged. You can see your key's quota on the Quotas page for the PageSpeed Insights API in Google Cloud.

**Is my API key safe?**
It's stored as a secret input, sent only to Google in a request header, and never written to the log, the dataset or an error message. Restricting the key to the PageSpeed Insights API limits what it can be used for.

**Can I run it on a schedule?**
Yes. Use Apify Schedules to run it weekly with the same input. Each run makes a new dataset, so you can compare scores over time.

**Can I call it from my own code?**
Yes. Start it and read the results through the Apify API or the Python and JavaScript clients. It also works with webhooks, Make, Zapier and Google Sheets.

### Limits

- It needs your own Google API key for more than 2 URLs. Your key's daily quota caps how many URLs you can test in a day.
- It tests public pages. Google can't reach pages behind a login.
- Tests are slow, usually 10 to 30 seconds each, 4 at a time by default. A 500-URL run on both devices takes a while.
- It reads XML sitemaps, including gzipped ones. Plain-text and RSS sitemaps aren't read in this version.
- It runs in 256 MB by default. A sitemap file over 55 MB uncompressed is refused.

### Integrations

This Actor runs on Apify, so everything the platform does works with it.

- **API.** Start a run and read the results from your own code, in any language. [Apify API](https://docs.apify.com/integrations/api)
- **Schedules.** Run it weekly and catch a page that got slower. [Schedules](https://docs.apify.com/actors/running/schedules)
- **Tasks.** Save your input once as a task and run it again with one click, or on a schedule. [Tasks](https://docs.apify.com/actors/running/tasks)
- **Webhooks.** Get a call to your own URL when a run succeeds or fails. [Webhooks](https://docs.apify.com/integrations/webhooks)
- **Google Sheets and Drive.** Download the results as CSV or Excel and open them in Sheets. Or add a Google Sheets step after this Actor in Make or Zapier. Or send the files to a Google Drive folder. [Datasets and exports](https://docs.apify.com/storage/dataset), [Google Drive](https://docs.apify.com/integrations/drive)
- **Zapier, Make and n8n.** Start a run from another app and send the results on to the next step. [Zapier](https://docs.apify.com/integrations/zapier), [Make](https://docs.apify.com/integrations/make), [n8n](https://docs.apify.com/integrations/n8n)
- **AI agents (MCP).** Claude, ChatGPT and other MCP clients can find and run this Actor through the Apify MCP server. [Apify MCP server](https://docs.apify.com/integrations/mcp)
- **Other Actors.** Pass this Actor's results to another Actor when a run finishes. [Actor-to-Actor](https://docs.apify.com/integrations/actors)

### Works with

- [Site Audit](https://apify.com/bjgomes/site-audit-seo): an SEO score from 0 to 100 for every page, with a fix for each issue.
- [Sitemap URL Extractor & Status Checker](https://apify.com/bjgomes/sitemap-url-extractor-status-checker): the full URL list of a site, with lastmod and status codes.
- [Broken Link Checker from Sitemap](https://apify.com/bjgomes/broken-link-checker-from-sitemap): every broken link on every page in a sitemap, with the page it's on.

### Support

Found a bug or need a feature? Open an issue on this Actor's **Issues** tab. Include the run ID and the URL you gave it.

# Actor input Schema

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

A website (example.com) to find its sitemaps from robots.txt, /sitemap.xml and /sitemap\_index.xml, or a sitemap URL to read directly. Every page listed is tested, up to Maximum URLs.

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

Individual pages to test, instead of or as well as a sitemap. Tested before any sitemap pages.

## `apiKey` (type: `string`):

Your own Google Cloud API key with the PageSpeed Insights API enabled. Stored encrypted by Apify, sent only to Google in a request header, never logged or written to the output. Without a key the run tests at most 2 URLs on Google's shared keyless quota, which is often already used up.

## `devices` (type: `array`):

Test as a mobile device, a desktop, or both. Each URL and device is one test.

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

Stop after this many unique URLs across all inputs. Tests run = URLs x devices.

## `maxOpportunities` (type: `integer`):

How many of Lighthouse's improvement opportunities to list per test, largest estimated time saving first.

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

Parallel PageSpeed requests. Google's default quota is far above this; lower it if your key has a custom limit.

## `requestTimeoutSecs` (type: `integer`):

The longest one PageSpeed test may take, start to finish, retries included. A slow page can take a minute.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://apify.com"
    }
  ],
  "devices": [
    "mobile"
  ],
  "maxUrls": 2,
  "maxOpportunities": 5,
  "maxConcurrency": 4,
  "requestTimeoutSecs": 120
}
```

# Actor output Schema

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

One row per URL and device with the PageSpeed scores and metrics, plus an error row for any test that failed.

# 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 = {
    "startUrls": [
        {
            "url": "https://apify.com"
        }
    ],
    "devices": [
        "mobile"
    ],
    "maxUrls": 2
};

// Run the Actor and wait for it to finish
const run = await client.actor("bjgomes/bulk-pagespeed-insights-for-a-sitemap").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 = {
    "startUrls": [{ "url": "https://apify.com" }],
    "devices": ["mobile"],
    "maxUrls": 2,
}

# Run the Actor and wait for it to finish
run = client.actor("bjgomes/bulk-pagespeed-insights-for-a-sitemap").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 '{
  "startUrls": [
    {
      "url": "https://apify.com"
    }
  ],
  "devices": [
    "mobile"
  ],
  "maxUrls": 2
}' |
apify call bjgomes/bulk-pagespeed-insights-for-a-sitemap --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,bjgomes/bulk-pagespeed-insights-for-a-sitemap"
        }
    }
}
```

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/6PbDvF6WyFBPtZhAO/builds/zFOnP9gdeG0cP0De2/openapi.json
