~ / guides / Scraping vs Crawling: Which to Pick

Scraping vs Crawling: Which to Pick

MR
Marcus Reed
Founder & lead tester · about the author
the short version
  • Crawling discovers URLs by following links across a site. Scraping extracts specific fields from a page you already have. Most real projects do both: crawl to find pages, scrape to pull data.
  • Pick by job. Need a map of what exists (every product URL, every category): crawl. Need structured values off known pages (price, title, rating): scrape.
  • I ran a 40-line Python demo: a crawler reached 3 of 4 pages by following links (the orphan page had no inbound link), then a scraper pulled title and price off each product page.
  • Tooling splits the same way. Scrapy is a crawl-first framework; BeautifulSoup is a parse-only library; a scraper API like ChocoData handles fetch, proxies, and parsing per request from $19/mo.

People use “scraping” and “crawling” as if they were one thing, then get surprised when a tool built for one is bad at the other. They are two jobs. Crawling discovers which URLs exist by following links; scraping pulls specific data out of a page you already have. This piece defines both, shows the difference in real code I ran, and lays out pricing, features, and a clear pick by scenario. The short version: most projects need both, in that order.

What is the difference between scraping and crawling?

Crawling discovers URLs by following links; scraping extracts fields from a page you already hold. A crawler starts at one or more seed URLs, fetches each page, finds the links inside it, and queues those to fetch next, building a map of what exists. A scraper takes a single page and pulls structured values out of it: a title, a price, a rating, a table. One answers “which pages are there,” the other answers “what does this page say.”

The two run in sequence in most pipelines. You crawl a category to discover 400 product URLs, then scrape each one for its fields. Search engines do the same: a crawler finds and refetches pages, then an indexing stage parses each into title, headings, and content. The web scraping pillar covers the extraction half in depth; this comparison is about when you need the discovery half too.

AxisCrawlingScraping
GoalFind which URLs existPull data from a known page
InputSeed URL(s)A specific page (URL or HTML)
OutputA list/graph of URLsStructured fields (price, title, rows)
Core operationFollow links, queue, dedupeSelect nodes, extract text/attributes
State it tracksVisited set, URL frontier, depthUsually none beyond the current page
Typical toolScrapy, a crawl frameworkBeautifulSoup, a parser, a scraper API
Fails whenIt misses pages or loops foreverA selector breaks or a page is blocked

Can you see the difference in code?

Yes. Here is a 40-line demo with no network and no dependencies: a tiny in-memory site of four pages, a crawler that discovers reachable URLs by following links, and a scraper that extracts title and price from one page. I ran this on Python 3.11.9.

from collections import deque
from html.parser import HTMLParser

# A tiny in-memory "site": 4 pages. No network involved.
SITE = {
    "/index.html":  '<a href="/p1.html">P1</a> <a href="/p2.html">P2</a>',
    "/p1.html":     '<a href="/p2.html">P2</a> <h1>Widget A</h1> <span class="price">$12.00</span>',
    "/p2.html":     '<a href="/p1.html">P1</a> <a href="/index.html">Home</a> <h1>Widget B</h1> <span class="price">$15.50</span>',
    "/orphan.html": '<h1>Unlinked</h1>',  # exists, but nothing links to it
}

class LinkParser(HTMLParser):
    def __init__(self):
        super().__init__(); self.links = []
    def handle_starttag(self, tag, attrs):
        if tag == "a":
            for k, v in attrs:
                if k == "href": self.links.append(v)

# CRAWLING: start at one URL, follow links, discover reachable pages.
def crawl(start):
    seen, order, q = set(), [], deque([start])
    while q:
        url = q.popleft()
        if url in seen or url not in SITE: continue
        seen.add(url); order.append(url)
        p = LinkParser(); p.feed(SITE[url])
        for link in p.links:
            if link not in seen: q.append(link)
    return order

The crawler is a queue, a visited set, and link-following: the three things every crawler has. The scraper is the opposite shape, one page in, fields out:

# SCRAPING: given ONE page, pull specific fields out of it.
class FieldParser(HTMLParser):
    def __init__(self):
        super().__init__(); self.cur=None; self.title=None; self.price=None
    def handle_starttag(self, tag, attrs):
        if tag == "h1": self.cur = "title"
        if tag == "span" and ("class","price") in attrs: self.cur = "price"
    def handle_data(self, data):
        if self.cur == "title": self.title = data.strip()
        if self.cur == "price": self.price = data.strip()
        self.cur = None

def scrape(url):
    p = FieldParser(); p.feed(SITE[url])
    return {"url": url, "title": p.title, "price": p.price}

