Website SEO Audit: On-Page SEO Checker & Site Crawler
Pricing
Pay per event
Website SEO Audit: On-Page SEO Checker & Site Crawler
Crawl a website and run an on-page SEO audit of every page: missing or long titles and meta descriptions, H1s, canonicals, noindex, image alt text, thin content, Open Graph, JSON-LD, slow responses and duplicate titles. Issues and a 0 to 100 score per page.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Scott Williams
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
19 hours ago
Last modified
Categories
Share
Website SEO Audit: on-page SEO checker and crawler, $3 per 1,000 pages
Website SEO Audit crawls a website from a start page and checks every page it reaches for on-page SEO problems: missing or overlong titles, missing meta descriptions, missing H1s, canonical tags, noindex, images without alt text, thin content, Open Graph tags, JSON-LD errors, slow responses, and more. Each page gets a list of issues with a severity and a 0 to 100 score, so you can sort a whole site and fix the worst pages first.
It reads the HTML your server sends. No browser, no proxy, no login. It obeys robots.txt and crawl delays.
At a glance: $0.003 per page audited ($3.00 per 1,000 pages); pages that return an error are saved free. Pay per result with no subscription, set a maximum cost per run, export to JSON, CSV, Excel or HTML, or call it from the Apify API, n8n, Make or Zapier.
Who it's for
- SEO agencies and freelancers running quick client audits or monthly site health checks.
- Site owners and developers checking a site after a redesign, CMS migration or template change.
- Content teams finding thin pages, duplicate titles and missing meta descriptions.
- Anyone automating SEO checks: schedule it weekly and compare scores between runs.
What it checks on every page
| Area | Checks |
|---|---|
| Title | Missing, too long (over 60 characters), too short (under 20 characters), duplicated on another audited page |
| Meta description | Missing, too long (over 160 characters), too short (under 70 characters), duplicated on another audited page |
| Headings | Missing H1, more than one H1, number of H2s |
| Indexing | Canonical missing or pointing to another page, noindex and nofollow in meta robots or the X-Robots-Tag header |
| Social and structured data | Open Graph and Twitter card tags, missing og:title, JSON-LD types found, JSON-LD blocks that fail to parse |
| Content | Word count, thin content (under 200 words), images without alt text (with up to 5 example image URLs) |
| Technical | HTTP status, redirects, response time (slow over 2 seconds), HTML size (large over 1 MB), HTTPS, viewport tag, lang attribute, hreflang links |
| Links | Internal, external and nofollow link counts per page |
Issue codes and score
Every issue has a code and a severity (error, warning or notice). The score starts at 100 and loses a fixed number of points per issue (never below 0):
| Code | Severity | Points |
|---|---|---|
http-error | error | 40 |
missing-title | error | 15 |
missing-meta-description | error | 10 |
missing-h1 | error | 8 |
missing-viewport | error | 6 |
thin-content | warning | 5 |
slow-response | warning | 5 |
duplicate-title | warning | 5 |
not-https | warning | 5 |
noindex | notice | 5 |
title-too-long, title-too-short | warning | 4 |
missing-canonical | warning | 4 |
images-missing-alt | warning | 4 |
meta-description-too-long, meta-description-too-short | warning | 3 |
multiple-h1 | warning | 3 |
large-html | warning | 3 |
duplicate-meta-description | warning | 3 |
invalid-json-ld | warning | 3 |
missing-lang | warning | 2 |
canonical-to-other-page | notice | 2 |
missing-open-graph | notice | 2 |
redirected | notice | 2 |
Duplicate titles and descriptions are detected among the pages audited in the same run for the same site.
How to use it
- Open the Actor in Apify Console and click Try for free.
- Add one start URL per site in Websites to audit (usually the home page). The Actor follows links on the same host.
- Set Max pages per site. The default is 10; raise it for a full audit.
- Optional: turn on Only pages with issues, add Exclude URL patterns such as
/tag/, or turn on Include JSON-LD data. - Click Start, then sort the dataset by
scoreor export it to CSV or Excel.
Input
| Field | Type | Default | Description |
|---|---|---|---|
startUrls | array (required) | ["https://www.python.org/"] | Start page for each site. Links on the same host are crawled (www. and the bare domain count as the same host). |
maxPagesPerSite | integer, 1 to 10,000 | 10 | Stop after this many pages per site. A sample run (start URLs exactly ["https://www.python.org/"], as in {} and the prefill) audits at most 2 pages. |
onlyPagesWithIssues | boolean | false | Only save pages that have at least one issue. Skipped pages are not charged. |
includeJsonLd | boolean | false | Add the parsed JSON-LD blocks to each page. Personal fields such as email, telephone and author are removed. |
excludePatterns | array of strings | none | Regular expressions. Matching URLs are not crawled, for example /tag/ or \?page=. |
maxResults | integer | 0 | Cap on pages saved to the dataset. 0 means no cap. |
Example input
{"startUrls": [{"url": "https://books.toscrape.com/"}],"maxPagesPerSite": 200,"onlyPagesWithIssues": true,"excludePatterns": ["/tag/"]}
Output
One dataset item per audited page. This is a real item from a 5-page test run on books.toscrape.com (a public scraping sandbox), shortened to the most used fields:
{"site": "https://books.toscrape.com/","url": "https://books.toscrape.com/","finalUrl": "https://books.toscrape.com/","statusCode": 200,"redirected": false,"responseTimeMs": 205,"htmlSizeKb": 50.1,"title": "All products | Books to Scrape - Sandbox","titleLength": 40,"metaDescription": null,"metaDescriptionLength": 0,"canonicalUrl": null,"metaRobots": "NOARCHIVE,NOCACHE","language": "en-us","viewport": "width=device-width","h1": ["All products"],"h1Count": 1,"h2Count": 0,"wordCount": 326,"imagesTotal": 20,"imagesMissingAlt": 0,"internalLinks": 94,"externalLinks": 0,"noindex": false,"jsonLdTypes": [],"issues": [{"code": "missing-meta-description", "severity": "error"},{"code": "missing-canonical", "severity": "warning"},{"code": "missing-open-graph", "severity": "notice"},{"code": "duplicate-title", "severity": "warning"}],"issueCount": 4,"score": 79,"checkedAt": "2026-10-30T01:31:18.989426+00:00"}
All output fields
| Field | Description |
|---|---|
site | The start URL this page was found from |
url, finalUrl | Requested URL and the URL after redirects |
statusCode, redirected, responseTimeMs, htmlSizeKb | HTTP status, whether it redirected, response time in ms, HTML size in KB |
xRobotsTag | X-Robots-Tag response header |
title, titleLength | Page title and its length |
metaDescription, metaDescriptionLength, metaKeywords | Meta description, its length, and meta keywords |
canonicalUrl, metaRobots, noindex, nofollow | Canonical link, meta robots, and the parsed noindex and nofollow flags |
language, charset, viewport, themeColor, faviconUrl | Basic head tags |
feedUrls, hreflang | RSS/Atom feed links and hreflang alternates (lang, url) |
openGraph | title, description, image, type, url, site_name, locale |
twitterCard | card, title, description, image, site |
publishedTime, modifiedTime | Article dates when the page declares them |
jsonLd, jsonLdTypes, jsonLdErrors | Parsed JSON-LD (only with includeJsonLd), the schema.org types found, and the number of blocks that failed to parse |
h1, h1Count, h2Count, wordCount | Headings and visible word count |
imagesTotal, imagesMissingAlt, imagesMissingAltSample | Image counts and up to 5 image URLs without alt text |
internalLinks, externalLinks, nofollowLinks | Link counts |
issues, issueCount, score | Issues found (code, severity), how many, and the 0 to 100 score |
checkedAt | When the page was fetched (UTC) |
error | Only on pages that could not be fetched |
The RUN_SUMMARY record in the key-value store has, per site, the number of pages audited, pages skipped by robots.txt, the average score, a count of every issue code, and a note when the crawl stopped at maxPagesPerSite or at your result or cost limit.
Pricing
This Actor uses pay-per-event pricing. You pay for pages audited, not for compute time.
| Event | Price |
|---|---|
| Page audited (one dataset item) | $0.003 |
| Actor start (charged by Apify, once per GB of run memory; 1 event at the default 1 GB) | $0.00005 |
Examples:
- Audit a 10-page site: 10 × $0.003 + $0.00005 = about $0.03.
- Audit 500 pages: about $1.50.
- Audit 1,000 pages: about $3.00.
Pages skipped by Only pages with issues are not saved and not charged. Pages blocked by robots.txt are not fetched and not charged. Pages that return an error (HTTP 400 and above, or no response) are saved with the http-error issue, free of charge. They still count toward Max pages in dataset.
Set a maximum cost per run. In Apify Console set Maximum cost per run, or pass maxTotalChargeUsd through the API. The Actor stops cleanly when the limit is reached and records it in RUN_SUMMARY (stoppedByCostLimit).
Integrations and API
The Actor ID is fecklesspleb/website-seo-audit (in API URLs: fecklesspleb~website-seo-audit).
- n8n: use the Apify node, choose Run an Actor with this Actor ID, then read the dataset items. Send low scores to Slack, email or a sheet.
- Make: use the Apify module Run an Actor, then Get Dataset Items.
- Zapier: use the Apify app with Run Actor, then a dataset items step.
- Schedules: create an Apify schedule to run the audit weekly or monthly, and a webhook to be notified when a run finishes.
- AI agents: call it through the Apify API, the Apify clients, or Apify's MCP server.
Run it with curl
curl -X POST "https://api.apify.com/v2/acts/fecklesspleb~website-seo-audit/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"startUrls": [{"url": "https://books.toscrape.com/"}], "maxPagesPerSite": 50, "onlyPagesWithIssues": true}'
Python
from apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("fecklesspleb/website-seo-audit").call(run_input={"startUrls": [{"url": "https://books.toscrape.com/"}], "maxPagesPerSite": 50},max_total_charge_usd=1,)pages = client.dataset(run["defaultDatasetId"]).list_items().itemsfor page in sorted(pages, key=lambda p: p.get("score") or 0):print(page.get("score"), page["url"], [i["code"] for i in page.get("issues", [])])
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });const run = await client.actor('fecklesspleb/website-seo-audit').call({startUrls: [{ url: 'https://books.toscrape.com/' }],maxPagesPerSite: 50,});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items.filter((p) => p.score < 80).map((p) => p.url));
Limits
- It reads the HTML the server returns and does not run JavaScript. Pages that build their content only in the browser may show up as thin or missing tags.
- It does not run Lighthouse and does not measure Core Web Vitals or page speed scores.
responseTimeMsis the server response time seen by the crawler. - It crawls one host per start URL and does not follow links to other domains or subdomains.
- Links on pages with a
nofollowmeta robots tag are not followed. - On the Apify platform, pages whose address is private, loopback or link-local (also after a redirect) are not fetched. They are output free with
statusCode: nullanderror: "Blocked: private, loopback and link-local addresses are not allowed". - Duplicate title and description checks only compare pages audited in the same run.
- The crawl is polite: at most about 2 requests per second per host, slower when robots.txt sets a crawl delay.
Common searches this Actor answers
- website SEO audit tool
- on-page SEO checker for a whole site
- SEO crawler that exports to CSV or Excel
- find pages with missing meta descriptions
- find duplicate page titles
- check H1 tags on every page
- find images missing alt text
- find noindex pages and canonical problems
- bulk SEO check via API
FAQ
How do I run an SEO audit of my whole website? Put your home page in Websites to audit and set Max pages per site to the number of pages you want checked (up to 10,000). The Actor follows internal links from page to page.
Can I audit several websites in one run?
Yes. Add one start URL per site. Each site is crawled separately and gets its own section in RUN_SUMMARY.
How is the SEO score calculated? Each page starts at 100 and loses the points listed in the issue table above for each issue found. The score is a quick way to sort pages; it is not a Google ranking factor.
Can I find only the pages with missing meta descriptions or titles?
Turn on Only pages with issues and filter the dataset by issue code, for example missing-meta-description or missing-title.
Does it check page speed or Core Web Vitals? No. It records the server response time and flags responses over 2 seconds, but it does not run Lighthouse.
Why are some pages missing from the results?
They are disallowed by robots.txt, on another host, excluded by your patterns, linked only from a nofollow page, or beyond Max pages per site. The run summary shows pages skipped by robots.txt and whether the crawl stopped early.
Does it work on JavaScript sites? It audits the HTML the server sends. Server-rendered and static sites work well. For sites that render everything in the browser, results reflect the initial HTML only.
Can I export the audit to Excel or Google Sheets? Yes. Apify datasets export to CSV, Excel, JSON and HTML, and you can send them to Google Sheets through n8n, Make or Zapier.
Is it legal to run an SEO audit on a website? The Actor reads public pages the way a search engine does, obeys robots.txt and crawl delays, and never logs in. Use it on sites you own or are allowed to audit.
Related Actors
- Broken Link Checker: find 404s and dead links and images on the same site.
- Website Metadata Extractor: pull titles, meta tags, Open Graph and JSON-LD from a list of URLs without crawling.
- Tech Stack Detector: see which CMS, frameworks, analytics and hosting a website uses.