By Rajesh Kannan, Co-Founder & CTO, Gramosoft · Last updated: · Written and tested against Scrapling 0.5 (released October 17, 2026); adaptive results measured in our own offline test on November 2, 2026

Every scraper breaks the same way: the site ships a redesign, .product-title becomes .item-name, and your script returns an empty list without an error. Scrapling is a Python library built around that problem. This tutorial takes you from install to adaptive selectors, stealth fetching and spiders on Scrapling 0.5, shows where adaptive matching stopped working in our own test, and how GcrawlAI fits in when the fetch is the part that fails.

TL;DR

  • Scrapling is three tools in one: a fast parser with adaptive selectors, HTTP and stealth-browser fetchers, and a Spider framework.
  • Adaptive in two steps: run css(selector, auto_save=True) while it works, then adaptive=True after the redesign.
  • Our test: adaptive mode saves only the first match, so call find_similar() to get the full list back.
  • It has a threshold: when classes and tags both changed, the best match scored 38.29%, under the default 40%, and nothing came back until percentage=30.
  • v0.5 broke old tutorials: css_first() is gone, there's no DynamicFetcher, and Camoufox isn't a dependency.
  • Blocked on the fetch? Let GcrawlAI fetch through residential IPs and a real browser, and keep Scrapling for parsing.

How do you do adaptive web scraping with Scrapling? Install it with pip install scrapling, select elements with page.css(selector, auto_save=True) so Scrapling stores each element's fingerprint in SQLite, and after the site changes call page.css(selector, adaptive=True) to relocate it by similarity. Add find_similar() to recover the whole list. When the fetch gets blocked, GcrawlAI fetches the HTML and Scrapling still parses it.

Scrapling 0.5 at a glance

Piece What it does Needs a browser?
Selector (parser) CSS, XPath, text and regex search, adaptive relocation, find_similar() No
Fetcher / FetcherSession HTTP requests with browser TLS impersonation (curl_cffi), HTTP/3, retries No
StealthyFetcher / StealthySession A stealth Chromium that renders JavaScript and can solve Cloudflare's challenge Yes (scrapling install)
Spider Async crawling with concurrency, follow(), pause and resume Only if a session needs one
MCP server and shell Scrapling as a tool for AI agents, and an interactive scraping shell Depends on the fetcher

Pair Scrapling with GcrawlAI for the fetch

Scrapling's adaptive parser fixes broken selectors, but it can't fix a blocked request. GcrawlAI handles the fetch: each request runs a real browser through a residential IP in the country you choose, with stealth escalation and retries built in. You get the HTML back, hand it to Scrapling's Selector, and your adaptive selectors keep working unchanged.

GcrawlAI homepage with the logo and menu (Products, Playground, Extractors, Docs, Pricing, Blog, Use Cases, Custom Request): Transform any website into structured LLM-ready data, the open-source web scraping API for AI agents, RAG pipelines and data teams, with JavaScript rendering, anti-bot stealth and proxies included in every request and no credit system; General, Extractors and Offline Bundle modes, tabs for Scrape, Map, Crawl, Batch, Screenshot, Search and Smart Answer, and the Batch tab open with a URL list, Markdown format, country picker and a Start batch scraping button
The GcrawlAI homepage doubles as a playground: here the Batch tab queues a list of URLs as one job, with rendering, anti-bot stealth and proxies included in every request (desktop screenshot taken October 31, 2026; the phone view, from October 28, shows on small screens).

The homepage says it directly: JavaScript rendering, anti-bot stealth and proxies are included in every request, with no credit system. That split suits Scrapling well. Scrapling's parser works on any HTML string, so fingerprints, find_similar() and every selector in this tutorial behave the same whether the page came from Scrapling's fetchers or from GcrawlAI.

Cycle diagram, the adaptive loop: GcrawlAI fetches the page through residential IPs and a real browser, Scrapling parses the HTML, css with auto_save stores the element fingerprint in your SQLite file, and after a redesign css with adaptive=True relocates the element and find_similar collects the rest, feeding clean data back to your code
The adaptive loop: GcrawlAI fetches, Scrapling parses and remembers.

