Skip to main content

Avoid getting blocked

A scraper might get blocked for numerous reasons. Let's narrow it down to the two main ones. The first is a bad or blocked IP address. You can learn about this topic in the proxy management guide. The second reason is browser fingerprints (or signatures), which we will explore more in this guide. Check the Apify Academy anti-scraping course to gain a deeper theoretical understanding of blocking and learn a few tips and tricks.

Browser fingerprint is a collection of browser attributes and significant features that can show if our browser is a bot or a real user. Moreover, most browsers have these unique features that allow the website to track the browser even within different IP addresses. This is the main reason why scrapers should change browser fingerprints while doing browser-based scraping. In return, it should significantly reduce the blocking.

Using browser fingerprints

Changing browser fingerprints can be a tedious job. Luckily, Crawlee provides this feature with minimal configuration necessary - the usage of fingerprints can be enabled in PlaywrightCrawler by using the fingerprint_generator argument of the PlaywrightCrawler.__init__. You can either pass your own implementation of FingerprintGenerator or use DefaultFingerprintGenerator.

import asyncio

from crawlee.crawlers import PlaywrightCrawler, PlaywrightCrawlingContext
from crawlee.fingerprint_suite import DefaultFingerprintGenerator


async def main() -> None:
crawler = PlaywrightCrawler(
# Fingerprint generator to be used. By default no fingerprint generation is done.
fingerprint_generator=DefaultFingerprintGenerator(),
)

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

# Find a link to the next page and enqueue it if it exists.
await context.enqueue_links(selector='.morelink')

# Run the crawler with the initial list of URLs.
await crawler.run(['https://news.ycombinator.com/'])


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

In certain cases we want to narrow down the fingerprints used - e.g. specify a certain operating system, locale or browser. This is also possible with Crawlee - the crawler can have the generation algorithm customized to reflect the particular browser version and many more. For description of fingerprint generation options please see HeaderGeneratorOptions, ScreenOptions and DefaultFingerprintGenerator.__init__ See the example bellow:

from crawlee.fingerprint_suite import (
DefaultFingerprintGenerator,
HeaderGeneratorOptions,
ScreenOptions,
)

fingerprint_generator = DefaultFingerprintGenerator(
header_options=HeaderGeneratorOptions(browsers=['chromium']),
screen_options=ScreenOptions(min_width=400),
)

If you do not want to use fingerprints, then do not pass fingerprint_generator argument to the PlaywrightCrawler.__init__. By default, fingerprints are disabled.

Using Camoufox

In some cases even PlaywrightCrawler with fingerprints is not enough. You can try using PlaywrightCrawler together with Camoufox. See the example integration below:

import asyncio

# Camoufox is external package and needs to be installed. It is not included in crawlee.
from camoufox import AsyncNewBrowser
from typing_extensions import override

from crawlee.browsers import (
BrowserPool,
PlaywrightBrowserController,
PlaywrightBrowserPlugin,
)
from crawlee.crawlers import PlaywrightCrawler, PlaywrightCrawlingContext


class CamoufoxPlugin(PlaywrightBrowserPlugin):
"""Example browser plugin that uses Camoufox browser,
but otherwise keeps the functionality of PlaywrightBrowserPlugin.
"""

@override
async def new_browser(self) -> PlaywrightBrowserController:
if not self._playwright:
raise RuntimeError('Playwright browser plugin is not initialized.')

return PlaywrightBrowserController(
browser=await AsyncNewBrowser(
self._playwright, **self._browser_launch_options
),
# Increase, if camoufox can handle it in your use case.
max_open_pages_per_browser=1,
# This turns off the crawlee header_generation. Camoufox has its own.
header_generator=None,
)


async def main() -> None:
crawler = PlaywrightCrawler(
# Limit the crawl to max requests. Remove or increase it for crawling all links.
max_requests_per_crawl=10,
# Custom browser pool. Gives users full control over browsers used by the crawler.
browser_pool=BrowserPool(plugins=[CamoufoxPlugin()]),
)

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

# Extract some data from the page using Playwright's API.
posts = await context.page.query_selector_all('.athing')
for post in posts:
# Get the HTML elements for the title and rank within each post.
title_element = await post.query_selector('.title a')

# Extract the data we want from the elements.
title = await title_element.inner_text() if title_element else None

# Push the extracted data to the default dataset.
await context.push_data({'title': title})

# Find a link to the next page and enqueue it if it exists.
await context.enqueue_links(selector='.morelink')

# Run the crawler with the initial list of URLs.
await crawler.run(['https://news.ycombinator.com/'])


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

Related links