Skip to main content
Version: Next

Crawling links

Most crawls start from a handful of URLs and grow by following links discovered on the visited pages. This guide shows how to crawl a fixed list of URLs, how to enqueue newly discovered links with the enqueue_links helper, and how to control which links end up in the RequestQueue.

Crawling a list of URLs​

The simplest case is crawling a known list of URLs. Pass the list to crawler.run and the request handler is invoked for each of them.

Run on
import asyncio

from crawlee.crawlers import BeautifulSoupCrawler, BeautifulSoupCrawlingContext


async def main() -> None:
crawler = BeautifulSoupCrawler()

# Define the default request handler, which will be called for every request.
@crawler.router.default_handler
async def request_handler(context: BeautifulSoupCrawlingContext) -> None:
context.log.info(f'Processing {context.request.url} ...')

# Run the crawler with the initial list of requests.
await crawler.run(
[
'https://crawlee.dev',
'https://apify.com',
'https://example.com',
]
)


if __name__ == '__main__':
asyncio.run(main())

To crawl a whole website, call the enqueue_links helper in the request handler. It finds the links on the current page and adds them to the RequestQueue, so the crawler systematically works through the site page by page.

tip

If no options are given, by default the method will only add links that are under the same hostname. This behavior can be controlled with the strategy option. For details, see Enqueue strategies.

Run on
import asyncio

from crawlee.crawlers import BeautifulSoupCrawler, BeautifulSoupCrawlingContext


async def main() -> None:
crawler = BeautifulSoupCrawler(
# Limit the crawl to max requests. Remove or increase it for crawling all links.
max_requests_per_crawl=10,
)

# Define the default request handler, which will be called for every request.
@crawler.router.default_handler
async def request_handler(context: BeautifulSoupCrawlingContext) -> None:
context.log.info(f'Processing {context.request.url} ...')

# Enqueue all links found on the page.
await context.enqueue_links()

# Run the crawler with the initial list of requests.
await crawler.run(['https://crawlee.dev'])


if __name__ == '__main__':
asyncio.run(main())

You'll often want to follow only links that match certain patterns. Pass the include or exclude parameters to enqueue_links - both accept lists of globs or regular expressions - and only the matching links are added to the RequestQueue. This keeps the crawl focused on the relevant sections of a website and avoids scraping unnecessary content.

Run on
import asyncio

from crawlee import Glob
from crawlee.crawlers import BeautifulSoupCrawler, BeautifulSoupCrawlingContext


async def main() -> None:
crawler = BeautifulSoupCrawler(
# Limit the crawl to max requests. Remove or increase it for crawling all links.
max_requests_per_crawl=10,
)

# Define the default request handler, which will be called for every request.
@crawler.router.default_handler
async def request_handler(context: BeautifulSoupCrawlingContext) -> None:
context.log.info(f'Processing {context.request.url} ...')

# Enqueue all the documentation links found on the page, except for the examples.
await context.enqueue_links(
include=[Glob('https://crawlee.dev/docs/**')],
exclude=[Glob('https://crawlee.dev/docs/examples')],
)

# Run the crawler with the initial list of requests.
await crawler.run(['https://crawlee.dev'])


if __name__ == '__main__':
asyncio.run(main())

enqueue_links is a convenience helper. Internally it calls extract_links to find the links and add_requests to add them to the queue. If you need custom filtering of the extracted links before enqueuing them, use extract_links and add_requests directly:

Run on
import asyncio

from crawlee import Glob
from crawlee.crawlers import BeautifulSoupCrawler, BeautifulSoupCrawlingContext


async def main() -> None:
crawler = BeautifulSoupCrawler(
# Limit the crawl to max requests. Remove or increase it for crawling all links.
max_requests_per_crawl=10,
)

# Define the default request handler, which will be called for every request.
@crawler.router.default_handler
async def request_handler(context: BeautifulSoupCrawlingContext) -> None:
context.log.info(f'Processing {context.request.url} ...')

# Extract all the documentation links found on the page, except for the examples.
extracted_links = await context.extract_links(
include=[Glob('https://crawlee.dev/docs/**')],
exclude=[Glob('https://crawlee.dev/docs/examples')],
)
# Some very custom filtering which can't be achieved by `extract_links` arguments.
max_link_length = 30
filtered_links = [
link for link in extracted_links if len(link.url) < max_link_length
]
# Add filtered links to the request queue.
await context.add_requests(filtered_links)

# Run the crawler with the initial list of requests.
await crawler.run(['https://crawlee.dev'])


if __name__ == '__main__':
asyncio.run(main())

Enqueue strategies​

enqueue_links automatically resolves relative links based on the page's context and decides which of them to follow according to the strategy option (the EnqueueStrategy type alias). Four strategies are available:

  • all - Enqueues all links found, regardless of the domain they point to. Useful when you want to follow every link, including those that navigate to external websites.
  • same-domain - Enqueues all links found that share the same domain name, including any possible subdomains.
  • same-hostname - Enqueues all links found for the exact same hostname. This is the default strategy. It restricts the crawl to links with the same hostname as the current page and excludes subdomains.
  • same-origin - Enqueues all links found that share the same origin - the same protocol, domain, and port - ensuring a strict scope for the crawl.
note

These examples use the BeautifulSoupCrawler, but the same method is available for all crawlers and works exactly the same way.

Run on
import asyncio

from crawlee.crawlers import BeautifulSoupCrawler, BeautifulSoupCrawlingContext


async def main() -> None:
crawler = BeautifulSoupCrawler(
# Limit the crawl to max requests. Remove or increase it for crawling all links.
max_requests_per_crawl=10,
)

# Define the default request handler, which will be called for every request.
@crawler.router.default_handler
async def request_handler(context: BeautifulSoupCrawlingContext) -> None:
context.log.info(f'Processing {context.request.url} ...')

# Enqueue all links found on the page. Any URLs found will be matched by
# this strategy, even if they go off the site you are currently crawling.
await context.enqueue_links(strategy='all')

# Run the crawler with the initial list of requests.
await crawler.run(['https://crawlee.dev'])


if __name__ == '__main__':
asyncio.run(main())