Here's the whole pairing. The function tries the selector normally, refreshes the fingerprint when it works, and falls back to adaptive relocation with a looser threshold when a redesign breaks it:

# pip install gcrawl-sdk scrapling
"""GcrawlAI fetches the page (real browser, residential IPs, stealth); Scrapling parses it adaptively.

Scrapling's parser works on any HTML, so adaptive selectors keep working even when the fetch
happens somewhere else. Fingerprints live in your own SQLite file, next to your code.
"""
import os

from gcrawl_sdk import GcrawlClient, ScrapeOptions
from scrapling.parser import Selector

gc = GcrawlClient(api_key=os.getenv("GCRAWL_API_KEY", "gc-YOUR_API_KEY"))
STORE = "scrapling_elements.db"


def fetch_html(url, geo="US"):
    page = gc.scrape(url=url, options=ScrapeOptions(html_enabled=True, markdown_enabled=False, proxy_geo=geo), wait=True)
    return page.html or ""


def product_names(url, selector=".product-title"):
    page = Selector(fetch_html(url), url=url, adaptive=True, storage_args={"storage_file": STORE, "url": url})
    names = page.css(selector, auto_save=True)                 # normal run: match and refresh the fingerprint
    if names:
        return [n.text for n in names]
    first = page.css(selector, adaptive=True, percentage=30)   # selector broke: relocate from the fingerprint
    if not first:
        return []
    return [first[0].text] + [n.text for n in first[0].find_similar()]


if __name__ == "__main__":
    print(product_names("https://example.com/products"))
  • The fetch: residential IP rotation with country targeting, a real browser's TLS and headers, JavaScript rendering and stealth escalation, all in one request.
  • The parse: Scrapling on your side, with fingerprints in your own SQLite file, so nothing about your selectors leaves your project.
  • Scrape, crawl, screenshots and Search API: scrape to HTML, Markdown or JSON, crawl whole sites, capture screenshots, or use the Search API.
  • For developers and AI agents: a Python SDK, an MCP server for Claude, Cursor and other agents, the Playground to test a page your script gets blocked on, and the docs.

The MIT-licensed core runs on your own servers if you'd rather own the stack:

git clone https://github.com/GramosoftAI/GcrawlAI.git && cd GcrawlAI
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt && playwright install
cp .env.example .env                      # add your database, Redis and proxy settings
python -m api.core.db_setup
redis-server &
celery -A web_crawler.crawler.celery_config worker -l info &
uvicorn api.api:app --port 8000

Pricing is a free plan of 50 requests a month with no card, paid plans from $0.67 to $1.45 per 1,000 requests, and a self-hosted core that costs nothing. The plan table is in the cost section.

Where it falls short: GcrawlAI fetches public pages; it won't log in for you or get past a site that refuses every visitor, and those URLs come back as an error. Every request runs a browser through residential IPs, so plain pages that Scrapling's Fetcher already gets for free cost the same as hard ones. Use it for the pages that block you, not every page.

"Scrapling and a scraping API solve different failures. One keeps your selectors alive through a redesign; the other keeps your requests alive through a block. Most teams we talk to need both, and they shouldn't have to rewrite their parser to get the second."

Rajesh Kannan, Co-Founder & CTO at Gramosoft

What Scrapling is, and what changed in 0.5

Scrapling is an open-source Python scraping library by Karim Shoair, licensed BSD-3 and requiring Python 3.10 or later. It bundles a parser, fetchers and a crawler, and its signature feature is adaptive element tracking. Version 0.5 shipped on October 17, 2026, and several of its API changes break code from older tutorials.

The source and release notes are on Scrapling's GitHub repository. We checked every call in this guide against the installed 0.5 package, because many tutorials still show the older API:

