Skip to content
Scraping API · Use case

Extract every link and email address from a web page

To extract all links from a website page you need absolute URLs, not a mix of relative paths, anchors and javascript: handlers. Lead enrichment, link audits, partner directories, crawl seeds and contact pages all start with the same two lists. The Scraping API returns them with one method each, already cleaned.

The problem

Raw hrefs and mailto links need cleaning before they are data

A page’s anchors are a mess of relative paths, fragment links, duplicates, tracking redirects, mailto: and javascript: pseudo-links. Before you can store or follow them, each one needs resolving against the page URL, validating and deduplicating.

Email addresses are worse: some sit in mailto links, others only in plain text in a footer or a contact paragraph. A regular expression over the HTML catches addresses inside scripts and misses the ones behind a mailto prefix, and a plain fetch sees nothing at all on contact pages rendered in the browser.

The links method sweeps every anchor and returns absolute, deduplicated URLs, dropping mailto, javascript and empty hrefs. The emails method scans mailto links and plain text and returns bare addresses with the mailto prefix stripped. Both accept a selector to scope the sweep and the shared options to render first.

How it works

How to get all URLs and emails from a page

Each method is one request that resolves to an array of strings. Scope it, render it, or drop down to a rule when you need more than the URL.

1 · Every link and every email
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const url = 'https://example.com/contact'

const [links, emails] = await Promise.all([
  microlink.links(url),
  microlink.emails(url)
])

links resolves to absolute, deduplicated URLs and emails to addresses such as [email protected]. The two calls run in parallel and each is cached on its own.

2 · Scope to a part of the page
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const nav = await microlink.links('https://example.com', {
  selectorAll: 'nav a'
})

const footer = await microlink.emails('https://example.com', {
  selector: 'footer'
})

const { hostname } = new URL('https://example.com')
const external = nav.filter(link => new URL(link).hostname !== hostname)

selectorAll narrows links to the navigation and selector narrows emails to the footer. Filtering by hostname on your side splits internal from external links.

3 · Link text next to each URL
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const { anchors } = await microlink.extract('https://example.com', {
  anchors: {
    selectorAll: 'main a',
    attr: {
      text: { selector: ':scope', attr: 'text' },
      href: { selector: ':scope', attr: 'href', type: 'url' }
    }
  }
})

links returns URLs only. A nested extract rule turns each anchor into { text, href }; the :scope selector points both child rules at the anchor itself. That is what a link audit or a sitemap check usually needs.

Parameters used
  • links Every anchor as an absolute, deduplicated URL. mailto, javascript and empty hrefs are dropped.
  • emails Addresses from mailto links and plain text, as bare strings.
  • selectorAll Overrides the default a selector of links to scope the sweep.
  • type url resolves and validates links, email finds addresses. Keep them when changing the scope.
  • prerender Forces a browser for contact pages and menus rendered on the client.

Both methods read one page per call; they do not crawl. To list every page a site wants indexed, the sitemap tool reads its sitemap.xml, and you can run links() or emails() on each URL it returns.

Why it works

Why dedicated link and email methods beat a regex over HTML

Links and emails look trivial until you store them. The methods do the resolving, validating and deduplicating that every hand-rolled extractor ends up reimplementing.

01 · Clean URLs
Absolute, deduplicated, followable.
Relative hrefs are resolved against the page URL, and values that do not parse as a URL are dropped by the url type. What you get back can go straight into a queue or a database.

Under the hood links is a rule of selectorAll a, attr href and type url, so you can override any part of it or write the rule yourself in a website to JSON schema.

02 · Both sources
Emails from mailto links and from plain text.
The email type scans the whole document, so an address printed in a paragraph is found as well as one inside a mailto link, and the prefix is stripped from both.

Contact pages rendered by JavaScript need prerender: true and a waitForSelector; scraping JavaScript-rendered pages explains the waits.

03 · Composable
The same facets feed an LLM or a CRM.
Links and emails are two of the facets that make a page useful to an agent or an enrichment job, next to the body text and the metadata. Each one is a separate, cached request.

When not to: obfuscated addresses such as name [at] domain [dot] com, emails in images or links assembled by JavaScript are not detected. Parse those yourself with npm packages in a remote function.

FAQ

Call microlink.links(url). It sweeps every anchor on the page and resolves to an array of absolute, deduplicated URLs. Pass selectorAll to limit it to a region such as nav a or main a.

Does the email extractor API find obfuscated addresses?

No. It finds addresses in mailto links and in plain text. Addresses written as name [at] domain, rendered as images or assembled by JavaScript are not detected; handle those with your own logic in a function.

Can I get the anchor text along with each extracted URL?

Yes, with extract. A rule with selectorAll a and an object under attr containing text and href rules, each with selector :scope, returns one { text, href } object per link, with href validated as a URL.
No. Each call reads one page. To cover a site, get its URL list from the sitemap, or feed the internal links you extracted back into your own queue, and call links() once per page.
Yes. Run links(), emails() and markdown() on the same URL in parallel and combine the results. Turn any URL into LLM context shows that pattern with metadata and technologies added.
Related use cases

Solve the next problem with the same API

LLM context from any URL

Compose Markdown, links, emails, metadata and tech stack from one URL into a context object for your agent.

Any website to JSON

Declare the fields you want as CSS selector rules and get typed JSON back, with null for anything the page does not have.

Tables and lists to JSON arrays

Turn table rows, product grids and search results into an array of objects, one nested rule per column.

Custom fields from JavaScript apps

Render React, Vue or Angular apps in a real browser, wait for the element you need, then run your rules.

JavaScript with npm packages

Require any npm package inside a remote function, pin its version, and skip the browser entirely when the code does not need one.

Only the fields you need

Include or exclude normalized fields per request and trim the JSON for faster, lighter metadata calls.

Ready to extract every link and email?

Absolute URLs and bare email addresses from any page, one method each. Start on the free tier and try it on your own site.