HasData
Back to all posts

Scraper vs Crawler and When to Use Each

A crawler discovers pages by following links, and a scraper extracts fields from pages it already knows. Use a crawler when the URL list is the thing you lack, and a scraper when you have the list and want the data. Most production pipelines run both in sequence, but on sites that publish a sitemap the crawl stage often buys nothing. I measured both approaches on eight sites, and on the crawled route between 31% and 95% of the requests brought back pages the task never needed.

The Short Answer

The job decides the tool, and most jobs fall into a handful of shapes.

The jobWhat to useWhy
Prices from known product pagesScraperThe URL list already exists
Every article a site publishedCrawler, or its sitemapDiscovery is the job
One listing page followed dailyScraperSame URL every day
Broken links across a siteCrawlerOnly a full traversal proves absence
A dataset from ten known sourcesScraper per sourceTen lists, no discovery
Search-style index of a domainCrawlerCoverage is the point
New listings as they appearCrawler on the listing section, scraper on the itemsDiscovery finds them, extraction reads them

The two halves of the last row are the standard pipeline. A discovery pass keeps the URL frontier fresh, an extraction pass turns each URL into rows, and the two run on different schedules, since links change more often than page layouts.

How a Crawler Works

A web crawler is a loop around a queue. It starts from seed URLs, fetches a page, collects that page’s links, adds the ones it has not seen to the queue, and repeats until the queue empties or a limit stops it. The whole mechanism fits in a screen of Python, shown here against a demo bookshop that exists to be crawled:

from collections import deque
from urllib.parse import urljoin, urlparse

import requests
from bs4 import BeautifulSoup


def crawl(start, cap=50):
    host = urlparse(start).netloc
    seen, queue, pages = {start}, deque([start]), []
    while queue and len(pages) < cap:
        url = queue.popleft()                     # popleft makes it breadth-first
        try:
            response = requests.get(url, timeout=15)
        except requests.RequestException:
            continue
        pages.append(url)
        for a in BeautifulSoup(response.text, "html.parser").find_all("a", href=True):
            link = urljoin(url, a["href"]).split("#")[0]
            if urlparse(link).netloc == host and link not in seen:
                seen.add(link)                    # deduplicate before queueing
                queue.append(link)
    return pages


pages = crawl("https://books.toscrape.com/", cap=50)
print(len(pages), "pages fetched,", pages[1])

Run against a demo bookshop, this fetches 50 pages and stops.

The queue is the URL frontier, the set of discovered-but-unvisited pages. The seen set is deduplication, and it runs before queueing rather than before fetching, so the same URL never even enters the frontier twice. popleft makes the traversal breadth-first, spreading across sections level by level, while pop from the same deque would make it depth-first, chasing one chain of links to its end. And the seed list decides everything downstream, since the crawler can only reach what links connect to its starting points.

Flowchart describing the process of web crawling

Flowchart describing the process of web crawling

Production crawlers grow from exactly this loop. The frontier moves to disk so a crash resumes instead of restarting, deduplication becomes a Bloom filter when the URL set outgrows memory, and politeness delays and robots.txt checks wrap the fetch, which is the loop Scrapy maintains for you at framework scale. Web crawling in Python covers that growth path with measurements, including a queue on SQLite and a four-tool speed comparison.

Search engines run this exact loop continuously. Googlebot starts from known pages and sitemaps, and its frontier is fed by every link on every page it has already seen.

How a Scraper Works

A web scraper starts where the crawler ends, with URLs already in hand. It fetches each one, extracts named fields from the markup with selectors or from JSON embedded in the page, and writes rows. The output is typed data (a price, a title, a date) rather than pages.

Flowchart describing the process of web scraping

Flowchart describing the process of web scraping

The working steps stay the same across targets. Define which fields the task needs, fetch the page with headers a site expects from a browser, extract the fields, and save them in a usable format. Blocking is the main operational difference from crawling a friendly site. Pages worth scraping tend to defend themselves, so proxies, realistic headers and rate limits show up early in any real setup. All pages of one site usually share a layout, so one set of selectors covers thousands of URLs.

What Each Costs

A crawl spent between 31% and 95% of its requests on pages the task never needed, while the sitemap route paid about one request per useful page. Those numbers come from giving eight sites one task each, collecting every page of one type (blog posts, release pages, docs), and solving it twice. The first route is a breadth-first crawl from the homepage capped at 400 requests. The second reads the site’s sitemap.xml and fetches only the URLs matching the target pattern.

The crawl side counts every request the traversal makes, and the sitemap side counts the sitemap reads plus one request per matching page, capped at 400 fetches either way.

SiteCrawl requestsUseful pages in the crawlSitemap-route requestsUseful pages by sitemapCrawl waste
nodejs.org400 (cap)21401400 of 1,621 listed95%
webflow.com400 (cap)21407400 of 1,459 listed95%
go.dev400 (cap)29no usable sitemapnone93%
astro.build400 (cap)42no usable sitemapnone90%
playwright.dev400 (cap)747574 of 74 listed82%
hasdata.com331135122121 of 121 listed59%
python.org400 (cap)218no usable sitemapnone46%
djangoproject.com400 (cap)277rate-limited, see below1931%

The waste column carries the whole comparison in one number. On nodejs.org and webflow.com, 400 crawl requests surfaced 21 useful pages each, while their sitemaps listed 1,621 and 1,459 targets and delivered 400 of them inside the same request budget, roughly nineteen times more useful pages per request. On playwright.dev both routes surfaced 74 docs pages each, and the sitemap route did it in 75 requests against the crawl’s 400. On hasdata.com the sitemap route delivered 121 of the crawl’s 135 pages for just over a third of the requests.

