# Link Extractor (`automation-lab/link-extractor`) Actor

Extract all links from any web page or website: URL, anchor text, internal or external, nofollow and rel, page location. Crawl up to 1,000 pages, filter links, export one row per link to CSV. Priced per page, not per link.

- **URL**: https://apify.com/automation-lab/link-extractor.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Developer tools, SEO tools
- **Features:** Limited permissions
- **Stats:** 165 total users, 6 monthly users, 99.9% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 url extracteds

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

## Link Extractor

Extract all links from any web page or website: URL, anchor text, internal or external, nofollow and rel, and where on the page each link sits. Export one row per link to CSV or Excel. You pay per page, not per link.

### What does Link Extractor do?

This actor extracts **all hyperlinks** from web pages. For each link, it captures the anchor text, href, rel attributes (nofollow, ugc, sponsored), target attribute, and classifies links as internal or external. It also detects the link's location in the page (nav, header, footer, main content, sidebar). Process a list of pages, or crawl a website up to 1,000 pages per start URL to build a link map. Keep only the links you need with the internal/external, duplicate and keyword filters.

### Who is it for?

- 🔍 **SEO specialists** — extracting all outbound and internal links from web pages for auditing
- 💻 **Web developers** — mapping site link structures for migration planning
- 📊 **Market researchers** — discovering linked resources and partner networks from competitor sites
- 🛡️ **Security analysts** — identifying external link destinations for phishing or malware detection
- 📝 **Content strategists** — analyzing link patterns and resource references across web pages

### Use cases

- **SEO specialists** -- audit internal and external linking patterns to optimize site architecture and link equity flow
- **Link builders** -- identify nofollow and sponsored link usage on target websites for outreach planning
- **Content strategists** -- understand link distribution across page sections to improve content structure
- **Migration teams** -- extract all links before and after URL changes to verify nothing is broken
- **Competitive analysts** -- discover who competitors link to and how they structure their outbound links

### Why use Link Extractor?

- **Batch processing** -- extract links from hundreds of pages in a single run
- **Priced per page, not per link** -- a page with 500 links costs the same as a page with 5, so link-heavy pages stay cheap
- **Website crawling** -- follow links on the same site breadth-first, up to 5 levels deep and 1,000 pages per start URL. Redirects such as `example.com` to `www.example.com` are followed
- **Link filters** -- keep only internal or external links, remove duplicates, or keep URLs containing a word such as `/blog/` or `.pdf`
- **One row per link** -- the "All links" dataset view gives a flat table for CSV, Excel and Google Sheets
- **Detailed link attributes** -- captures rel, target, anchor text, and link type for each link
- **Truthful fetch diagnostics** -- reports HTTP status, final redirect URL, and content type instead of presenting failed or non-HTML fetches as successful empty extractions
- **Internal/external classification** -- automatically sorts links by domain relationship
- **Page location detection** -- identifies whether links sit in nav, header, footer, sidebar, or main content
- **Structured JSON output** -- machine-readable results ready for analysis or import into SEO tools
- **Pay-per-event pricing** -- cost-effective at scale, starting at fractions of a cent per URL
- **Fast and lightweight** -- HTTP-only requests with no browser overhead, so runs complete quickly

### Input parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `urls` | string\[] | Yes | -- | Web pages to extract all links from. A line that is not a web page URL is skipped at no charge |
| `maxDepth` | integer | No | `0` | Follow page links on the same site up to 5 levels. At `0`, only supplied URLs are fetched |
| `maxPagesPerStartUrl` | integer | No | `25` | Save at most 1-1,000 pages per start URL when crawling is enabled |
| `linkScope` | string | No | `all` | Keep `all` links, only `internal` links, or only `external` links |
| `uniqueLinks` | boolean | No | `false` | Keep only the first link to each destination URL on a page |
| `urlKeywords` | string\[] | No | -- | Keep only links whose URL contains at least one of these words, ignoring case |

#### Example input: supplied pages only

```json
{
    "urls": [
        "https://www.google.com",
        "https://en.wikipedia.org/wiki/Web_scraping",
        "https://example.com"
    ]
}
```

#### Example input: bounded website map

```json
{
    "urls": ["https://example.com"],
    "maxDepth": 2,
    "maxPagesPerStartUrl": 25
}
```