You'll see in older tutorials In Scrapling 0.5
page.css_first(".price") Removed. Use page.css(".price").get() for text, or page.css(".price")[0] for the element
from scrapling.fetchers import DynamicFetcher Not exported. Use StealthyFetcher or StealthySession for browser rendering
StealthyFetcher "uses Camoufox" (Firefox) Camoufox isn't a dependency; the browser fetchers run Chromium through patchright and Playwright
Fingerprints "just work" with no setup They do, but the default database sits inside the installed package; point storage_args at your own file

Version 0.5 also added request recording (record_requests), saving and loading browser state, shadow DOM piercing (pierce_shadow), breadth-first or depth-first crawl strategies, and MCP server improvements for AI agents.

Install Scrapling

Scrapling installs in layers, so you only pull in what you use. The base package is the parser alone, which is all adaptive selectors need. The fetchers extra adds HTTP and browser fetching plus spiders, and one more command downloads the browser. Extras for the MCP server and shell sit on top of those.

pip install scrapling                 # parser only: Selector, css, xpath, adaptive
pip install "scrapling[fetchers]"     # + Fetcher, StealthyFetcher, sessions and spiders
scrapling install                     # downloads the browser that StealthyFetcher drives
# extras: "scrapling[ai]" adds the MCP server, "scrapling[shell]" the shell, "scrapling[all]" both

If you only parse HTML you already have, from files, a queue or GcrawlAI, stop after the first line. Skip scrapling install unless you'll use StealthyFetcher; it downloads a full browser.

Your first scrape with Fetcher

Fetcher sends plain HTTP requests, but with a browser's TLS fingerprint and headers through curl_cffi, so it passes checks that block requests. It returns a response you can query straight away with CSS or XPath. For static pages it's the fastest option Scrapling has, with no browser involved.

# pip install "scrapling[fetchers]"
from scrapling.fetchers import Fetcher

page = Fetcher.get("https://quotes.toscrape.com/", impersonate="chrome")   # browser-grade TLS and headers
print(page.status)

for quote in page.css(".quote"):
    print(quote.css(".text::text").get(), "-", quote.css(".author::text").get())

print(page.css(".tag::text").getall()[:5])        # every match as a list
print(page.css(".author::text").get())            # first match (css_first() is gone in v0.5)

Three habits to pick up early. .css() always returns a list-like Selectors object, so .get() gives the first value and .getall() gives all of them. The ::text and ::attr(href) pseudo-elements work as they do in Scrapy. And impersonate="chrome" is what makes the TLS handshake look like a browser's, so keep it on.

How adaptive selectors work

Adaptive scraping happens in two runs. On a normal run, auto_save=True stores a fingerprint of the matched element: its tag, attributes, text, path and neighbours. After a redesign breaks the selector, adaptive=True scores every element on the page against that fingerprint and returns the closest match above a percentage threshold.

  • auto_save=True: use it on every run where the selector works, so the fingerprint follows small changes over time.
  • adaptive=True: tries the selector first, and relocates from the fingerprint only when it matches nothing.
  • percentage: the minimum similarity score, 40 by default. Lower it carefully; too low and you'll match the wrong element.
  • identifier: a name for the fingerprint, so two selectors on one page don't overwrite each other.
  • storage_args: where fingerprints live. Pass {"storage_file": "...db", "url": url} so they sit in your project.

Fingerprints are keyed by the site's domain, which is why you pass url. If you don't set storage_args, Scrapling writes to a database inside its own package folder. That works on a laptop but gets wiped by a reinstall, and isn't shared between containers.

Our test: where adaptive selectors stopped working

We wanted to see adaptive mode fail, not just succeed. We saved fingerprints on version 1 of a three-product page, then ran the same selector against two redesigns: one that renamed every class, and one that also changed the tags and added a badge. Everything ran offline in Scrapling 0.5 on November 2, 2026.

Results card from our November 2, 2026 test of Scrapling 0.5 adaptive selectors on three versions of a product page: version 1 matched 3 of 3 with the original selector; redesign A (classes renamed) matched 0 with the plain selector, 1 with adaptive and 3 with find_similar; redesign B (classes and tags changed) matched nothing at the default 40% threshold because the top score was 38.29%, and 3 of 3 at percentage=30
Our test: adaptive saved the first redesign at the default threshold, and needed percentage=30 for the second.