Running crawl first, then scrape on each product page it found, prints:

CRAWL from /index.html (discover URLs by following links):
  1. /index.html
  2. /p1.html
  3. /p2.html
  reached 3 pages; /orphan.html not reached (no inbound link)

SCRAPE each discovered product page (extract fields):
  Widget A  | $12.00 | /p1.html
  Widget B  | $15.50 | /p2.html

Two things to notice. The crawl reached 3 of 4 pages: /orphan.html exists but no page links to it, so link-following never finds it (a real limitation of crawling, and why sitemaps exist). The scrape ignored discovery entirely and just turned each known page into {title, price}. Same site, two different jobs.

When should you crawl, scrape, or do both?

Crawl when you need a map of what exists; scrape when you need values off known pages; do both when you need data from pages you have not enumerated yet. That last case is the common one, which is why the tools blur together in people’s heads.

Your jobCrawl?Scrape?Example
Build a list of every product URLYesNoEnumerate a store’s catalog for a sitemap
Pull price/title from URLs you already haveNoYesA known list of 500 product pages
Get data from a whole site you have not mappedYesYesDiscover, then extract, every listing
Audit broken links or page coverageYesNoSite health check, no field extraction
Watch one page for changesNoYesTrack a single competitor’s price
Feed a search indexYesYesDiscover pages, then parse each to index

A quick rule: if you can already write down the exact URLs you want, you only need to scrape. If you cannot, because they live behind categories, pagination, or internal links, you need to crawl first.

How do the tools differ for scraping vs crawling?

Tools sort onto the same axis: crawl-first frameworks, parse-only libraries, and scraper APIs that do the fetch for you. Picking the wrong category is where people waste a weekend, asking a parser to crawl or hand-rolling a crawler when an API would do.

ToolCategoryCrawls (link-following)?Parses fields?Fetches pages?Best at
ScrapyCrawl frameworkYes (built-in)Yes (selectors)Yes (async engine)Whole-site crawl + extract at scale
BeautifulSoupParser libraryNo (you write the loop)YesNo (pair with requests)Parsing HTML you already fetched
requests / httpxHTTP clientNoNoYesGetting one page’s HTML
Playwright / SeleniumHeadless browserYou script itVia DOMYes (renders JS)JS-heavy pages, either job
Scraper API (ChocoData)Managed fetchPer URL you sendYes (structured JSON)Yes (proxies + JS)Fetch + unblock + parse per request

Scrapy is the one tool here that crawls out of the box: its Spider classes follow links and manage a request queue, which is exactly the crawl loop from the demo, productionized. BeautifulSoup does no crawling at all; you feed it HTML and it gives you a tree to query, so you supply the fetch and the link-following yourself. See the Scrapy guide for crawl-and-extract and the BeautifulSoup guide for parsing. Both run on the Python stack.

A scraper API sits in a different spot. You still decide which URLs to hit (so you bring your own crawl logic, or send a list), and the API handles the hard part of fetching them: rotating residential IPs, rendering JavaScript, and returning parsed JSON instead of raw HTML. It does not crawl for you, it makes each fetch reliable, which is the step that breaks at scale. That is the getting-blocked problem.

What do scraper APIs cost, and what do they include?

Documented entry prices in mid-2026 run from $19/mo (ChocoData) up to $499/mo (Bright Data Scale), and the billing unit differs by vendor, which matters more than the headline number. Read each row by what it charges for: a request, a credit, or a delivered record.

One disclosure: bestscraperapi.com earns affiliate commissions from some vendors I cover, including ChocoData below. That does not move the figures. Every price here comes from the vendor’s own pricing page in mid-2026, and I flag anything I could not verify.

VendorEntry priceFree tier (no card)Billing unitCrawl built in?Returns structured JSON?
ChocoData$19/mo (Vibe)1,000 requests/moPer request (5 credits)No (per URL)Yes (250+ endpoints)
Scrape.do$29/mo (Hobby)1,000 credits/moPer credit, success-onlyNoSome endpoints
Oxylabs$49/mo (Micro)Up to 2,000 resultsPer resultSome scrapersYes (parsed targets)
Bright Data$499/mo (Scale)5,000 records/moPer delivered recordYes (web crawler product)Yes (datasets)
Apify$29/mo (Starter)$5 usage/moPer compute + per eventYes (crawler actors)Per actor

The split to notice: ChocoData, Scrape.do, and Oxylabs price the fetch-and-parse step, so you crawl yourself and send them URLs. Bright Data and Apify also sell crawl-included products (a managed web crawler, marketplace crawler actors) that discover and extract in one product, billed per record or per compute. If you already have your URL list, pay for fetch-and-parse. If you need discovery managed too, the crawl-included products cost more and remove a step.