Crawling uses a breadth-first queue, stays on the site of each start URL, removes URL fragments, skips common asset URLs, and applies `maxPagesPerStartUrl` independently to each start URL. If the start URL redirects, the crawl continues on the site it landed on. `www.example.com` and `example.com` count as one site; other subdomains such as `docs.example.com` are not crawled. Two links that redirect to one page save and charge that page once.

#### Example input: outbound links only

```json
{
    "urls": ["https://www.python.org"],
    "linkScope": "external",
    "uniqueLinks": true
}
```

Filters only narrow the `links` list. The counters such as `totalLinks` still describe the whole page, and a crawl still follows internal links you filtered out. The price per page stays the same.

Each page row includes `statusCode`, `finalUrl`, and `contentType`. A non-2xx response or an explicitly non-HTML content type produces an empty `links` array with an actionable, non-null `error`. Network failures keep these diagnostics as `null` when no HTTP response was received. A page that was requested is charged, including a row that reports an error, because it tells you what the site answered. A line that is not a web page URL is never requested or charged, and the run's status message names it.

### Output example

One row per page. The `links` list is shortened here; this page has 31 links.

```json
{
    "url": "https://www.iana.org/help/example-domains",
    "sourceStartUrl": "https://www.iana.org/help/example-domains",
    "depth": 0,
    "statusCode": 200,
    "finalUrl": "https://www.iana.org/help/example-domains",
    "contentType": "text/html; charset=utf-8",
    "title": "Example Domains",
    "totalLinks": 31,
    "internalLinks": 27,
    "externalLinks": 4,
    "nofollowLinks": 0,
    "uniqueInternalDomains": 1,
    "uniqueExternalDomains": 2,
    "links": [
        {
            "sourceUrl": "https://www.iana.org/help/example-domains",
            "href": "https://pti.icann.org/",
            "anchorText": "Public Technical Identifiers",
            "isInternal": false,
            "isExternal": true,
            "isNofollow": false,
            "isUgc": false,
            "isSponsored": false,
            "rel": null,
            "target": null,
            "linkType": "page",
            "location": "footer"
        }
    ],
    "error": null,
    "extractedAt": "2026-11-06T11:09:35.728Z"
}
```

#### One row per link for CSV and Excel

In the run's **Output** tab, choose the **All links (one row per link)** view and export it. Through the API, add `view=links` to the dataset URL:

```
https://api.apify.com/v2/datasets/<datasetId>/items?view=links&format=csv
```

Each row has `sourceUrl`, `href`, `anchorText`, `isInternal`, `isExternal`, `isNofollow`, `isUgc`, `isSponsored`, `rel`, `target`, `linkType` and `location`. Pages with no saved links, including error rows, appear only in the default page view.

### How to extract links from web pages