The test script, with the three page versions inline so you can rerun it yourself:

"""Scrapling adaptive selectors, tested offline: save element fingerprints on version 1 of a page,
then find the same elements after two redesigns, with no network and no selector changes.
pip install scrapling   (the parser alone is enough for this script)
"""
import os
from scrapling.parser import Selector

URL = "https://shop.example/products"
DB = "scrapling_elements.db"                 # keep the fingerprints in your project, not in site-packages
STORE = {"storage_file": DB, "url": URL}

V1 = """<html><body><main>
  <div class="product"><h2 class="product-title">Steel Kettle</h2><span class="price">$39</span></div>
  <div class="product"><h2 class="product-title">Glass Teapot</h2><span class="price">$24</span></div>
  <div class="product"><h2 class="product-title">Pour-over Set</h2><span class="price">$31</span></div>
</main></body></html>"""

# Redesign A: every class renamed, one extra wrapper div
V2 = """<html><body><main><section class="grid">
  <div class="card"><h2 class="item-name">Steel Kettle</h2><span class="cost">$39</span></div>
  <div class="card"><h2 class="item-name">Glass Teapot</h2><span class="cost">$24</span></div>
  <div class="card"><h2 class="item-name">Pour-over Set</h2><span class="cost">$31</span></div>
</section></main></body></html>"""

# Redesign B: classes renamed, tags changed (h2 -> h3, span -> p) and a badge added before the title
V3 = """<html><body><main><ul class="list">
  <li class="tile"><em>New</em><h3 class="name">Steel Kettle</h3><p class="amount">$39</p></li>
  <li class="tile"><em>New</em><h3 class="name">Glass Teapot</h3><p class="amount">$24</p></li>
  <li class="tile"><em>New</em><h3 class="name">Pour-over Set</h3><p class="amount">$31</p></li>
</ul></main></body></html>"""


def page(html):
    return Selector(html, url=URL, adaptive=True, storage_args=STORE)


def all_items(first):
    """Adaptive mode relocates the element whose fingerprint was saved (the first match);
    find_similar() then collects its structural siblings."""
    return [first.text] + [x.text for x in first.find_similar()] if first else []


if __name__ == "__main__":
    if os.path.exists(DB):
        os.remove(DB)

    # Run 1: the selectors work, and auto_save stores the first match's fingerprint in SQLite
    v1 = page(V1)
    titles = v1.css(".product-title", auto_save=True)
    prices = v1.css(".price", auto_save=True)
    print("v1 titles:", [t.text for t in titles], "| prices:", [p.text for p in prices])

    for label, html in (("Redesign A: classes renamed", V2), ("Redesign B: classes and tags changed", V3)):
        p = page(html)
        print(f"\n{label}")
        print("  plain .product-title     :", [x.text for x in p.css(".product-title")])
        print("  adaptive (default 40%)   :", [x.text for x in p.css(".product-title", adaptive=True)])
        for pct in (40, 30):
            hit = p.css(".product-title", adaptive=True, percentage=pct)
            print(f"  adaptive + find_similar ({pct}%):", all_items(hit[0]) if hit else "no match")

And the output, unedited:

v1 titles: ['Steel Kettle', 'Glass Teapot', 'Pour-over Set'] | prices: ['$39', '$24', '$31']

Redesign A: classes renamed
  plain .product-title     : []
  adaptive (default 40%)   : ['Steel Kettle']
  adaptive + find_similar (40%): ['Steel Kettle', 'Glass Teapot', 'Pour-over Set']
  adaptive + find_similar (30%): ['Steel Kettle', 'Glass Teapot', 'Pour-over Set']

Redesign B: classes and tags changed
  plain .product-title     : []
WARNING: Adaptive relocation found no element above the 40% threshold (top score: 38.29%). Lower `percentage` if this is the right element.
  adaptive (default 40%)   : []