ChocoData’s own pricing tiers, from its pricing page in mid-2026:

PlanPriceIncluded requestsConcurrency
Free$0 (no card)1,000 requests/mo (5,000 credits)10
Vibe$19/mo27,000 requests/mo30
Pro$49/mo82,000 requests/mo50
Custom$100 to $2k/mo200,000 to 4,000,000+ requests/mo100 to 500+

One request is 5 credits, JS rendering and screenshots add 10 credits each, and pay-as-you-go top-ups list at $0.90 per 1,000 successful requests (refundable within 30 days). It exposes a universal endpoint shaped GET /api/v1/{site}/{resource} plus 453 dedicated endpoints across 235 sites, with 250+ returning validated structured JSON. It handles the fetch; you handle which URLs to send, including any you discover by crawling.

How fast and reliable are these (approximate)?

These figures are vendor self-reports compiled from each provider’s published pages in mid-2026, labeled approximate. They are not first-hand tests, and the metrics are not directly comparable (an uptime SLA is not a request success rate). My own same-target benchmark is still being built; methodology will live at how we test.

VendorSelf-reported speedSelf-reported success / uptimeSourceNote
ChocoDataMedian 2.6s, p95 6s, p99 ~10sNot publishedChocoData homepagePublishes latency percentiles, not a success rate
OxylabsNot published as one figure99.9% success rate (Amazon)Oxylabs product pageSuccess rate is target-specific
Bright DataNot published as one figure99.99% uptime SLABright Data product pageUptime, not per-request success
Scrape.doNot publishedBills only successful requestsScrape.do pricing pageSuccess-only billing, no headline number

Treat all of these as directional. Latency swings with JavaScript rendering on or off, “success” is defined differently by each vendor, and every row is a vendor measuring itself on its own targets. The only fair comparison is running them against the same pages at the same time, which is the benchmark I have not finished. Until then, the honest use of this table is sanity-checking orders of magnitude, not picking a winner by a tenth of a second.

Which should you pick? A recommendation by scenario

Pick by the job in front of you, using the rule that if you can list the URLs, you scrape, and if you cannot, you crawl first. Here is my call per scenario, tools and product included.

ScenarioMy pickWhy
Map every URL on a site (sitemap, audit)Scrapy (crawl only)Built-in link-following, queue, and dedupe; no API cost for a pure crawl
Parse HTML you already downloadedBeautifulSoupParse-only, zero crawl overhead, simplest path
Crawl + extract a whole site, self-hostedScrapyOne framework does discovery and extraction at scale
Fetch known URLs reliably, get clean JSONChocoDataPer-request fetch with proxies, JS, structured JSON, from $19/mo
JS-heavy pages, either jobPlaywrightRenders the page; script the crawl or the scrape as needed
Want discovery managed for you tooBright Data / ApifyCrawl-included products, billed per record or compute
Watch one page for changesA scraper API on a scheduleNo crawl needed; one URL, parsed fields, on a timer

My honest default for most developers: write the crawl yourself when you control the logic (Scrapy or a short link-following loop like the demo), and pay a per-request scraper API for the fetch when sites block you or render with JavaScript. That keeps discovery cheap and in your control while outsourcing the part that actually breaks. Start on a free tier (ChocoData gives 1,000 requests/mo with no card) and only move up when volume forces it. Try ChocoData if a managed fetch fits your pipeline.

One reminder for budgeting: every price here is what the vendor published in mid-2026, and scraper pricing changes often. Confirm the current figure on the vendor’s own page, and check whether a plan bills per request, per credit, or per delivered record before you compare, so you are comparing like with like.

FAQ

Is a web crawler the same as a spider?

Yes. 'Spider' is the older name for a crawler, the program that walks a site by following links. Scrapy calls its crawl classes Spiders for this reason. Both terms mean the link-following discovery program, separate from the parser that extracts fields.

Do search engines scrape or crawl?

Both, in sequence. Googlebot crawls to discover and refetch URLs, then an indexing stage parses each page to extract title, headings, links, and content. The crawl finds the pages; the parse turns them into index entries. Your own pipeline usually mirrors that split.

Can I crawl a site without scraping it?

Yes. A pure crawl that only records URLs (for a sitemap audit, a broken-link check, or coverage measurement) never extracts page fields. You still fetch each page to read its links, but you discard the body instead of parsing values out of it.

MR
Marcus Reed
I've built and run web scrapers for the better part of a decade. On this site I put scraper APIs and scraping tools through real jobs against real targets, then write up what actually holds up.