Grouped bar chart of requests spent per useful page on four sites, crawl against the sitemap route, with the crawl costing between 2.5 and 19 requests per page and the sitemap route close to one everywhere

Three of the eight sites had no sitemap a script could use, and there the crawl stops being waste and becomes the only route, at 46% to 93% overhead that is simply the price of discovery. Check for the sitemap first, and crawl when there is none.

The run also surfaced costs that live outside the request counts. The go.dev crawl downloaded 11.7 GB in 400 requests, because a crawler follows whatever it finds and that site links installer archives from regular pages, so a real crawler needs content-type and size guards before the body download, on top of the URL filters. And on djangoproject.com the targeted phase ran right after the 400-request crawl from the same address and got rate-limited down to 19 successful fetches, although the same URLs answer 200 in isolation. Requests spent on discovery count against the same budget the extraction needs later.

When to Crawl and When to Scrape

Crawl when the URL list genuinely does not exist and cannot be derived, and scrape whenever the list exists or a sitemap provides it. The measured costs above are why that rule holds, and the table below is the same rule spread over the properties that matter.

QuestionCrawlerScraper
What it producesURLs and raw pagesTyped fields from each page
What it needs to startSeed URLsThe full URL list
Requests per useful pageMany, most of the budget goes to discoveryAbout one
Parsing involvedLinks onlyThe fields the task needs
ScopeWhole sites or sectionsKnown pages
When it winsThe list does not exist and no sitemap covers itThe list exists or a sitemap provides it

A sitemap, a category listing, or a search results page each replace a crawl with a far shorter fetch list, and finding all URLs on a domain compares those discovery routes directly.

Use Cases

Crawling earns its requests where coverage itself is the deliverable. Search indexing is the canonical case, and the same traversal logic drives site audits (broken links, duplicate meta tags, orphan pages), SEO monitoring of a competitor’s whole site, and research corpora built from many domains. Each of these needs pages the operator cannot enumerate in advance.

Scraping owns every task with a known target. Price monitoring on product pages, job aggregation from listing feeds, content aggregation from named sources, review collection, lead lists and analysis of pages no official API exposes all fit that shape. The common thread is a URL list plus a field list, and the practical work lives in extraction quality and block avoidance rather than discovery.

Challenges of Web Crawling and Web Scraping

When it comes to web crawling and web scraping, some challenges need to be considered. Depending on the size of the project, these could range from simple technical issues such as slow loading times or blocked requests (due to anti-scraping measures) all the way up to complex legal questions regarding data privacy laws.

Blocking crawls in robots.txt

Before crawling a resource, ensure the site allows it. If the robots.txt file states that the resource prohibits the use of data from any page, it is worth being polite and complying with the terms.

IP blocking

While crawling, remember that a person cannot click on links every millisecond. Internet resources consider such actions suspicious and may block the IP from which such actions are performed. Therefore it makes sense to make at least a short delay between requests and use proxies that hide your real IP. But you can’t use the same proxy indefinitely either, so for crawling, just like for scraping, you need to use several proxies (proxy pool) and keep changing them between each other.

To ensure successful crawling, you should also check your bot’s settings. Specify headers and user agents manually, as this will help the site recognize that a real user is interacting with the site. The good idea is to use real user agents and headers, such as your browser headers.

CAPTCHAs

Websites use captchas to tell bots and humans apart, so following all the above guidelines helps you avoid triggering them in the first place. If a site still returns them, reduce your request volume, respect its rate limits, and use an official API where one is available. HasData’s Web Scraping API runs the proxy pool and rendering on the service side and returns the parsed data.

Spider trap

Some resources leave crawler traps called Honeypots. These are additional hidden links in the code that are not visible to regular users in the browser. And if a crawler follows this link, the resource detects it is a bot and blocks it.

Overcrawling

Sometimes bot can get stuck in an infinite loop if not programmed properly or crawl too much and overwhelm the target website with excessive requests, thus taking away resources from other users who may be trying to access it at that time.

Web Crawling and Web Scraping Best Practices

When crawling and scraping, there are some rules to follow that will make crawling easier for both you and the websites.

Be polite

If you have the option, crawl pages that the resource has allowed in robots.txt and limit the frequency of requests. Otherwise, a heavy load on a resource can cause it to respond to all users extremely slowly or even fail.

Crawl at low-use hours

You should also crawl when there is less pressure on resources, for example, at night. This will allow you to get the data you need while causing the least damage to the resource.

Use caching strategies

Implement caching strategies to reduce the number of requests made on each page and store previously crawled data for future reference points when deciding what content needs to be scraped next.

Conclusion

Ask what you are missing. Missing the URLs means discovery, and discovery means a crawler or, cheaper, a sitemap. Missing the data from URLs you can already name means a scraper. The measurement above puts numbers on that choice, and the numbers lean one way: on sites with a sitemap, the targeted route did the same job for a fraction of the requests, so a crawl is worth running only when nothing else can produce the list.

Sergey Ermakovich
Sergey Ermakovich
Sergey is the Co-founder and CMO at HasData, a web scraping API handling billions of requests. He specializes in web data extraction infrastructure, large-scale scraping reliability, and technical SEO. Sergey writes extensively on headless browser orchestration, API development, and scaling data pipelines for enterprise applications.
Articles

Might Be Interesting