WARNING: Adaptive relocation found no element above the 40% threshold (top score: 38.29%). Lower `percentage` if this is the right element.
  adaptive + find_similar (40%): no match
  adaptive + find_similar (30%): ['Steel Kettle', 'Glass Teapot', 'Pour-over Set']

Two findings matter for real projects. First, adaptive mode found only Steel Kettle, because auto_save stores the first match's fingerprint, not one per element. find_similar() on that element brought back all three. Second, redesign B scored 38.29%, just under the default threshold, so the default returned nothing. Scrapling did log a warning with the top score, which tells you exactly how far to lower percentage.

"The warning with the top score is the most useful line Scrapling prints. Log it, alert on it, and treat any relocation under 40% as something a person should check before the data ships."

Rajesh Kannan, Co-Founder & CTO at Gramosoft

Find elements without selectors

Sometimes you don't have a working selector at all, only a value you can see on the page. Scrapling can search by visible text or by a regular expression, and find_similar() turns one found element into the whole list of elements built the same way. Together they make a quick way to bootstrap a scraper.

# No selector at all: find by visible text, then expand to every element built the same way
from adaptive_demo import V3, page

p = page(V3)
kettle = p.find_by_text("Steel Kettle")                      # exact text, first match
print(kettle.tag, dict(kettle.attrib))
print([x.text for x in kettle.find_similar()])                # the other product names
print([x.text for x in p.find_by_regex(r"^\$\d+$", first_match=False)])   # every price
h3 {'class': 'name'}
['Glass Teapot', 'Pour-over Set']
['$39', '$24', '$31']

find_similar() compares structure, not text: same tag, same depth, similar parent. By default it ignores href and src so links to different products still count as similar, and similarity_threshold (0.2 by default) controls how strict the comparison is.

Render JavaScript with StealthyFetcher

StealthyFetcher launches a patched Chromium, renders the page, and returns the same response object as Fetcher, so your selectors don't change. Use it for pages that build content with JavaScript or run a browser check. Wrap many pages in StealthySession so one browser stays open and cookies carry between requests.

# pip install "scrapling[fetchers]" && scrapling install
from scrapling.fetchers import StealthyFetcher, StealthySession

# One-off: launch a stealth Chromium, render JavaScript, wait for the network to settle
page = StealthyFetcher.fetch("https://quotes.toscrape.com/js/", headless=True, network_idle=True, block_ads=True)
print(len(page.css(".quote")), "quotes rendered by JavaScript")

# Many pages: keep one browser open and reuse it (cookies and state carry over)
with StealthySession(headless=True, solve_cloudflare=True, block_ads=True) as session:
    for n in (1, 2):
        page = session.fetch(f"https://quotes.toscrape.com/js/page/{n}/", network_idle=True)
        print(n, page.css(".quote .text::text").getall()[:1])
  • network_idle=True: waits until network activity stops, which is usually when rendered content has arrived.
  • wait_selector: waits for a specific element instead, faster when you know what you need.
  • solve_cloudflare=True: attempts Cloudflare's interstitial and Turnstile challenge on session requests.
  • block_ads=True: skips ad and tracker requests, which speeds up rendering.
  • proxy or proxy_rotator: routes the browser through your own proxies; Scrapling doesn't include any.

A stealth browser fixes the browser signals, but your IP is still yours. From a cloud server, a datacenter IP is often enough to get challenged before any fingerprint is read. That's the point where a residential proxy pool or GcrawlAI earns its cost.

Crawl a site with a Spider

For more than a handful of pages, Scrapling's Spider class gives you a Scrapy-style crawler with async requests underneath. You define start URLs and a parse method that yields items and follow-up requests. Set crawldir and the crawl saves its state, so you can stop it and resume later.

# pip install "scrapling[fetchers]"
from scrapling.spiders import Spider, Response


class QuotesSpider(Spider):
    name = "quotes"
    start_urls = ["https://quotes.toscrape.com/"]
    concurrent_requests = 4                         # stay polite
    robots_txt_obey = True                          # off by default; honour robots.txt

    async def parse(self, response: Response):
        for quote in response.css(".quote"):
            yield {"text": quote.css(".text::text").get(), "author": quote.css(".author::text").get()}
        next_link = response.css(".next a::attr(href)").get()
        if next_link:
            yield response.follow(next_link)