1. Go to [Link Extractor](https://apify.com/automation-lab/link-extractor) on Apify Store.
2. Enter the URLs you want to extract links from in the `urls` field.
3. Click **Start** and wait for the run to finish.
4. Download your results as JSON, CSV, or Excel from the **Dataset** tab.

### How much does it cost to extract links?

Link Extractor uses pay-per-event pricing: a one-time start charge plus one event for each page processed, however many links the page has. The per-page event uses Apify tiered pricing, so higher Apify subscription tiers receive lower per-event prices.

| Event | Price | Description |
|-------|-------|-------------|
| Start | $0.035 | One-time per run |
| URL extracted | Tiered by Apify plan | Per page processed |

| Apify plan | URL extracted price |
|------------|---------------------|
| Free | $0.00115 |
| Bronze | $0.00100 |
| Silver | $0.00078 |
| Gold | $0.00060 |
| Platinum | $0.00040 |
| Diamond | $0.00028 |

**Example costs at the Bronze tier:**

- 10 URLs: $0.035 + 10 x $0.00100 = **$0.045**
- 100 URLs: $0.035 + 100 x $0.00100 = **$0.135**
- 1,000 URLs: $0.035 + 1,000 x $0.00100 = **$1.035**

From November 21, 2026 the URL extracted price will be: Free $0.00100, Bronze $0.00100, Silver $0.00090, Gold, Platinum and Diamond $0.00080. The start charge and the Bronze price stay the same.

### API usage with Link Extractor

#### Node.js

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('automation-lab/link-extractor').call({
    urls: ['https://example.com'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient('YOUR_TOKEN')
run = client.actor('automation-lab/link-extractor').call(run_input={
    'urls': ['https://example.com'],
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(f'Found {items[0]["totalLinks"]} links')
```

#### cURL

```bash
curl "https://api.apify.com/v2/acts/automation-lab~link-extractor/runs" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{"urls": ["https://example.com"]}'
```

### Use with AI agents via MCP

[Link Extractor](https://apify.com/automation-lab/link-extractor) is available as a tool for AI assistants via the [Model Context Protocol (MCP)](https://docs.apify.com/platform/integrations/mcp).

#### Setup for Claude Code

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=automation-lab/link-extractor"
```

#### Setup for Claude Desktop, Cursor, or VS Code

```json
{
    "mcpServers": {
        "apify": {
            "url": "https://mcp.apify.com?tools=automation-lab/link-extractor"
        }
    }
}
```

#### Example prompts

- "Extract all links from this webpage"
- "Get all outbound links from our homepage"
- "Map the internal links of this website, up to 200 pages"

Learn more in the [Apify MCP documentation](https://docs.apify.com/platform/integrations/mcp).

### Integrations

Link Extractor integrates with your existing workflow tools through the Apify platform. Connect it to **Make** (formerly Integromat), **Zapier**, or **n8n** to automate link extraction and feed results into SEO tools or spreadsheets. Export link data to **Google Sheets** for collaborative analysis, send alerts to **Slack** when new external links appear, or use **webhooks** to trigger downstream processing whenever a run completes.

Common integration patterns include:

- **SEO dashboard** -- schedule weekly runs and push results to Google Sheets to track link profile changes over time
- **Link monitoring** -- use webhooks to compare new results against previous runs and alert on unexpected external link additions

### Related actors

Use Link Extractor alongside these Apify actors when you need a broader website audit workflow:

- [Broken Link Checker](https://apify.com/automation-lab/sitewide-broken-link-checker) -- verify whether links on a site return errors or redirects.
- [Bulk URL Status Checker](https://apify.com/automation-lab/bulk-url-status-checker) -- check the HTTP status of the links you extracted.
- [Sitemap XML URL Extractor](https://apify.com/automation-lab/sitemap-xml-url-extractor) -- collect URL lists from XML sitemaps before extracting links from each page.
- [Website Crawler](https://apify.com/automation-lab/website-content-crawler) -- crawl page text when you need content in addition to links.

### Tips and best practices

- **Choose the right discovery mode** -- keep `maxDepth` at `0` for an exact supplied URL list, enable bounded crawling for a small same-host site map, or feed sitemap URLs when you need sitemap-controlled coverage.
- **Filter by rel attribute** -- use the `isNofollow`, `isUgc`, and `isSponsored` fields to segment links by their SEO significance.
- **Check link location** -- links in nav and footer carry different SEO weight than links in the main content body.
- **Run regularly** to track link changes over time, especially after content updates or site redesigns.
- **Export to CSV** -- use the "All links (one row per link)" view for a flat table. The default page view keeps each page's links nested in one row.

### Legality

This tool analyzes publicly accessible web content. Automated analysis of public web resources is standard practice in SEO and web development. Always respect robots.txt directives and rate limits when analyzing third-party websites. For personal data processing, ensure compliance with applicable privacy regulations.

### FAQ

**What types of links does it extract?**
It extracts all `<a>` tag hyperlinks including page links, anchor links, mailto links, and tel links. It captures the href, anchor text, rel attributes, and target attribute for each.

**Does it follow links to other pages?**
Only when you opt in. Set `maxDepth` from 1 to 5 to follow normal HTTP(S) page links on the same site in breadth-first order. `maxPagesPerStartUrl` caps the pages fetched for each supplied start URL. The default `maxDepth: 0` keeps the original independent-page behavior.

**What are `sourceStartUrl` and `depth`?**
Every page summary includes its originating supplied URL in `sourceStartUrl` and its integer crawl `depth`. Explicitly supplied pages have depth 0, which keeps provenance clear when multiple site maps are produced in one run.

**Can it detect broken links?**
No. Link Extractor reports the HTTP status of each page it actually fetches, but it does not request every extracted destination URL. Pair it with a dedicated link checker to validate all discovered links.

**What does the `location` field mean?**
The `location` field indicates where on the page the link was found -- for example, `nav`, `header`, `footer`, `sidebar`, or `body`. This helps you understand the context and SEO weight of each link.

**Some links are missing from the results. Why?**
The actor extracts links from the initial HTML response without running JavaScript. Links injected dynamically by JavaScript frameworks (React, Vue, Angular) after page load will not be captured. Most server-rendered pages and traditional CMS platforms include all links in the HTML source. For JavaScript-heavy single-page applications, consider using a browser-based scraper. The run's status message counts pages that loaded with no links in their HTML, so you can spot these.

**Why does a site return 403 or a bot page?**
Link Extractor sends plain HTTP requests with no proxy, and some large sites refuse them. It does not bypass anti-bot protection. The row's `error` field gives the status code, and a bot page shows as very few links with status 200.

**Is `docs.example.com` internal to `example.com`?**
Yes. A link is internal when it points to the page's own site or one of its subdomains, and `www` is treated as the bare domain, so `docs.example.com` is internal from both `example.com` and `www.example.com`. A link from `docs.example.com` to `example.com` is external.

**How many URLs can I process in one run?**
There is no hard limit. Up to 10 different sites are fetched at once. When crawling, each hostname is fetched one page at a time, even with several start URLs on it, so a site never gets parallel requests from one run. A plain URL list sends at most 3 requests at once to one hostname. The default run timeout is 1 hour. If a run gets close to its timeout, it stops, keeps the rows it already saved and says so in the status message, so you can raise the timeout for the rest.

**What happens with very large pages or files?**
Files that are not HTML are skipped from their headers, without downloading them, and the row's `error` field gives the Content-Type. Only the first 5 MB of a page is read. A row holds at most about 8.5 MB of links, because one dataset item cannot exceed 9 MB. The count fields still describe every link found on the part that was read, and `error` says when a list was shortened.

**Is any data sent outside Apify?**
Failed operations send sanitized diagnostic input, exceptions and Actor/build/run IDs to our private GlitchTip service so we can repair faults. Secret fields and URL queries are removed, and reports are retained for 30 days.

### Other SEO tools

- [Website Health Report](https://apify.com/automation-lab/website-health-report) -- comprehensive website health and performance audit
- [Lighthouse Website Audit](https://apify.com/automation-lab/website-lighthouse-seo-audit) -- performance and SEO scores for your pages
- [URL Redirect Chain Checker](https://apify.com/automation-lab/url-redirect-chain-checker) -- follow redirects and see every hop
- [HTTP Status Code Checker](https://apify.com/automation-lab/http-status-checker) -- check status codes for a list of URLs

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/link-extractor/changelog.md

# Actor input Schema

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

Web pages to extract all links from. A line that is not a web page URL is skipped at no charge.

## `maxDepth` (type: `integer`):

Follow page links on the same site breadth-first up to this depth, to map a website. Keep 0 to process only the supplied URLs. Redirects of the start URL are followed, and www and the bare domain count as one site.

## `maxPagesPerStartUrl` (type: `integer`):

Maximum number of pages saved for each start URL when crawling is enabled. Each saved page is one charge.

## `linkScope` (type: `string`):

Keep all links, or only those on the same site (internal) or on other sites (external). The page counters still describe the whole page, and crawling still follows internal links.

## `uniqueLinks` (type: `boolean`):

Keep only the first link to each destination URL on a page.

## `urlKeywords` (type: `array`):

Keep only links whose URL contains at least one of these words, ignoring case. For example: /blog/ or .pdf

## Actor input object example

```json
{
  "urls": [
    "https://apify.com",
    "https://crawlee.dev",
    "https://docs.apify.com/platform/actors"
  ],
  "maxDepth": 0,
  "maxPagesPerStartUrl": 10,
  "linkScope": "all",
  "uniqueLinks": false
}
```

# Actor output Schema

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

Table of processed pages, HTTP fetch diagnostics, crawl provenance, and link totals.

## `links` (type: `string`):

One flat row for each link, ready for CSV or Excel.

# 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",
        "https://docs.apify.com/platform/actors"
    ],
    "maxPagesPerStartUrl": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/link-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://apify.com",
        "https://crawlee.dev",
        "https://docs.apify.com/platform/actors",
    ],
    "maxPagesPerStartUrl": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/link-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://apify.com",
    "https://crawlee.dev",
    "https://docs.apify.com/platform/actors"
  ],
  "maxPagesPerStartUrl": 10
}' |
apify call automation-lab/link-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/link-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/Loo9IhVTCekYb01WC/builds/rZoq6cUDynWNjASo0/openapi.json