result = QuotesSpider(crawldir="./crawl_state").start()   # Ctrl+C pauses; rerun to resume
print(len(result.items), "quotes")
result.items.to_json("quotes.json")

concurrent_requests caps parallel requests, robots_txt_obey (off by default) skips URLs the site's robots.txt disallows, and response.follow() resolves relative links for you. Items come back on result.items with a to_json() export. A spider can also send different URLs to different sessions, for example cheap HTTP requests for listing pages and a stealth browser only for the detail pages that need one.

Scrapling cheat sheet

Every call this tutorial uses fits on one page. The groups follow the order you'll reach for them: install, fetch, select, adapt and crawl. Keep it next to your editor while you write your first spider, and check any call you copy from an older tutorial against the 0.5 changes table above before you rely on it.

Periodic-table style cheat sheet of 21 Scrapling 0.5 building blocks grouped by family: install commands, fetchers (Fetcher, FetcherSession, StealthyFetcher, StealthySession, AsyncFetcher), selection (css, xpath, get, getall, find_by_text, find_by_regex), adaptive (auto_save, adaptive, percentage, storage_args, find_similar, relocate) and crawling (Spider, follow, crawldir)
Scrapling 0.5 on one page: the 21 calls this tutorial uses.

What blocked pages cost to fetch

Scrapling itself is free. The cost appears when its own fetchers get blocked and you add residential proxies, browser servers or a scraping API. For 100,000 successful protected pages a month, credit-based APIs cost $249 to $399 and GcrawlAI costs $101; for plain pages, they're cheaper.

Option, 100,000 successful pages a month Plain HTML pages Protected pages (JS rendering + premium proxies) Are failed requests billed?
GcrawlAI (every request rendered, residential IPs included) $101 (Growth $79 + 20,000 overage at $1.10 per 1,000) $101 (same request, no multiplier) No: failed requests are never charged
ScraperAPI $49 (Hobby, 100,000 credits) $299 (Business; 25 credits per page = 2.5M credits) Charges only 200 and 404 responses
ZenRows $39 (Build B3, 120,000 credits) $399 (Growth G3; 25 credits per page = 2.5M credits) Failed requests never charged; 404 and 410 count as successful
ScrapingBee $49 (Freelance, 250,000 credits; JS off) $249 (Business; 25 credits per page = 2.5M credits) Charges only 200, 404 and 410

List-price arithmetic, not a benchmark. Credit costs and billing rules come from ScraperAPI's credit docs, ZenRows' pricing docs and ScrapingBee's FAQ, checked November 1, 2026. The honest read: for plain HTML, Scrapling's free Fetcher beats all of them, and ZenRows, ScraperAPI and ScrapingBee cost less than GcrawlAI. GcrawlAI wins on protected pages, because rendering, residential IPs and stealth are already in the one request.

GcrawlAI plans (GcrawlAI pricing, checked October 28, 2026):

Plan Price Requests/month Concurrent Per 1,000 Overage per 1,000
Free $0 forever, no card 50 2 – –
Starter $29/month 20,000 10 $1.45 $1.60
Growth (most popular) $79/month 80,000 35 $0.99 $1.10
Pro $199/month 250,000 50 $0.80 $0.90
Scale $499/month 750,000 100 $0.67 $0.75
Enterprise Volume pricing 2M+ Custom – –
Self-hosted $0 (MIT) Your servers Your servers Infrastructure only –

Every plan, Free included, has scrape, crawl, the Search API, JavaScript rendering, residential IP rotation and stealth escalation in each request, and each feature counts as one request. Failed requests are never charged. Annual billing charges 10 months for 12, unused requests on paid plans roll over once, and Indian customers are billed in INR with a GST invoice.

Which setup to choose

Match the setup to what actually breaks. If selectors break, Scrapling's adaptive parser is the fix and costs nothing. If requests get blocked, the fix is in the fetch, and that's where GcrawlAI fits. Most real projects mix both: Scrapling everywhere, and a managed fetch only for the hard domains.

Your situation Scrapling setup Where GcrawlAI fits
Static pages, no blocking Fetcher + auto_save Not needed; keep GcrawlAI for the domains that start blocking
JavaScript-rendered pages StealthyFetcher or StealthySession GcrawlAI renders without running browsers yourself
Blocked from a cloud server Your own residential proxies via proxy_rotator GcrawlAI includes residential IPs in every request
Site redesigns often adaptive=True + find_similar(), with the score logged Fetch with GcrawlAI, parse adaptively with Scrapling
Large crawls Spider with crawldir GcrawlAI crawl jobs, or route hard URLs through it
AI agents need web data scrapling[ai] MCP server GcrawlAI MCP server with scrape, crawl and Search API

When Scrapling on its own is enough

Plenty of sites need nothing more than Scrapling. If its Fetcher gets clean responses and your selectors only break on the occasional redesign, adaptive mode and a logged similarity score will carry you a long way for free. Bring in GcrawlAI when blocks, not selectors, become the daily problem.

Even then you can stay on your own servers: the MIT-licensed GcrawlAI core self-hosts for free, and the free Cloud plan's 50 requests a month are enough to test the pages your scripts get blocked on. Paste one into the Playground to compare. For the build-or-buy trade-off, read web scraping with Python vs a scraping API.

Scrape responsibly with Scrapling

Adaptive selectors and a stealth browser make Scrapling powerful, and that power comes with obligations. A login wall, a terms-of-service ban, personal data or a robots.txt disallow is the site saying no, and looking like a browser doesn't change that. Keep concurrency low, cache what you've fetched, and stop when asked.

Scrapling's spiders only honour robots.txt when you set robots_txt_obey = True; in our local test it skipped the disallowed page and counted it in the crawl stats. Turn it on, and set polite limits yourself. For the legal background, read what web scraping is and where it's legal, and get advice for your own case.

Frequently asked questions

What is Scrapling used for?

Scraping websites in Python with less maintenance. It parses HTML with CSS, XPath, text and regex search, fetches pages over HTTP or through a stealth browser, crawls with spiders, and relocates elements after a redesign with adaptive selectors.

How do Scrapling adaptive selectors work?

Run css(selector, auto_save=True) while the selector works, and Scrapling stores a fingerprint of the element in SQLite. After a redesign, css(selector, adaptive=True) scores elements against that fingerprint and returns the best match above a threshold, 40% by default.

Why does adaptive mode return only one element?

In our test, auto_save stored only the first match's fingerprint, so adaptive relocation returned that one element. Call find_similar() on it to get the rest of the list back.

What replaced css_first in Scrapling 0.5?

css_first() no longer exists. Use page.css(selector).get() for the first text or HTML value, or page.css(selector)[0] when you need the element itself.

Is Scrapling better than BeautifulSoup or Scrapy?

It covers more ground than BeautifulSoup, adding fetching, crawling and adaptive selectors, and its parser is fast. Scrapy is more mature for very large crawls and has a bigger plugin ecosystem. For new scrapers that must survive redesigns, Scrapling is a strong default.

Can Scrapling bypass Cloudflare?

StealthySession has a solve_cloudflare option for Cloudflare's interstitial and Turnstile challenge. It doesn't change your IP, so a datacenter address can still be challenged or blocked. Residential proxies or GcrawlAI cover that part.

Is GcrawlAI open source, and can I use it commercially?

Yes. The GcrawlAI core is MIT licensed on GitHub, so you can self-host it and use it commercially. The managed Cloud API adds residential IPs and stealth escalation, with a Python SDK and MCP for AI agents, a free plan of 50 requests a month and paid plans from $0.67 to $1.45 per 1,000 requests.

RK

Rajesh Kannan M

Rajesh Kannan M is Co-Founder & CTO of Gramosoft in Chennai, where he leads the team building GcrawlAI, an open-source web scraping, crawling and Search API platform for developers and AI agents. LinkedIn