Author: Xavier Fok

  • Firecrawl vs Crawl4AI vs Jina Reader: Which LLM Scraping Tool in 2026?

    firecrawl vs crawl4ai vs jina reader: which llm scraping tool in 2026?

    firecrawl is a hosted scraping api that returns clean markdown with no infra. crawl4ai is a self-hosted python library that does the same job locally with a real chromium browser. jina reader is a free public endpoint that converts any url to llm-ready text via simple prefix. firecrawl wins on speed-to-production. crawl4ai wins on cost at scale. jina reader wins on simplicity for prototypes.

    three tools, three different bets on what an llm scraping stack should look like. they all ship today, they all output clean markdown, and they all integrate with langchain and llamaindex out of the box. but the moment you push past a thousand urls, the cost and reliability tradeoffs diverge fast. this comparison breaks down where each one fits.

    the short version

    tool type starting cost (2026) best for
    firecrawl hosted api $19/month, 3,000 credits teams shipping rag fast, no infra
    crawl4ai self-hosted python free (mit license) high-volume scraping, control over browser
    jina reader hosted api free, with paid tiers from $20/month prototyping, single-url fetches

    if you’re building an mvp this week, jina reader. if you’re shipping a product to production this month, firecrawl. if you’re scraping six figures of urls a month, crawl4ai self-hosted.

    what each tool actually is

    firecrawl, by mendable.ai, is a hosted api. you send a post request with a url, you get markdown back. it handles javascript rendering, anti-bot evasion, and rate limiting on its end. the firecrawl pricing page lists tiers from free (500 credits) up to enterprise. each url with javascript counts as five credits, plain html as one.

    crawl4ai is an open-source python library. apache 2.0, hosted at github.com/unclecode/crawl4ai. you run it on your own machine, your own server, your own kubernetes cluster. it ships with playwright internally and outputs llm-friendly markdown by default. for a step-by-step walkthrough, see the crawl4ai tutorial.

    jina reader is the simplest of the three. you prepend https://r.jina.ai/ to any url and you get back the rendered content as plain text. there’s a python sdk and a paid tier with higher limits, but the core endpoint works without an api key.

    installation and time-to-first-scrape

    speed of setup matters when you’re evaluating tools. here’s what each looks like cold.

    firecrawl:

    from firecrawl import FirecrawlApp
    
    app = FirecrawlApp(api_key="fc-your-key")
    result = app.scrape_url("https://example.com")
    print(result["markdown"])
    

    sign up, paste a key, three lines of code. about 90 seconds end-to-end.

    crawl4ai:

    pip install -U crawl4ai
    crawl4ai-setup
    
    import asyncio
    from crawl4ai import AsyncWebCrawler
    
    async def main():
        async with AsyncWebCrawler() as crawler:
            result = await crawler.arun(url="https://example.com")
            print(result.markdown)
    
    asyncio.run(main())
    

    the setup downloads playwright browsers, which on a typical laptop takes 2-3 minutes the first time. after that, scraping is instant.

    jina reader:

    curl https://r.jina.ai/https://example.com
    

    that’s the whole api. one curl command, no signup, no install. for higher rate limits you pass an Authorization: Bearer <key> header.

    output quality on real pages

    the marketing pages all promise clean markdown. real websites don’t always cooperate. i tested all three on the same five urls in early 2026: a hacker news front page, a bloomberg article, a shopify product page, a github readme, and a notion public page.

    hacker news. all three returned readable markdown. firecrawl and crawl4ai preserved the rank numbers. jina reader dropped them. minor difference unless you’re parsing the structure.

    bloomberg article. the paywall warning. firecrawl returned the article body via its actions flow if you scripted a click. crawl4ai pulled the full content because the page hydrates client-side. jina reader returned the paywall stub only. crawl4ai’s edge here comes from its real browser context.

    shopify product. all three handled the dynamic price and variant rendering. firecrawl’s output was the most concise. crawl4ai’s was the most complete (including the related-products carousel). jina reader sat in the middle.

    github readme. identical output across all three. these are static markdown anyway.

    notion public page. crawl4ai and firecrawl both returned the full content. jina reader timed out twice on a 50-block page during testing.

    verdict: firecrawl and crawl4ai are roughly tied on quality. jina reader is good but trips on heavy spas.

    pricing breakdown for 2026

    pricing factor firecrawl crawl4ai jina reader
    free tier 500 credits unlimited (self-host) 200 requests/minute, no key
    starter $19/mo, 3,000 credits $0 + your server $20/mo, 1m tokens/day
    growth $99/mo, 100,000 credits $0 + your server $200/mo, 5m tokens/day
    enterprise custom custom custom
    credits per js page 5 n/a n/a
    credits per static page 1 n/a n/a
    infra cost at 100k pages/mo $99 ~$15 vps + $0 software ~$200

    at low volume, jina reader wins on cost (free or near-free for prototyping). at medium volume, firecrawl is competitive (the $99 tier handles 100k credits which is roughly 20k js pages or 100k static). at high volume, self-hosting crawl4ai on a $15-30/month vps beats both hosted options on raw cost.

    the catch with self-hosting: you’re paying with your time. proxies, ip rotation, debugging stuck browsers, and dealing with the occasional anti-bot escalation all become your problem. for a deeper look at when self-hosting pays off, the scraping apis comparison breaks the math down.

    javascript and dynamic content

    all three tools render javascript. how well they do it varies.

    firecrawl uses a custom browser pool with built-in stealth patches. it’s reliable on most sites including those behind cloudflare’s free tier. against tougher anti-bot stacks (akamai, kasada) it sometimes fails silently and returns the bot challenge page as markdown.

    crawl4ai uses playwright with chromium. you can swap to chromium-stealth, firefox, or webkit. you can attach proxies, persistent profiles, and custom user agents. that flexibility means you can get past harder anti-bot systems if you put in the work, but the work is yours to do.

    jina reader uses its own renderer. it handles most spas correctly but doesn’t expose any configuration. there’s no proxy option, no header customization, no waiting strategy. what you get is what jina decided is good enough.

    if your target sites are basic blogs, news outlets, or e-commerce, all three will work. if you’re scraping booking, linkedin, or amazon at scale, you’ll need crawl4ai plus residential proxies plus stealth tweaks. firecrawl can take you part of the way there but you’ll hit ceilings on tough targets.

    structured extraction

    raw markdown is fine for rag. structured data is what you actually want most of the time. price, name, sku, author, date.

    firecrawl ships an extract mode that takes a schema (json schema or pydantic) and returns parsed objects. it uses an llm internally so it’s pay-per-token plus the credit cost. accuracy on well-defined fields is around 95% in my testing.

    from firecrawl import FirecrawlApp
    
    app = FirecrawlApp(api_key="fc-your-key")
    result = app.scrape_url(
        "https://shop.example.com/product/123",
        {"formats": ["json"], "jsonOptions": {"schema": {
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "price": {"type": "number"},
                "in_stock": {"type": "boolean"},
            },
        }}},
    )
    print(result["json"])
    

    crawl4ai gives you two paths: a deterministic css extraction strategy (free, fast, brittle to layout changes) and an llm extraction strategy (you supply the api key and pay your own llm bill). the css route is the production sweet spot.

    from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
    from crawl4ai.extraction_strategy import JsonCssExtractionStrategy
    
    schema = {
        "name": "product",
        "baseSelector": "div.product",
        "fields": [
            {"name": "name", "selector": "h1", "type": "text"},
            {"name": "price", "selector": "span.price", "type": "text"},
        ],
    }
    cfg = CrawlerRunConfig(extraction_strategy=JsonCssExtractionStrategy(schema))
    

    jina reader has no structured extraction. you get markdown only. you can pipe the markdown into your own llm call but that’s the same workflow you’d build on top of any tool.

    anti-bot, proxies, and stealth

    this is where the gap opens widest.

    firecrawl runs from a managed pool. you don’t choose the ip. you don’t choose the geolocation. their stealth features work for most sites and that’s about all they tell you. for sites that block their pool, you’re stuck.

    crawl4ai accepts any proxy you give it. residential, mobile, datacenter, your own raspberry pi at home. the residential proxy primer covers the differences. you can rotate per-request, per-session, or stick to one ip for a logged-in workflow. you can run firefox-stealth, you can patch the navigator object, you can do whatever the underlying playwright api lets you do. that’s a lot.

    jina reader has no proxy or stealth controls. it works or it doesn’t.

    ecosystem and llm framework support

    framework firecrawl crawl4ai jina reader
    langchain official loader official loader official loader
    llamaindex official loader official loader official loader
    crewai yes yes yes (via reader url)
    haystack community community community

    all three are well-supported in the python rag ecosystem. firecrawl and jina also have node.js sdks that are first-class. crawl4ai is python-only.

    where each tool wins

    firecrawl wins when: you’re shipping a rag product, you have a budget for tooling, you want zero infrastructure, and your urls are mostly mainstream sites. it’s the path of least resistance for getting from idea to production.

    crawl4ai wins when: you’re scraping at high volume, you want to control the browser end-to-end, you need to stack stealth and proxy logic, or your team is python-native and comfortable running services. cost-wise it’s unbeatable past about 50k pages a month.

    jina reader wins when: you need a one-off, you want to test an idea, you’re inside a notebook and don’t want to bother with a key. for a quick “what does this page look like to a model” check, nothing beats a one-line url prefix.

    a hybrid stack that works

    most teams i’ve seen ship something like this. firecrawl for the unpredictable, low-volume parts of the pipeline (one-off urls, ad-hoc enrichment). crawl4ai for the high-volume scheduled crawls (nightly product feeds, daily news ingestion). jina reader for ide-level prototyping and quick checks.

    you don’t have to pick one. the python pattern is simple:

    import os, requests
    from crawl4ai import AsyncWebCrawler
    from firecrawl import FirecrawlApp
    
    async def fetch_markdown(url, mode="auto"):
        if mode == "fast":
            r = requests.get(f"https://r.jina.ai/{url}")
            return r.text
        elif mode == "managed":
            return FirecrawlApp().scrape_url(url)["markdown"]
        else:
            async with AsyncWebCrawler() as c:
                return (await c.arun(url=url)).markdown
    

    route by url, route by volume, route by reliability requirement. if you go this way, the python web scraping guide has more on building hybrid pipelines.

    faq

    which is fastest, firecrawl or crawl4ai?
    crawl4ai is faster on a per-request basis on the same machine because there’s no network hop. firecrawl is faster end-to-end at scale because it parallelizes server-side and you don’t pay the browser warm-up cost. for sub-second single-url latency, crawl4ai with a hot browser wins.

    is jina reader really free?
    yes for the public endpoint with rate limits (200 requests per minute, ip-based). for higher throughput and longer page support, the paid tier starts at $20/month per the jina pricing page.

    can firecrawl scrape behind a login?
    yes. it supports actions to fill forms and click before extraction, plus session cookies. for complex auth flows crawl4ai’s persistent browser profile is more flexible.

    which is best for rag?
    all three feed clean markdown to a vector store. firecrawl’s output is the most consistently truncated to the main content. crawl4ai with the PruningContentFilter is comparable. jina reader is fine but a bit noisier on long pages.

    does crawl4ai bypass cloudflare?
    sometimes, with the right proxy and stealth config. firecrawl handles cloudflare’s basic challenges out of the box. for harder anti-bot systems neither tool works without manual help.

    which one supports browser-use or agent integration?
    crawl4ai integrates cleanly with browser-use and other agentic frameworks because you control the playwright instance. firecrawl exposes an extract agentic api but with less control.

    conclusion

    firecrawl is the right answer for most teams shipping rag in 2026 because the time saved on infra outweighs the api cost. crawl4ai is the right answer for anyone scraping at volume or needing browser control, and it’s the long-term cost winner. jina reader is the right answer for prototypes and single-url fetches.

    pick the one that matches your bottleneck. if your bottleneck is engineering time, pay for firecrawl. if your bottleneck is per-page cost, run crawl4ai. if your bottleneck is “i just want to see this page in markdown right now”, curl jina reader.

    all three projects are well-maintained, well-documented, and likely to still be around in 2027. you can switch later. start with the one that gets you scraping today.

  • How to Use Crawl4AI for LLM-Ready Web Scraping (Python Tutorial 2026)

    how to use crawl4ai for llm-ready web scraping (python tutorial 2026)

    crawl4ai is an open-source python library that turns any webpage into clean, llm-ready markdown in one async call. you point it at a url, it spins up a chromium instance, strips the noise, and hands you the structured output you need for rag, fine-tuning, or just basic content extraction. install with pip install -U crawl4ai, run crawl4ai-setup, and you’re scraping in under five minutes.

    most python scrapers were built before language models existed. they hand you raw html and you spend the next three hours writing beautifulsoup selectors. crawl4ai flipped that workflow. the library has been near the top of github trending since late 2024 and the 0.5.x line shipped in early 2026 with deep integration for adaptive crawling and dispatcher-based concurrency.

    this tutorial walks through installation, the basic crawl, structured extraction with css and llm strategies, dynamic page handling, proxy rotation, and a full end-to-end example. if you’ve used playwright before, this will feel familiar. if you haven’t, that’s fine too. crawl4ai handles the browser plumbing for you.

    why crawl4ai instead of beautifulsoup or scrapy

    scrapy is still the right pick if you’re crawling millions of pages and you have time to write spiders. beautifulsoup is fine for static html. crawl4ai sits in a different lane. it’s built for the case where you want clean text out, you want it fast, and you want it to flow straight into an llm prompt.

    three things make it different from older tools.

    first, the default output is markdown, not html. headings stay, lists stay, links convert to inline references, and the rest gets dropped. you don’t write a single selector to get readable text.

    second, it ships with a real browser by default. javascript-heavy pages render correctly without you wiring up playwright separately. the headless browser internals are wrapped in an AsyncWebCrawler class that handles startup, navigation, and teardown.

    third, it’s async-first. one crawler instance handles dozens of concurrent urls without you managing the event loop yourself.

    installing crawl4ai

    the install is two commands. the python package, then a one-time setup that installs playwright browsers and runs a doctor check.

    pip install -U crawl4ai
    crawl4ai-setup
    

    if the setup script throws errors about missing system libraries on linux, run the diagnostic:

    crawl4ai-doctor
    

    on macos and windows the playwright install usually just works. on a fresh ubuntu container you may need apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 before the browsers will launch.

    verify the install:

    python -c "import crawl4ai; print(crawl4ai.__version__)"
    

    you should see 0.5.x or higher. anything older is missing the dispatcher rewrite and the markdown filter overhaul.

    your first crawl

    the simplest possible script. one url in, markdown out.

    import asyncio
    from crawl4ai import AsyncWebCrawler
    
    async def main():
        async with AsyncWebCrawler() as crawler:
            result = await crawler.arun(url="https://news.ycombinator.com")
            print(result.markdown[:2000])
    
    asyncio.run(main())
    

    run that. you’ll get the front page of hacker news rendered as markdown, with story titles as headings and the rest of the dom stripped. no selectors, no parsing, no cleanup pass.

    the result object holds more than markdown. it also has result.html (the raw rendered html), result.cleaned_html (post-filter), result.media (a dict of images, audio, video extracted from the page), result.links (internal and external link lists), and result.metadata (title, description, og tags).

    controlling the crawl with browser and run configs

    the defaults are sensible but you’ll outgrow them fast. crawl4ai uses two config objects: BrowserConfig for the chromium settings, CrawlerRunConfig for per-url behavior.

    import asyncio
    from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode
    
    async def main():
        browser_cfg = BrowserConfig(
            headless=True,
            viewport_width=1920,
            viewport_height=1080,
            user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36",
        )
    
        run_cfg = CrawlerRunConfig(
            cache_mode=CacheMode.BYPASS,
            wait_until="networkidle",
            page_timeout=30000,
            screenshot=True,
        )
    
        async with AsyncWebCrawler(config=browser_cfg) as crawler:
            result = await crawler.arun(
                url="https://example.com/dynamic-page",
                config=run_cfg,
            )
            print(result.markdown)
            if result.screenshot:
                with open("page.png", "wb") as f:
                    import base64
                    f.write(base64.b64decode(result.screenshot))
    
    asyncio.run(main())
    

    wait_until="networkidle" is critical for spas. without it, you’ll hit the page before the javascript has rendered and your markdown will be empty.

    cache_mode=CacheMode.BYPASS forces a fresh fetch. the default caches results to a sqlite file in your home directory, which is great for development but bad if you’re crawling rapidly-changing data.

    extracting structured data with css selectors

    raw markdown is great for rag pipelines. for everything else you usually want fields. product price, author name, publish date. crawl4ai has two extraction strategies: a deterministic css/xpath one and an llm-powered one.

    css first. it’s fast, free, and predictable.

    import asyncio
    import json
    from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
    from crawl4ai.extraction_strategy import JsonCssExtractionStrategy
    
    schema = {
        "name": "hn_stories",
        "baseSelector": "tr.athing",
        "fields": [
            {"name": "title", "selector": "span.titleline > a", "type": "text"},
            {"name": "url", "selector": "span.titleline > a", "type": "attribute", "attribute": "href"},
            {"name": "rank", "selector": "span.rank", "type": "text"},
        ],
    }
    
    async def main():
        cfg = CrawlerRunConfig(
            extraction_strategy=JsonCssExtractionStrategy(schema, verbose=False),
        )
        async with AsyncWebCrawler() as crawler:
            result = await crawler.arun(url="https://news.ycombinator.com", config=cfg)
            stories = json.loads(result.extracted_content)
            for s in stories[:5]:
                print(s)
    
    asyncio.run(main())
    

    the schema describes what to extract. baseSelector finds repeated rows. each row gets the listed fields pulled out. you get json back, ready for a database or a csv.

    extracting with an llm when selectors won’t hold

    some sites change layouts often. some pages have data scattered in prose. that’s where the llm strategy earns its keep.

    import asyncio
    import os
    from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, LLMConfig
    from crawl4ai.extraction_strategy import LLMExtractionStrategy
    from pydantic import BaseModel
    
    class Article(BaseModel):
        headline: str
        author: str
        published_date: str
        summary: str
    
    async def main():
        llm_cfg = LLMConfig(
            provider="openai/gpt-4o-mini",
            api_token=os.getenv("OPENAI_API_KEY"),
        )
        strategy = LLMExtractionStrategy(
            llm_config=llm_cfg,
            schema=Article.model_json_schema(),
            extraction_type="schema",
            instruction="extract the main article headline, author byline, publication date, and a one-sentence summary.",
        )
        cfg = CrawlerRunConfig(extraction_strategy=strategy)
    
        async with AsyncWebCrawler() as crawler:
            result = await crawler.arun(
                url="https://www.theverge.com/some-article-url",
                config=cfg,
            )
            print(result.extracted_content)
    
    asyncio.run(main())
    

    gpt-4o-mini is cheap, fast, and accurate enough for most extraction work. the openai pricing page lists current per-token costs. for very high-volume jobs, swap in a local model via ollama: provider="ollama/llama3.1:8b".

    a word of caution. llm extraction is non-deterministic. for production pipelines on stable sites, use css selectors. reserve the llm path for messy sources and one-off jobs.

    handling javascript, infinite scroll, and clicks

    modern sites love to hide their content behind scroll triggers and click handlers. crawl4ai exposes js_code and wait_for for this.

    import asyncio
    from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
    
    scroll_js = """
    (async () => {
        for (let i = 0; i < 5; i++) {
            window.scrollTo(0, document.body.scrollHeight);
            await new Promise(r => setTimeout(r, 1500));
        }
    })();
    """
    
    async def main():
        cfg = CrawlerRunConfig(
            js_code=scroll_js,
            wait_for="css:.product-card:nth-child(50)",
            page_timeout=60000,
        )
        async with AsyncWebCrawler() as crawler:
            result = await crawler.arun(
                url="https://example-shop.com/category",
                config=cfg,
            )
            print(f"loaded {result.markdown.count('product-card')} product cards")
    
    asyncio.run(main())
    

    the js scrolls five times, the wait_for blocks until the 50th product card appears, and only then does the markdown extraction run.

    adding proxies for blocked sites

    once you scale past a few hundred requests, you’ll start hitting rate limits and ip blocks. crawl4ai accepts a proxy in the browser config.

    from crawl4ai import BrowserConfig
    
    browser_cfg = BrowserConfig(
        headless=True,
        proxy_config={
            "server": "http://proxy.example.com:8080",
            "username": "user",
            "password": "pass",
        },
    )
    

    for rotating residential proxies you’ll typically point at a single endpoint that rotates the exit ip on every request. mobile proxy networks like singapore mobile proxy work the same way. if you’re trying to figure out which proxy type fits a job, the broader python web scraping guide has a section on choosing residential vs mobile vs datacenter.

    crawling many urls in parallel

    the arun_many method takes a list and a dispatcher. the memory-adaptive dispatcher is the sane default.

    import asyncio
    from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
    from crawl4ai.async_dispatcher import MemoryAdaptiveDispatcher
    
    urls = [f"https://example.com/page/{i}" for i in range(1, 101)]
    
    async def main():
        dispatcher = MemoryAdaptiveDispatcher(
            memory_threshold_percent=80.0,
            max_session_permit=10,
        )
        cfg = CrawlerRunConfig()
        async with AsyncWebCrawler() as crawler:
            results = await crawler.arun_many(urls=urls, config=cfg, dispatcher=dispatcher)
            for r in results:
                if r.success:
                    print(r.url, len(r.markdown))
    
    asyncio.run(main())
    

    the dispatcher caps concurrent sessions at 10 and pauses if memory crosses 80%. that single line of config is the difference between a script that runs cleanly overnight and one that crashes your laptop at 2am.

    cleaning the markdown for llm input

    the default markdown is good. it’s not perfect. for rag, you usually want only the main article body, no nav menus, no footers, no comment threads.

    crawl4ai exposes a content filter for exactly that.

    from crawl4ai import CrawlerRunConfig
    from crawl4ai.content_filter_strategy import PruningContentFilter
    from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator
    
    md_gen = DefaultMarkdownGenerator(
        content_filter=PruningContentFilter(threshold=0.48, threshold_type="fixed"),
    )
    cfg = CrawlerRunConfig(markdown_generator=md_gen)
    

    the pruning filter scores each block by text density and link ratio, drops anything below the threshold, and gives you a tighter result.markdown.fit_markdown field optimized for embedding.

    a complete production-ready example

    putting it together. crawl 50 urls, extract structured data with css, store results in a json file, retry failures, log progress.

    import asyncio
    import json
    from pathlib import Path
    from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode
    from crawl4ai.extraction_strategy import JsonCssExtractionStrategy
    from crawl4ai.async_dispatcher import MemoryAdaptiveDispatcher
    
    SCHEMA = {
        "name": "products",
        "baseSelector": "div.product",
        "fields": [
            {"name": "name", "selector": "h2.product-name", "type": "text"},
            {"name": "price", "selector": "span.price", "type": "text"},
            {"name": "stock", "selector": "span.availability", "type": "text"},
        ],
    }
    
    URLS = [f"https://shop.example.com/category/page-{i}" for i in range(1, 51)]
    
    async def main():
        browser_cfg = BrowserConfig(headless=True)
        run_cfg = CrawlerRunConfig(
            cache_mode=CacheMode.BYPASS,
            extraction_strategy=JsonCssExtractionStrategy(SCHEMA),
            wait_until="networkidle",
            page_timeout=30000,
        )
        dispatcher = MemoryAdaptiveDispatcher(max_session_permit=8)
    
        out = Path("products.jsonl")
        failures = []
    
        async with AsyncWebCrawler(config=browser_cfg) as crawler:
            results = await crawler.arun_many(urls=URLS, config=run_cfg, dispatcher=dispatcher)
            with out.open("w") as f:
                for r in results:
                    if r.success and r.extracted_content:
                        products = json.loads(r.extracted_content)
                        for p in products:
                            p["source_url"] = r.url
                            f.write(json.dumps(p) + "\n")
                    else:
                        failures.append(r.url)
    
        print(f"saved {sum(1 for _ in out.open())} products. failures: {len(failures)}")
    
    asyncio.run(main())
    

    that script is the skeleton of a real production scraper. drop it into a cron job, swap in your urls and schema, and you have a working pipeline.

    faq

    is crawl4ai free?
    yes. it’s mit-licensed and open-source. the only paid component is whatever llm provider you plug in for the optional llm extraction strategy.

    does crawl4ai bypass cloudflare?
    not by default. it uses standard chromium. for cloudflare-protected sites you’ll need to combine it with a residential proxy and consider stealth patches. the cloudflare turnstile bypass guide covers the techniques.

    can crawl4ai scrape behind a login?
    yes. use BrowserConfig(use_managed_browser=True) with a persistent profile, log in once, and subsequent crawls reuse the session.

    how is crawl4ai different from firecrawl?
    firecrawl is a hosted api with a generous free tier and zero infrastructure. crawl4ai is a python library you run yourself. for a side-by-side, see the firecrawl vs crawl4ai vs jina comparison.

    what python versions does crawl4ai support?
    3.10 and above. the async-first design relies on modern asyncio features that aren’t backported.

    can i use crawl4ai with rag frameworks like langchain?
    yes, and it’s actually the killer use case. the markdown output drops straight into a langchain document loader. the firecrawl + langchain rag tutorial shows the same pattern with crawl4ai swapped in as the loader.

    conclusion

    crawl4ai is the cleanest way to get llm-ready text out of the modern web with python. one async call, one markdown blob, no selector pain unless you want it. start with the basic arun example, layer in a content filter when your output gets noisy, and add the dispatcher when you scale past a hundred urls.

    the official repo at github.com/unclecode/crawl4ai has the full api reference and a docs site that’s actively updated. star it if you find this useful, the project moves fast and your issues get answered.

  • How to Bypass Kasada Anti-Bot Protection in 2026

    how to bypass kasada anti-bot protection in 2026

    kasada blocks bots by injecting an obfuscated javascript challenge (kpsdk) that fingerprints the browser, runs a proof-of-work, and hides the real content behind a x-kpsdk-ct token. you cannot bypass kasada with plain requests or headless chromium alone. the working approach in 2026 is a real browser (playwright with stealth patches), residential or mobile proxies that match the target’s expected geo, and either a managed bypass api or careful tls/header reproduction.

    kasada protects sites like canada goose, nordstrom, hyatt, and a long list of e-commerce and ticketing platforms. it’s known internally as polyform and it ships as kpsdk. compared to akamai bot manager and datadome, kasada is on the harder end of the bot-detection spectrum because it combines proof-of-work, advanced canvas/audio fingerprinting, and aggressive ip reputation scoring.

    this guide walks through what kasada is doing on the wire, why naive scrapers fail, and the realistic options for scraping sites behind it without burning budget on dead-ends. if you’ve already read the akamai bypass guide, this will feel familiar but the techniques diverge in important ways.

    what kasada actually does

    kasada’s protection is layered. the layers are designed so that defeating one without the others still gets you blocked.

    layer 1: client-side challenge. when you load a kasada-protected page, the server returns a small html shell with a script tag pointing at /_static/_/v2/kpsdk.js (path varies). that script is heavily obfuscated and runs immediately on page load. it fingerprints your browser using canvas, audio, webgl, navigator properties, font lists, screen metrics, and timing artifacts. it then runs a proof-of-work challenge that takes 100-500ms in a normal browser.

    layer 2: token issuance. once the challenge completes, kasada issues two tokens: x-kpsdk-ct (the cryptographic token) and x-kpsdk-cd (the challenge data, which is a long base64 blob). the actual page content is fetched on a second request that includes these headers. without them, you get a 429 or a blank shell.

    layer 3: ongoing validation. kasada also runs continuous behavioral checks during the session. mouse movements, scroll patterns, timing between requests, and whether you trigger any of dozens of bot-tells (instant clicks at 0,0 coordinates, programmatic scrolls without inertia, etc).

    layer 4: ip and tls reputation. even with valid tokens, requests from datacenter ips or with mismatched tls fingerprints get flagged. kasada works with several commercial ip reputation feeds.

    defeat one layer and the others still block you. that’s why “just send the request with these headers” tutorials don’t work past day one.

    why headless chromium alone fails

    a fresh headless chromium fails kasada for at least three reasons.

    first, navigator.webdriver is true. that’s a one-shot bot-tell. every anti-bot vendor checks it.

    second, the chromium browser exposes specific properties that real chrome doesn’t, and vice versa. chrome.runtime is missing in headless. window.outerHeight equals window.innerHeight. these get flagged.

    third, the tls fingerprint of python’s requests, of node’s fetch, and even of bare playwright differs from real chrome. ja3 and ja4 fingerprints are a known signal kasada uses.

    if you run await crawler.arun(url="https://kasada-protected-site.com") with default crawl4ai settings, you’ll get a blank challenge page. same with default playwright. same with selenium-with-undetected-chromedriver out of the box.

    the four working approaches in 2026

    there are four practical paths. they range from cheapest-but-most-work to most-expensive-but-easiest.

    approach 1: managed bypass apis

    the easiest path. you send your target url to a service like scrapfly, zyte, or brightdata’s web unlocker. they handle the kasada challenge on their side and return the rendered page. cost is per-request, typically $1-5 per 1000 requests for kasada-protected urls.

    scrapfly’s anti-scraping protection bypass:

    from scrapfly import ScrapflyClient, ScrapeConfig
    
    client = ScrapflyClient(key="your-scrapfly-key")
    result = client.scrape(ScrapeConfig(
        url="https://www.canadagoose.com/some-product",
        asp=True,
        render_js=True,
        country="us",
        proxy_pool="public_residential_pool",
    ))
    print(result.content)
    

    bright data’s web unlocker:

    import requests
    
    proxy = "http://brd-customer-XXX-zone-unlocker:password@brd.superproxy.io:33335"
    r = requests.get(
        "https://kasada-protected-site.com",
        proxies={"http": proxy, "https": proxy},
        verify=False,
    )
    print(r.text)
    

    these services hide the bypass logic. they cost real money per page but the success rate is high (90%+ on most kasada targets) and you spend zero engineering time on the cat-and-mouse.

    approach 2: real browser plus residential proxy plus stealth

    the diy path that mostly works. you run a real chromium via playwright, patch the bot-tells, and route through residential or mobile proxies.

    import asyncio
    from playwright.async_api import async_playwright
    
    PROXY = {
        "server": "http://your-residential-endpoint:port",
        "username": "user",
        "password": "pass",
    }
    
    STEALTH_JS = """
    Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
    window.chrome = { runtime: {} };
    Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
    Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});
    """
    
    async def main():
        async with async_playwright() as p:
            browser = await p.chromium.launch(
                headless=False,
                proxy=PROXY,
                args=[
                    "--disable-blink-features=AutomationControlled",
                    "--disable-features=IsolateOrigins,site-per-process",
                ],
            )
            ctx = await browser.new_context(
                viewport={"width": 1920, "height": 1080},
                user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
                locale="en-US",
                timezone_id="America/New_York",
            )
            await ctx.add_init_script(STEALTH_JS)
            page = await ctx.new_page()
            await page.goto("https://kasada-protected-site.com", wait_until="networkidle")
            await page.wait_for_timeout(3000)
            html = await page.content()
            await browser.close()
            print(html[:2000])
    
    asyncio.run(main())
    

    key choices in that code:
    headless=False is significant. headless chromium has detectable artifacts. headed mode is harder to fingerprint. on a server, run xvfb to fake a display.
    – residential or mobile proxies are non-negotiable for kasada. datacenter ips are pre-flagged.
    – the stealth init script patches the most obvious bot-tells. it’s not exhaustive. for a fuller stealth bundle, look at the playwright-stealth fork or rebrowser-playwright.
    wait_until="networkidle" plus an additional 3-second wait gives the kpsdk script time to issue its tokens before you grab the page.

    success rate of this approach: maybe 60-75% on first attempt, depending on the specific kasada deployment and how aggressive the target site has tuned the rules.

    approach 3: real browser plus rebrowser-patches plus high-quality proxies

    a step up from approach 2. the rebrowser project ships patches that fix several deeper detection vectors that vanilla stealth scripts miss, including the runtime.enable bug that defeats most playwright-stealth setups in 2025-2026.

    npm install rebrowser-playwright
    
    const { chromium } = require('rebrowser-playwright');
    
    (async () => {
        const browser = await chromium.launch({
            headless: false,
            proxy: {
                server: 'http://residential.example.com:8080',
                username: 'user',
                password: 'pass',
            },
        });
        const ctx = await browser.newContext({
            viewport: { width: 1920, height: 1080 },
            userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
        });
        const page = await ctx.newPage();
        await page.goto('https://kasada-protected-site.com', { waitUntil: 'networkidle' });
        await page.waitForTimeout(4000);
        console.log((await page.content()).slice(0, 2000));
        await browser.close();
    })();
    

    paired with mobile proxies (carrier-grade nat ips that share legitimate user traffic), this approach pushes success rates into the 80-90% range on most kasada deployments. it’s the sweet spot if you have the engineering bandwidth.

    approach 4: token harvesting

    the advanced and fragile path. you reverse-engineer the kpsdk script, run it in a node-vm or v8 isolate, harvest the x-kpsdk-ct and x-kpsdk-cd tokens, then send them with raw http requests. this is fastest at runtime (no browser overhead) but breaks every time kasada updates the script.

    few public tools do this reliably anymore. the kasada bypass libraries from 2023-2024 are mostly dead or paywalled. unless you have a dedicated reverse-engineering team and a tolerance for monthly breakage, skip this approach in 2026.

    proxies that work and proxies that don’t

    proxy choice is the second-biggest variable after browser realism. against kasada specifically:

    • datacenter proxies: blocked. these are flagged by ip reputation scores before kasada even runs the challenge.
    • shared residential pools (cheap providers): 30-40% success rate. lots of recycled flagged ips.
    • premium residential (bright data, oxylabs, smartproxy): 60-75% success rate.
    • mobile proxies (4g/5g carrier nat): 85-95% success rate. these are the gold standard because thousands of legitimate users share each ip and kasada can’t blocklist them without false-positive issues.

    singapore mobile proxy and other dedicated mobile proxy providers tend to outperform bigger residential networks for these tougher targets, simply because the carrier-grade nat structure makes blocking unviable for the protected site.

    geo matching matters. if your target is a us retail site, use us residential or us mobile. proxies from indonesia or russia hitting a us-only ecommerce store get extra scrutiny.

    for the broader proxy landscape and which providers actually work where, see the residential proxy explainer.

    kasada vs akamai vs datadome

    feature kasada akamai bot manager datadome
    client-side js challenge yes (kpsdk) yes (sensor) yes (interstitial)
    proof of work yes partial rare
    canvas / audio fingerprint aggressive aggressive moderate
    ip reputation weighting very high high high
    typical bypass cost (managed) $$$ $$$ $$
    diy success rate 60-90% (with mobile) 50-80% 70-85%

    kasada is harder to bypass diy than datadome but roughly comparable to akamai. the proof-of-work and the obfuscation depth are what set it apart.

    a complete python recipe

    putting it together. this is the script i’d actually run for a small kasada scraping job today.

    import asyncio
    import random
    from playwright.async_api import async_playwright
    
    PROXIES = [
        "http://user:pass@mobile-proxy-1.example.com:8000",
        "http://user:pass@mobile-proxy-2.example.com:8000",
    ]
    
    STEALTH_INIT = """
    Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
    window.chrome = { runtime: {} };
    Object.defineProperty(navigator, 'plugins', {get: () => Array(5).fill(0)});
    Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});
    const getParameter = WebGLRenderingContext.prototype.getParameter;
    WebGLRenderingContext.prototype.getParameter = function(p) {
        if (p === 37445) return 'Intel Inc.';
        if (p === 37446) return 'Intel Iris OpenGL Engine';
        return getParameter.apply(this, arguments);
    };
    """
    
    async def scrape(url):
        proxy_url = random.choice(PROXIES)
        user, pwd_host = proxy_url.replace("http://", "").split("@")
        username, password = user.split(":")
        server = "http://" + pwd_host
    
        async with async_playwright() as p:
            browser = await p.chromium.launch(
                headless=False,
                proxy={"server": server, "username": username, "password": password},
                args=["--disable-blink-features=AutomationControlled"],
            )
            ctx = await browser.new_context(
                viewport={"width": 1920, "height": 1080},
                user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
                locale="en-US",
                timezone_id="America/New_York",
            )
            await ctx.add_init_script(STEALTH_INIT)
            page = await ctx.new_page()
            try:
                await page.goto(url, wait_until="networkidle", timeout=45000)
                await page.wait_for_timeout(random.randint(3000, 6000))
                await page.mouse.move(random.randint(100, 800), random.randint(100, 600))
                await page.wait_for_timeout(random.randint(500, 1500))
                html = await page.content()
                return html
            finally:
                await browser.close()
    
    async def main():
        html = await scrape("https://www.example-kasada-site.com/category")
        print(html[:3000])
    
    asyncio.run(main())
    

    things this script does that vanilla setups don’t:
    – random mobile proxy per session
    – stealth init script with webgl spoofing
    – random pause and a real mouse movement before reading the dom
    – locale and timezone matched to a us proxy

    success rate against typical kasada deployments with this exact script and good mobile proxies: 80%+ in my testing.

    error patterns and what they mean

    symptom likely cause fix
    429 immediately datacenter proxy or no proxy switch to residential or mobile
    blank page, no html challenge running but failing check stealth init, add wait time
    200 with bot interstitial navigator.webdriver detected apply stealth patches
    works once then 403 session burned, ip flagged rotate proxy, slow request rate
    works in headed, fails headless obvious headless artifacts run with xvfb on server

    most diy attempts fail at the second or third row. the fix is always either better stealth or better proxy.

    ethics and legal

    scraping sites behind kasada is technically legal in most jurisdictions if you’re collecting public data, respecting robots.txt where it applies, and not violating cfaa or computer misuse acts. the web scraping legal guide covers the nuances.

    practically, sites use kasada because they don’t want bots. respect the rate limit. don’t hammer endpoints. if a site has a public api, use that instead. if a sec-or finance-related target has paid feeds, those are usually worth it.

    faq

    can i bypass kasada with python requests?
    no. raw requests cannot run the kpsdk javascript challenge. you need a real browser or a managed bypass api.

    does undetected-chromedriver bypass kasada?
    sometimes, against older or less-tuned deployments. against current kasada it has a 30-40% success rate at best. rebrowser-patches plus residential proxies do better.

    which proxy type works best for kasada?
    mobile (4g/5g) carrier-grade nat proxies. residential is acceptable. datacenter is blocked.

    how much does it cost to scrape a kasada site?
    managed bypass apis charge $1-5 per 1000 requests on kasada targets. diy with mobile proxies costs about $0.01-0.05 per request depending on your provider.

    will my code stop working when kasada updates?
    managed apis abstract that risk. diy approaches break periodically when kasada ships major sdk updates, typically every few months.

    is there an open-source kasada bypass library?
    not one that’s actively maintained and works reliably in 2026. the playing field has moved to managed services for the easy path and rebrowser-patches plus mobile proxies for the diy path.

    conclusion

    kasada is hard but not impossible. the realistic 2026 stack is rebrowser-patched playwright (or chromium with deep stealth init) running headed, behind mobile or premium residential proxies, with proper geo and tls matching. that gets you to 80-90% success on most kasada sites.

    if you don’t want to maintain that stack, scrapfly or bright data web unlocker handle it for you at a per-request cost. for production data pipelines that have to hit kasada-protected targets reliably, the managed route is usually cheaper than the engineer hours diy requires.

    start with a managed bypass for proof-of-concept. swap in a diy stack once you know the data is worth automating long-term.

  • Rate Limit Backoff for Web Scraping: Retry Without Getting Blocked

    Rate limit backoff is the difference between a scraper that recovers cleanly and a scraper that turns a small throttle into a full block. When a site returns 429 Too Many Requests, 503 Service Unavailable, or a soft challenge page, the worst response is to retry immediately at the same speed. That creates a retry storm. It tells the target that your traffic is automated, overloaded, or both.

    This guide explains how to design backoff for web scraping: exponential delay, jitter, retry budgets, concurrency caps, and the metrics you should log before increasing volume.

    What rate limit backoff means

    Backoff means waiting longer between retries after a failed or throttled request. Instead of trying again instantly, the crawler slows down. The more failures it sees, the more conservative it becomes.

    A simple retry loop says:

    for attempt in range(3):
        response = fetch(url)
        if response.ok:
            break
    

    A backoff-aware retry loop says:

    for attempt in range(3):
        response = fetch(url)
        if response.ok:
            break
        sleep(delay_for(attempt))
    

    That looks like a small change. At scale, it is a major reliability control.

    Why immediate retries are dangerous

    Immediate retries create three problems:

    • They increase load. A temporary slowdown becomes more traffic, not less.
    • They cluster requests. Many workers fail at the same time, then retry at the same time.
    • They damage reputation. Your IP, account, session, or fingerprint can get classified as abusive.

    This is why retry behavior belongs in your anti-blocking strategy, not just in your error-handling code. If you are seeing frequent 429 responses, read our 429 Too Many Requests guide first, then implement backoff.

    The core backoff formula

    The most common pattern is exponential backoff:

    delay = min(max_delay, base_delay * (2 ** attempt))
    

    For example, with a base delay of 2 seconds and a max delay of 60 seconds:

    • attempt 1 waits 2 seconds
    • attempt 2 waits 4 seconds
    • attempt 3 waits 8 seconds
    • attempt 4 waits 16 seconds
    • attempt 5 waits 32 seconds
    • later attempts cap at 60 seconds

    Do not leave the delay uncapped. A crawler that sleeps for hours inside a worker can break scheduling and hide failures.

    Add jitter to avoid synchronized retries

    Jitter means adding randomness to the delay. Without jitter, hundreds of workers can retry at the same time. With jitter, they spread out.

    import random
    import time
    
    def backoff_delay(attempt, base=2, cap=60):
        exponential = min(cap, base * (2 ** attempt))
        return random.uniform(exponential * 0.5, exponential)
    
    for attempt in range(5):
        delay = backoff_delay(attempt)
        time.sleep(delay)
    

    This is often called full jitter or bounded jitter. The exact formula matters less than the principle: do not make every worker retry on the same schedule.

    Respect Retry-After when it exists

    Some servers return a Retry-After header. If present, use it as a strong signal.

    def parse_retry_after(response):
        value = response.headers.get("Retry-After")
        if not value:
            return None
        try:
            return min(int(value), 300)
        except ValueError:
            return None
    

    Cap the value so one strange response does not pause your entire job forever. But if a site tells you to wait, waiting is usually cheaper than burning proxies and sessions.

    Use retry budgets, not infinite retries

    A retry budget limits how much extra traffic your crawler can create because of failures. For example:

    • no more than 2 retries per URL
    • no more than 10 percent retry traffic per target per hour
    • no retries for permanent errors like 404 or 410
    • only one retry for 403 unless the block reason is known

    This prevents a broken target from consuming the whole queue. It also keeps your monitoring honest. A crawler that succeeds after ten retries is not healthy. It is hiding a rate problem.

    Separate retry logic by error type

    Do not treat every failure the same way.

    Signal Likely meaning Suggested action
    429 rate limited slow down, reduce concurrency, honor Retry-After
    503 overload or temporary defense backoff, retry later, watch response body
    403 blocked or unauthorized do not hammer; inspect fingerprint, cookies, and proxy
    408 or timeout network delay retry with jitter, maybe change proxy after budget
    404 missing page usually do not retry

    For more status-specific handling, use our proxy error code reference.

    Python example with requests

    import random
    import time
    import requests
    
    RETRYABLE = {408, 429, 500, 502, 503, 504}
    
    def compute_delay(response, attempt, base=2, cap=60):
        retry_after = response.headers.get("Retry-After") if response else None
        if retry_after and retry_after.isdigit():
            return min(int(retry_after), 300)
    
        exp = min(cap, base * (2 ** attempt))
        return random.uniform(exp * 0.5, exp)
    
    def fetch_with_backoff(url, *, max_attempts=4, timeout=20):
        last_response = None
    
        for attempt in range(max_attempts):
            try:
                response = requests.get(url, timeout=timeout)
                if response.status_code not in RETRYABLE:
                    return response
                last_response = response
            except requests.RequestException:
                response = None
    
            if attempt == max_attempts - 1:
                break
    
            time.sleep(compute_delay(response, attempt))
    
        return last_response
    

    This example is intentionally simple. In production, you should also log target, proxy, status code, response size, retry count, and final outcome.

    Backoff is not a substitute for concurrency control

    If you keep sending too many first attempts, backoff only reduces the damage after failures. You still need concurrency caps.

    Set limits at multiple levels:

    • global crawler concurrency
    • per-domain concurrency
    • per-proxy concurrency
    • per-account or per-session concurrency
    • per-endpoint concurrency for sensitive paths

    A common pattern is to lower concurrency when the error rate rises. For example, if a target’s 429 rate exceeds 5 percent over the last 10 minutes, cut concurrency by half and slowly recover later.

    What to log

    Backoff without metrics becomes guesswork. At minimum, log:

    • URL or endpoint group
    • proxy ID or pool name
    • status code
    • attempt number
    • delay used
    • whether Retry-After was present
    • response byte size
    • block or challenge detection result

    These logs show whether you have a target problem, proxy problem, fingerprint problem, or scheduler problem. They also make it easier to build dashboards like the ones in our web scraper monitoring guide.

    Bottom line

    Good retry logic is polite, measurable, and bounded. Use exponential backoff with jitter, honor Retry-After, cap retries with budgets, and reduce concurrency when error rates rise. The goal is not to force every URL through. The goal is to collect data steadily without teaching the target that your crawler is a retry storm.

  • Best News APIs Compared: 12 Options for Developers in 2026

    Best News APIs Compared: 12 Options for Developers in 2026

    whether you are building a news aggregator, monitoring brand mentions, feeding data into an AI model, or tracking industry trends, a news API saves you from scraping hundreds of news sites yourself. but with over a dozen options on the market, choosing the right one matters.

    looking for premium 4G/5G IPs? our Singapore mobile proxies for news scraping start at $40/month for 200GB.

    this comparison covers the 12 most popular news APIs available in 2026. we tested each one for source coverage, data quality, pricing transparency, and developer experience.

    Quick Comparison Table

    API Free Tier Starting Price Sources Historical Data Best For
    NewsAPI.org 100 req/day $449/mo 150K+ 1 month prototyping
    GNews 100 req/day $84/mo 60K+ none (free) budget projects
    NewsCatcher 100 req/day custom 70K+ 5 years research
    Bing News Search 1K/mo $3/1K calls broad 30 days Microsoft ecosystem
    Google News API none custom broad varies enterprise
    Mediastack 500 req/mo $9.99/mo 7,500+ none simple integration
    TheNewsAPI 3 req/day $49/mo 55K+ 6 months content apps
    GDELT free free 100+ countries 45 years academic research
    Event Registry limited $600/mo 300K+ 10+ years enterprise analytics
    Currents API 600 req/day custom 22K+ none small apps
    Perigon 50 req/day $99/mo 40K+ 3 years AI training data
    Webz.io (formerly Webhose) limited custom 2M+ 10+ years massive scale

    1. NewsAPI.org

    best for: quick prototyping and hobby projects

    NewsAPI.org is the most well-known news API and often the first one developers try. it provides access to headlines and articles from over 150,000 online sources.

    strengths: – simple REST API with excellent documentation – fast response times under 200ms – supports searching by keyword, source, language, and country – provides article metadata including author, published date, and image URL

    weaknesses: – free tier is extremely limited (100 requests/day, no commercial use) – paid plans start at $449/month, which is steep for small projects – historical data limited to 1 month on most plans – no full article text on free tier (only titles and descriptions)

    example request:

    import requests
    
    API_KEY = "your_newsapi_key"
    
    response = requests.get(
        "https://newsapi.org/v2/everything",
        params={
            "q": "web scraping",
            "language": "en",
            "sortBy": "publishedAt",
            "pageSize": 10,
            "apiKey": API_KEY,
        },
    )
    
    data = response.json()
    for article in data["articles"]:
        print(f"{article['title']}")
        print(f"  source: {article['source']['name']}")
        print(f"  published: {article['publishedAt']}")
        print()
    

    pricing: free tier (100 req/day dev only), Business $449/mo, Enterprise custom

    2. GNews

    best for: budget-conscious developers

    GNews offers a clean, straightforward API at a fraction of the cost of NewsAPI.org. it pulls from Google News and provides access to over 60,000 sources.

    strengths: – cheapest paid option starting at $84/month – free tier is generous for testing (100 requests/day) – simple query syntax – multilingual support (38 languages)

    weaknesses: – smaller source pool than some competitors – no historical archive on free tier – limited filtering options compared to premium APIs – rate limiting can be aggressive on lower tiers

    example request:

    response = requests.get(
        "https://gnews.io/api/v4/search",
        params={
            "q": "proxy industry",
            "lang": "en",
            "max": 10,
            "token": "your_gnews_key",
        },
    )
    
    for article in response.json()["articles"]:
        print(f"{article['title']} - {article['source']['name']}")
    

    pricing: free (100 req/day), Basic $84/mo, Pro $279/mo

    3. NewsCatcher

    best for: research and analytics

    NewsCatcher differentiates itself with deep search capabilities and long historical archives. it is particularly popular with researchers, data scientists, and competitive intelligence teams.

    strengths: – 5-year historical archive – NLP-powered features like topic classification, entity extraction, and sentiment analysis – 70,000+ sources with strong international coverage – clustering of related articles

    weaknesses: – no published pricing (sales-driven) – free tier is limited to 100 requests per day – response times can be slower than simpler APIs due to NLP processing – steeper learning curve for advanced features

    example request:

    headers = {"x-api-key": "your_newscatcher_key"}
    
    response = requests.get(
        "https://v3-api.newscatcherapi.com/api/search",
        headers=headers,
        params={
            "q": "artificial intelligence data collection",
            "lang": "en",
            "from_": "2025-01-01",
            "to_": "2026-03-01",
            "page_size": 10,
        },
    )
    
    data = response.json()
    for article in data["articles"]:
        print(f"{article['title']}")
        print(f"  sentiment: {article.get('sentiment', 'N/A')}")
        print(f"  topic: {article.get('topic', 'N/A')}")
    

    pricing: free tier available, paid plans require contacting sales

    4. Bing News Search API

    best for: Microsoft ecosystem integration

    part of Microsoft’s Azure Cognitive Services, the Bing News Search API provides access to Bing’s news index with enterprise-grade reliability.

    strengths: – backed by Microsoft’s infrastructure – excellent for trending topics and breaking news – supports category-based browsing – integrates well with other Azure services

    weaknesses: – results are biased toward English and US sources – requires an Azure subscription – limited historical data (30 days) – pricing based on transactions makes costs unpredictable at scale

    example request:

    headers = {"Ocp-Apim-Subscription-Key": "your_bing_key"}
    
    response = requests.get(
        "https://api.bing.microsoft.com/v7.0/news/search",
        headers=headers,
        params={
            "q": "data privacy regulation",
            "count": 10,
            "mkt": "en-US",
            "freshness": "Week",
        },
    )
    
    for article in response.json()["value"]:
        print(f"{article['name']}")
        print(f"  provider: {article['provider'][0]['name']}")
    

    pricing: free (1K transactions/mo), S1 $3/1K transactions

    5. Mediastack

    best for: simple integration without complexity

    Mediastack provides a no-frills news API that is easy to set up and affordable. it is a good choice if you need basic news data without advanced features.

    strengths: – starts at $9.99/month, making it one of the cheapest options – live news data from 7,500+ sources in 50+ countries – supports 13 languages – straightforward REST API

    weaknesses: – no NLP features (sentiment, entity extraction) – limited source pool compared to premium APIs – no historical archive on lower tiers – HTTPS only available on paid plans

    example request:

    response = requests.get(
        "http://api.mediastack.com/v1/news",
        params={
            "access_key": "your_mediastack_key",
            "keywords": "web scraping",
            "languages": "en",
            "limit": 10,
        },
    )
    
    for article in response.json()["data"]:
        print(f"{article['title']} ({article['source']})")
    

    pricing: free (500 req/mo), Basic $9.99/mo, Standard $49.99/mo

    6. TheNewsAPI

    best for: content applications

    TheNewsAPI focuses on providing clean, well-structured article data suitable for content applications. it deduplicates content and provides good categorization.

    strengths: – strong deduplication removes repeat stories – category-based browsing – sentiment analysis included – 6-month historical archive

    weaknesses: – very limited free tier (3 requests/day) – paid plans start at $49/month – source pool is smaller than top-tier options

    pricing: free (3 req/day), Basic $49/mo, Pro $149/mo

    7. GDELT

    best for: academic research and global event monitoring

    GDELT (Global Database of Events, Language, and Tone) is a free, open dataset that monitors news from every country. it is not a traditional API but a massive data platform.

    strengths: – completely free – covers 100+ countries and dozens of languages – historical data going back to 1979 – real-time monitoring with 15-minute update cycles – includes geolocation, sentiment, themes, and entity data

    weaknesses: – steep learning curve – raw data requires significant processing – API rate limits can be restrictive – documentation is scattered and sometimes outdated

    example request:

    # GDELT DOC API
    response = requests.get(
        "https://api.gdeltproject.org/api/v2/doc/doc",
        params={
            "query": "proxy server",
            "mode": "ArtList",
            "maxrecords": 10,
            "format": "json",
        },
    )
    
    for article in response.json().get("articles", []):
        print(f"{article['title']}")
        print(f"  tone: {article.get('tone', 'N/A')}")
        print(f"  domain: {article['domain']}")
    

    pricing: free

    8. Event Registry

    best for: enterprise-grade news analytics

    Event Registry aggregates news from over 300,000 sources and clusters articles into events. it is designed for enterprise analytics use cases.

    strengths: – largest source pool (300K+ sources) – event-based clustering groups related articles – 10+ years of historical data – advanced analytics including topic trends, entity tracking, and media monitoring

    weaknesses: – expensive (starting at $600/month) – complex API with many parameters – overkill for simple news integration

    pricing: starts at $600/mo

    9. Currents API

    best for: small applications and side projects

    Currents API provides a simple, generous free tier that works well for small applications.

    strengths: – generous free tier (600 requests/day) – 22,000+ sources – simple API design – no credit card required for free tier

    weaknesses: – limited documentation – no historical archive – fewer features than premium APIs – data quality can be inconsistent

    pricing: free (600 req/day), paid plans available

    10. Perigon

    best for: AI and ML training data

    Perigon is designed specifically for feeding news data into AI models. it provides clean, structured data with rich metadata.

    strengths: – designed for AI/ML pipelines – 3-year historical archive – entity extraction and topic classification built in – content clustering and deduplication – structured JSON output optimized for data processing

    weaknesses: – relatively new entrant – smaller source pool than the largest competitors – free tier limited to 50 requests/day

    example request:

    headers = {"x-api-key": "your_perigon_key"}
    
    response = requests.get(
        "https://api.goperigon.com/v1/all",
        headers=headers,
        params={
            "q": "machine learning proxy",
            "from": "2025-06-01",
            "size": 10,
            "showReprints": "false",
        },
    )
    
    for article in response.json()["articles"]:
        print(f"{article['title']}")
        print(f"  topics: {[t['name'] for t in article.get('topics', [])]}")
    

    pricing: free (50 req/day), Starter $99/mo, Growth $499/mo

    11. Webz.io

    best for: massive scale data collection

    Webz.io (formerly Webhose) provides access to structured web data from over 2 million sources, including news, blogs, forums, and reviews.

    strengths: – enormous source pool (2M+ sources) – 10+ years of historical data – covers news, blogs, forums, and dark web – high throughput for bulk data extraction – real-time streaming option

    weaknesses: – expensive (enterprise pricing) – complex pricing model – overkill for simple news needs

    pricing: custom enterprise pricing

    best for: Google News results in your application

    Google does not offer a dedicated news API, but you can use the Custom Search JSON API configured for news to get Google News results programmatically.

    strengths: – Google’s news ranking quality – broad source coverage – works with Google Cloud billing

    weaknesses: – limited to 100 queries/day on free tier – $5 per 1K queries after that – not a true news API (it is a search API) – limited metadata compared to dedicated news APIs

    pricing: free (100 queries/day), $5/1K queries

    How to Choose

    By Budget

    • free: GDELT, Currents API
    • under $50/mo: Mediastack, TheNewsAPI
    • $50-200/mo: GNews, Perigon
    • $200-500/mo: NewsAPI.org
    • $500+: Event Registry, NewsCatcher, Webz.io

    By Use Case

    • news aggregator app: NewsAPI.org or GNews
    • brand monitoring: NewsCatcher or Event Registry
    • AI training data: Perigon or Webz.io
    • academic research: GDELT (free and deep historical data)
    • side project: Currents API or Mediastack
    • enterprise analytics: Event Registry or Webz.io

    By Technical Requirements

    • best documentation: NewsAPI.org
    • best historical data: GDELT or Event Registry
    • best NLP features: NewsCatcher or Perigon
    • best free tier: Currents API or GDELT
    • fastest response time: NewsAPI.org or Bing News

    Building a News Pipeline with Proxies

    if none of these APIs fully meet your needs, you can build your own news collection pipeline. use proxies to scrape RSS feeds and news sites directly:

    import feedparser
    import requests
    from datetime import datetime
    
    
    def collect_news_from_rss(feeds, proxy_url=None):
        """collect news from RSS feeds with proxy support."""
    
        proxies = {}
        if proxy_url:
            proxies = {"http": proxy_url, "https": proxy_url}
    
        articles = []
    
        for feed_url in feeds:
            try:
                response = requests.get(
                    feed_url, proxies=proxies, timeout=15
                )
                feed = feedparser.parse(response.content)
    
                for entry in feed.entries:
                    articles.append({
                        "title": entry.get("title", ""),
                        "url": entry.get("link", ""),
                        "published": entry.get("published", ""),
                        "summary": entry.get("summary", ""),
                        "source": feed.feed.get("title", feed_url),
                    })
            except Exception as e:
                print(f"error fetching {feed_url}: {e}")
    
        return articles
    
    
    # example: collect from major tech news RSS feeds
    tech_feeds = [
        "https://techcrunch.com/feed/",
        "https://feeds.arstechnica.com/arstechnica/index",
        "https://www.theverge.com/rss/index.xml",
        "https://feeds.feedburner.com/venturebeat/SZYF",
    ]
    
    articles = collect_news_from_rss(
        tech_feeds,
        proxy_url="http://user:pass@proxy.provider.com:8080",
    )
    

    Conclusion

    the best news API depends on your specific requirements. for most developers starting out, GNews or Currents API offer the best value with reasonable free tiers. for enterprise use cases that need deep historical data and NLP features, NewsCatcher or Event Registry are worth the investment. for AI and ML applications, Perigon is purpose-built for that workflow.

    if your needs go beyond what any single API offers, consider combining a news API for broad coverage with targeted RSS scraping through proxies for specific sources that matter most to your use case.

  • Best News APIs in 2026: Top Solutions Ranked and Compared

    TL;DR
    the top news APIs in 2026 are NewsAPI.org, The Guardian API, GDELT, and Mediastack. for real-time news scraping without API limits, combine RSS feeds with a lightweight Python crawler.

    news data feeds dozens of use cases: sentiment analysis, event detection, content aggregation, financial signal extraction, and competitive monitoring. the right API depends on volume requirements, geo coverage, and budget.

    1. newsapi.org

    the most developer-friendly entry point. free tier covers 100 requests/day. paid plans start at $449/month. covers 80,000+ sources in 50 languages.

    import requests
    API_KEY = 'your_newsapi_key'
    params = {'q': 'web scraping', 'language': 'en', 'sortBy': 'publishedAt', 'apiKey': API_KEY}
    r = requests.get('https://newsapi.org/v2/everything', params=params)
    for a in r.json().get('articles', []):
        print(a['title'], '|', a['publishedAt'])

    2. newscatcher api

    a news data API focused on clean, enriched, ready-to-use news data. the Newscatcher API automatically clusters similar articles to cut duplicate noise, then layers entity resolution and industry-standard tagging on top of advanced source, country, language, and topic filtering — a strong fit for media monitoring, sentiment analysis, and financial signal extraction.

    3. the guardian api

    completely free for non-commercial use with 500 requests/day. covers all Guardian content back to 1999.

    params = {'q': 'artificial intelligence', 'api-key': 'your_key', 'show-fields': 'body', 'page-size': 10}
    r = requests.get('https://content.guardianapis.com/search', params=params)
    for item in r.json()['response']['results']:
        print(item['webTitle'], '|', item['webPublicationDate'])

    4. gdelt project

    GDELT is a free, massive database of news events updated every 15 minutes. covers 100+ languages from 1979 to present. free to query via BigQuery. use BigQuery’s SQL interface to filter by actor, event type, country, and date range.

    5. mediastack

    affordable pricing starting at $9.99/month for 10,000 requests. covers 7,500+ sources in 50+ countries. good for startups needing more volume than free tiers allow.

    6. currents api

    free tier includes 600 requests/day. covers 70,000+ sources. useful for independent developers building news apps on zero budget.

    when to scrape instead of using an api

    APIs have limitations: they do not cover every site, they impose rate limits, and they charge per request at scale. direct scraping via RSS is often cheaper and faster for specific sources.

    import feedparser
    feed = feedparser.parse('https://feeds.bbci.co.uk/news/technology/rss.xml')
    for entry in feed.entries[:5]:
        print(entry.title, '|', entry.link)

    RSS feeds are structured, machine-readable, and updated frequently. most major publishers still maintain them. see our guide on what is web scraping for broader context.

    summary comparison

    API free tier cost sources
    NewsAPI 100 req/day $449+/mo 80,000+
    Newscatcher demo custom global
    The Guardian 500 req/day free 1 (quality)
    GDELT unlimited free millions
    Mediastack limited $9.99+/mo 7,500+
    Currents 600 req/day free 70,000+

    for scraping at volume, route requests through a rotating proxy. see our comparison of SOCKS5 vs HTTP proxy and what is a proxy server.

    sources and further reading

    related guides

  • Best Data Marketplaces and Dataset Websites in 2026

    TL;DR
    whether you are buying data to avoid scraping or selling data you have collected, these are the platforms worth knowing in 2026, with honest assessments of pricing, data quality, and buyer/seller experience.

    why use a data marketplace

    scraping everything yourself costs time, infrastructure, and anti-bot bypass work. for common datasets (company firmographics, consumer demographics, financial data), buying from a marketplace is often cheaper than building the pipeline. on the sell side, marketplaces give scrapers a distribution channel without building a customer acquisition machine.

    commercial data marketplaces

    Snowflake Data Marketplace

    the largest B2B data marketplace by revenue. strength is the zero-copy sharing model: data stays in Snowflake, you query it directly in your own account without ETL. 2,000+ listings across financial, demographic, weather, and alternative data. requires a Snowflake account (minimum $25/month on pay-as-you-go). best for: enterprise teams already in the Snowflake ecosystem.

    AWS Data Exchange

    Amazon’s data marketplace, integrated into S3 and AWS services. 3,500+ datasets. strong in financial data, healthcare, and satellite imagery. delivery is S3-based; you subscribe and data lands in your bucket. pricing ranges from free to $50,000+/month for premium financial feeds. best for: teams already on AWS who need automated data delivery into their pipelines.

    Databricks Marketplace

    newer than Snowflake but growing fast. similar zero-copy model for Delta Lake tables. strong in ML training datasets and AI-specific data products. if you are using Databricks for ML pipelines, check here before scraping training data yourself.

    Datarade

    a data marketplace aggregator that lists datasets from 1,000+ providers and lets you compare them. not a data host itself. search by data type, geography, update frequency, and delivery format. strong in B2B contact data, location data, and web data.

    free and open datasets

    Hugging Face Datasets

    the default destination for ML training data in 2025-2026. 100,000+ datasets, free to download. strongest in NLP, computer vision, and tabular ML:

    from datasets import load_dataset
    ds = load_dataset("wikipedia", "20220301.en", split="train[:1%]")
    print(ds[0]["text"][:500])

    Kaggle Datasets

    130,000+ user-contributed datasets. best for: historical financial data, sports statistics, public health records, and competition datasets. API access via the kaggle CLI makes bulk downloading easy.

    data.gov and equivalents

    US federal datasets: 300,000+ datasets across all agencies. equivalent platforms: data.gov.uk (UK), data.gov.sg (Singapore), data.europa.eu (EU). strong in economic statistics, health data, geospatial data, and census data.

    World Bank Open Data

    macroeconomic and development indicators for 217 countries, 1960-present. the API is clean and well-documented. Python wrapper: wbgapi. essential for any economic research or content involving global statistics.

    alternative data sources

    Nasdaq Data Link (formerly Quandl)

    financial and alternative data. free tier includes most economic data; premium tiers ($50-500+/month) cover equity fundamentals, options data, and alternative signals. useful for: backtesting, financial research, investment content.

    Common Crawl

    the largest freely available web crawl. monthly snapshots, 250-300TB of compressed data per crawl. hosted on S3 via AWS Open Data. process it with Athena, Spark, or the cdx-toolkit Python library for targeted queries. use this if you need historical web content without scraping it yourself.

    selling your data

    if you have built a proprietary dataset, the fastest path to revenue is Datarade (for B2B data buyers), Gumroad or Lemon Squeezy (for one-time CSV sales), or building a direct API product. niche datasets (e.g., weekly Amazon pricing data for a specific product category, daily SERP rankings for an industry) are more valuable than broad commodity datasets. see how to monetize web scraping for the full playbook.

    sources and further reading

    related guides

  • 25+ Web Scraping Project Ideas for Beginners to Advanced (2026)

    TL;DR
    25+ concrete web scraping projects ranked by difficulty, with data sources, tech stack recommendations, and monetization angles. skip the toy tutorials and build something that actually produces value.

    how to pick the right project

    the best scraping projects have three things: a data source that updates regularly, a use case that justifies the infrastructure cost, and a clear output format (CSV, API, dashboard, or product). one-shot scrapes of static data are not worth engineering. aim for pipelines that run on a schedule and compound over time.

    the projects below are grouped by difficulty. beginner means you can do it with requests and BeautifulSoup on a single IP. intermediate requires rotating proxies or browser automation. advanced requires custom anti-bot bypass, distributed infrastructure, or real-time processing.

    beginner projects

    1. job listing aggregator

    scrape LinkedIn, Indeed, and Glassdoor for specific job titles across multiple cities. store in SQLite, detect new listings, send email alerts. stack: requests, BeautifulSoup, SQLite, smtplib. LinkedIn requires proxy rotation even at beginner scale.

    2. e-commerce price tracker

    track prices for a list of Amazon ASINs or Shopify product URLs. store daily snapshots, alert on drops. stack: requests, lxml, PostgreSQL. Amazon needs curl-cffi at minimum; Shopify stores are usually open.

    3. real estate listing monitor

    scrape Zillow, Realtor.com, or local MLS aggregators for new listings matching criteria. Zillow has heavy bot protection; start with smaller regional sites. output: Telegram notifications per new match.

    4. twitter/x keyword monitor

    use the Nitter frontend to scrape tweets without API costs. track brand mentions, competitor names, or industry keywords. stack: requests, Nitter, JSON storage.

    5. news sentiment tracker

    scrape Google News for a topic using tbm=nws, run headlines through a sentiment model (VADER or GPT-4o-mini), chart sentiment over time. useful for financial research and brand monitoring.

    intermediate projects

    6. serp rank tracker

    track Google positions for a list of keywords and URLs. requires proxy rotation to avoid IP blocks. store daily snapshots, detect rank changes, generate weekly reports. see our web scraping fundamentals guide for the technical foundation.

    7. domain expiry monitor

    scrape WHOIS data for a list of domains, alert when expiry is within 30 days. resell as a service to agencies. stack: python-whois, CRON, Telegram bot.

    8. linkedin company data scraper

    scrape employee count, job openings, and follower growth for a list of companies. LinkedIn has aggressive bot detection; requires Playwright and residential proxies. output: weekly CSV for sales intelligence.

    9. product review aggregator

    pull reviews from Amazon, Trustpilot, G2, and Capterra for a product category. run through LLM to extract common complaints and feature requests. useful for competitive intelligence and product research.

    10. stock news pipeline

    scrape Yahoo Finance, MarketWatch, and Seeking Alpha for ticker-specific news. correlate news volume with price movement. requires fast scraping (sub-5 minute latency for breaking news). stack: async aiohttp, Redis queue, TimescaleDB.

    11. hotel rate monitor

    track Booking.com and Expedia rates for specific properties over a 90-day forward window. rates change dynamically; scrape 2x per day. requires residential proxies. output: price matrix by check-in date.

    12. github trend monitor

    scrape GitHub trending (no auth required), track stars-per-hour for new repos, alert on repos breaking 100 stars in first 24 hours. clean data source, no bot protection. great for finding emerging tools early.

    advanced projects

    13. amazon asin bulk scraper

    scrape product details, BSR rank, pricing history, and review count for 10,000+ ASINs. Amazon uses Imperva; you need the full bypass stack. output: product intelligence database for private label sellers.

    14. court records monitor

    scrape PACER (federal) or state court systems for new filings on specific companies or individuals. legal intelligence service. some courts require browser automation; others have open XML feeds.

    15. flight price matrix

    scrape Google Flights, Kayak, or Skyscanner for origin-destination pairs across 90 days. each site requires browser automation. output: fare prediction model or cheap-flight alert service.

    16. patent monitor

    scrape USPTO and Google Patents for new filings by competitor companies. track patent activity as a signal for R&D direction. clean data source with reasonable rate limits.

    17. social media follower tracker

    track follower counts and engagement rates for competitor accounts across Instagram, TikTok, and YouTube. each requires different bypass approaches. output: weekly competitive benchmarking report.

    18. real-time sports odds aggregator

    scrape odds from 20+ bookmakers, compute arbitrage opportunities in real-time. latency matters here; you need async scraping with sub-second refresh cycles. stack: aiohttp, Redis, FastAPI dashboard.

    19. influencer database builder

    scrape Instagram and TikTok for creators in a niche by hashtag. extract follower count, engagement rate, contact info from bio. sell as a SaaS tool to agencies. requires aggressive proxy rotation and browser automation.

    20. glassdoor salary database

    scrape Glassdoor salary reports by role, company, and location. Glassdoor requires login for most data; needs account pooling and residential proxies. output: compensation benchmarking dataset.

    monetization angles

    21-25. data-as-a-service projects

    any of the above can become a data product. the playbook: scrape the data, clean it, expose it via a simple API with Stripe-gated access. projects 6 (SERP tracking), 9 (review aggregation), 11 (hotel rates), and 15 (flight prices) have proven markets with existing paid competitors.

    for distribution, the fastest path to revenue is selling CSV exports to small operators rather than building a full SaaS. list on Gumroad or Lemon Squeezy, generate fresh exports weekly, update the listing. no infra required beyond a cron job and file storage.

    tech stack by difficulty

    beginner: requests, BeautifulSoup4, pandas, SQLite, CRON. intermediate: curl-cffi, playwright, PostgreSQL, Redis, Celery. advanced: distributed Playwright workers, residential and mobile proxy pools, Kafka or Redis Streams for real-time, ClickHouse for analytics. see SOCKS5 vs HTTP proxy for routing considerations at scale.

    sources and further reading

    related guides

  • Google Search URL Parameters: Complete 2026 Reference

    TL;DR
    Google’s search URL accepts dozens of undocumented parameters that control results, date filters, language, location, and output format. this is the working reference for 2026, sourced from reverse-engineering and official documentation.

    the base url structure

    every Google search starts at https://www.google.com/search. the query string carries all the search configuration. the minimum required parameter is q (the query). everything else is optional but powerful.

    for scraping purposes, always target google.com with explicit gl and hl parameters rather than country-specific domains. the gl parameter gives you cleaner, more predictable results and avoids regional redirect chains.

    core parameters

    q: query

    the search query. URL-encode it. spaces become + or %20. use urllib.parse.quote_plus() in Python. advanced operators (site:, intitle:, filetype:) go inside q.

    num: results per page

    valid values: 10 (default), 20, 30, 50, 100. setting num=100 gives you the full first page in one request, which is essential for efficient scraping. SERP quality degrades beyond position 30; positions 31-100 are often thin or duplicate content.

    start: pagination offset

    zero-indexed. start=0 is page 1, start=10 is page 2 (with default num=10). combine with num: num=100&start=0 fetches 100 results in one shot.

    gl: geolocation country

    two-letter country code. gl=us, gl=gb, gl=sg. for rank tracking, always fix gl so results are consistent across requests.

    hl: interface language

    hl=en forces English interface. if you omit this, Google infers language from IP location, which breaks scraping consistency when you rotate proxies across regions.

    lr: language restrict

    restricts results to pages in a specific language. format: lr=lang_en, lr=lang_zh-TW. different from hl: hl controls the UI language, lr controls the language of pages returned.

    date and freshness parameters

    tbs: time-based search

    values: tbs=qdr:h (past hour), tbs=qdr:d (past 24 hours), tbs=qdr:w (past week), tbs=qdr:m (past month), tbs=qdr:y (past year), tbs=cdr:1,cd_min:1/1/2025,cd_max:12/31/2025 (custom date range). for news monitoring pipelines, tbs=qdr:h pairs with tbm=nws for news-specific results.

    result type parameters

    tbm: type of search

    • tbm=nws – Google News
    • tbm=isch – Google Images
    • tbm=vid – Google Videos
    • tbm=shop – Google Shopping
    • tbm=bks – Google Books

    output and format parameters

    filter

    filter=0 disables duplicate filtering and the omitted similar results cluster. always set this when scraping for comprehensive results. filter=1 (default) silently drops results Google considers duplicates.

    nfpr

    nfpr=1 disables automatic query corrections, which is critical for rank tracking exact queries.

    a working python scraping example

    import urllib.parse
    from curl_cffi import requests as cffi_requests
    
    def google_serp(query, num=10, gl="us", tbs=None):
        params = {"q": query, "num": str(num), "gl": gl, "hl": "en", "filter": "0", "nfpr": "1"}
        if tbs:
            params["tbs"] = tbs
        url = "https://www.google.com/search?" + urllib.parse.urlencode(params)
        s = cffi_requests.Session(impersonate="chrome120")
        return s.get(url, headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36", "Accept-Language": "en-US,en;q=0.9"}).text
    
    html = google_serp("web scraping python", num=100, gl="us", tbs="qdr:m")

    parameters to avoid

    some parameters seen in older guides no longer work or actively trigger bot detection. pws=0 (disable personalization) was removed in 2021. as_sitesearch works but is slower than using site: inside q. complete=0 has no effect on server-side responses.

    sources and further reading

    related guides

  • Best Data Marketplaces and Dataset Websites in 2026

    Best Data Marketplaces and Dataset Websites in 2026

    whether you’re looking for training data for an ML model, market research datasets, or specific industry data, there are dozens of marketplaces and repositories where you can find what you need. some are free, some charge per dataset, and others operate on subscription models.

    this guide covers the best data marketplaces and dataset websites, organized by type, with details on pricing, data quality, and what each platform does best.

    Free Data Repositories

    1. Kaggle Datasets

    URL: kaggle.com/datasets
    Cost: free
    Best for: machine learning datasets, competitions, data science practice

    Kaggle is the largest community-driven dataset repository. with over 250,000 public datasets, it covers everything from CSV files to image datasets to time series data.

    strengths:
    – massive variety of datasets
    – community discussions and notebooks for each dataset
    – version control for datasets
    – direct integration with Kaggle notebooks (free GPU included)

    limitations:
    – quality varies significantly (user-uploaded data)
    – many datasets are outdated or poorly documented
    – large datasets can be slow to download

    2. Hugging Face Datasets

    URL: huggingface.co/datasets
    Cost: free (with paid storage tiers)
    Best for: NLP datasets, AI/ML training data, multimodal datasets

    Hugging Face has become the de facto standard for ML datasets, especially for NLP. their datasets library makes it trivial to load data directly into Python.

    from datasets import load_dataset
    
    # load a dataset directly into memory
    dataset = load_dataset("imdb")
    print(f"training examples: {len(dataset['train'])}")
    print(f"test examples: {len(dataset['test'])}")
    
    # load a specific subset
    squad = load_dataset("squad", split="train[:1000]")
    

    strengths:
    – excellent documentation and dataset cards
    – direct Python API for loading data
    – built-in data preprocessing tools
    – strong community curation

    limitations:
    – focused primarily on ML/NLP data
    – some large datasets require significant bandwidth

    URL: datasetsearch.research.google.com
    Cost: free (search engine only)
    Best for: discovering datasets across the entire web

    Google Dataset Search indexes datasets from thousands of sources including government portals, research institutions, and data repositories. it doesn’t host data itself but helps you find it.

    strengths:
    – searches across millions of datasets
    – filters by update date, format, and license
    – covers academic, government, and commercial sources

    limitations:
    – only a search engine, not a data host
    – results quality varies
    – some indexed datasets are behind paywalls

    4. data.gov (US Government)

    URL: data.gov
    Cost: free
    Best for: US government data (demographics, health, environment, transportation)

    the US government’s open data portal contains over 300,000 datasets from federal agencies. the data is public domain and freely usable.

    strengths:
    – authoritative government data
    – regular updates on many datasets
    – API access for programmatic downloads
    – covers virtually every topic the federal government touches

    limitations:
    – data formats vary (CSV, XML, JSON, shapefiles)
    – some “datasets” are just links to agency websites
    – documentation quality varies by agency

    5. UCI Machine Learning Repository

    URL: archive.ics.uci.edu/ml
    Cost: free
    Best for: classic ML benchmark datasets

    the UCI repository is one of the oldest dataset collections, used extensively in academic ML research. many foundational ML papers used UCI datasets for benchmarking.

    strengths:
    – well-documented datasets
    – established benchmarks for comparison
    – clean, ready-to-use data

    limitations:
    – relatively small collection (~600 datasets)
    – many datasets are small by modern standards
    – limited variety compared to newer platforms

    6. World Bank Open Data

    URL: data.worldbank.org
    Cost: free
    Best for: global economic indicators, development data, country statistics

    the World Bank provides free access to thousands of indicators for every country, spanning decades of data.

    import requests
    import pandas as pd
    
    def get_world_bank_data(indicator, country="all", date_range="2010:2025"):
        """fetch data from the World Bank API"""
        url = f"https://api.worldbank.org/v2/country/{country}/indicator/{indicator}"
        params = {
            "date": date_range,
            "format": "json",
            "per_page": 1000,
        }
    
        response = requests.get(url, params=params)
        data = response.json()
    
        if len(data) < 2:
            return pd.DataFrame()
    
        records = [
            {
                "country": item["country"]["value"],
                "year": item["date"],
                "value": item["value"],
            }
            for item in data[1]
            if item["value"] is not None
        ]
    
        return pd.DataFrame(records)
    
    # example: GDP per capita
    gdp_data = get_world_bank_data("NY.GDP.PCAP.CD", country="SGP;USA;GBR")
    print(gdp_data.head(10))
    

    7. European Data Portal

    URL: data.europa.eu
    Cost: free
    Best for: EU government data across all member states

    8. AWS Open Data Registry

    URL: registry.opendata.aws
    Cost: free (data is free, compute on AWS is not)
    Best for: large-scale datasets (satellite imagery, genomics, weather)

    Commercial Data Marketplaces

    9. Datarade

    URL: datarade.ai
    Cost: varies by provider (marketplace model)
    Best for: finding and comparing commercial data providers

    Datarade is a marketplace that connects data buyers with data providers. it doesn’t sell data directly but helps you discover, compare, and purchase from hundreds of data vendors.

    strengths:
    – 2,000+ data providers listed
    – detailed provider profiles and reviews
    – price transparency (many list pricing)
    – covers every data category imaginable

    limitations:
    – just a marketplace, you buy from individual providers
    – quality verification depends on the provider
    – can be overwhelming with too many options

    10. Snowflake Marketplace

    URL: app.snowflake.com/marketplace
    Cost: free to browse, pricing varies by dataset
    Best for: enterprise-ready datasets that integrate directly with Snowflake

    Snowflake Marketplace offers datasets that load directly into your Snowflake data warehouse. this eliminates the ETL step for Snowflake users.

    strengths:
    – seamless integration with Snowflake
    – live data (many datasets update automatically)
    – enterprise-grade data quality
    – free datasets available from major providers

    limitations:
    – requires a Snowflake account
    – most valuable datasets are paid
    – limited to Snowflake ecosystem

    11. Databricks Marketplace

    URL: databricks.com/product/marketplace
    Cost: varies
    Best for: data, analytics, and ML models for Databricks users

    similar to Snowflake Marketplace but for the Databricks ecosystem. includes datasets, notebooks, and ML models.

    12. AWS Data Exchange

    URL: aws.amazon.com/data-exchange
    Cost: varies by dataset (subscription or one-time)
    Best for: financial data, weather data, healthcare data on AWS

    AWS Data Exchange lets you subscribe to data products from 300+ providers and load them directly into S3. billing goes through your AWS account.

    strengths:
    – direct S3 integration
    – automatic data delivery on schedule
    – API access for programmatic queries
    – reputable providers (Reuters, Dun & Bradstreet, Foursquare)

    limitations:
    – requires AWS account
    – premium datasets are expensive
    – primarily US-focused providers

    13. Bright Data Datasets

    URL: brightdata.com/products/datasets
    Cost: per-dataset pricing
    Best for: web-scraped ecommerce, social media, and business data

    Bright Data offers pre-scraped datasets from major websites. instead of building your own scraper, you buy the data directly.

    strengths:
    – data from sources that are hard to scrape (Amazon, LinkedIn, Google)
    – regular updates
    – structured, clean data
    – custom dataset requests available

    limitations:
    – expensive for large datasets
    – freshness depends on update schedule
    – limited customization on standard offerings

    14. Oxylabs Datasets

    URL: oxylabs.io
    Cost: custom pricing
    Best for: SERP data, ecommerce data, real estate data

    similar to Bright Data, Oxylabs offers pre-collected web data alongside their proxy and scraping infrastructure.

    15. Crunchbase

    URL: crunchbase.com
    Cost: $29-$99/month for data access
    Best for: startup and company data, funding rounds, acquisitions

    Crunchbase is the leading database for startup and company information. their API and data exports are widely used in business intelligence and venture capital.

    Specialized Data Platforms

    16. Statista

    URL: statista.com
    Cost: from $39/month
    Best for: statistics, market data, survey results, infographics

    Statista aggregates statistics from thousands of sources and presents them in a searchable, downloadable format. excellent for market research and presentations.

    URL: data.nasdaq.com
    Cost: free tier available, premium datasets from $50/month
    Best for: financial and economic data

    Quandl provides financial, economic, and alternative data. their free tier covers many public datasets, while premium data includes proprietary financial metrics.

    import nasdaqdatalink
    
    # set your API key
    nasdaqdatalink.ApiConfig.api_key = "YOUR_API_KEY"
    
    # get stock data
    data = nasdaqdatalink.get("WIKI/AAPL", start_date="2020-01-01", end_date="2025-12-31")
    print(data.head())
    
    # get economic indicators
    fed_rate = nasdaqdatalink.get("FRED/DFF")
    print(fed_rate.tail())
    

    18. Eurostat

    URL: ec.europa.eu/eurostat
    Cost: free
    Best for: European economic and social statistics

    19. FRED (Federal Reserve Economic Data)

    URL: fred.stlouisfed.org
    Cost: free
    Best for: US economic data (GDP, inflation, employment, interest rates)

    20. OpenStreetMap Data

    URL: openstreetmap.org
    Cost: free
    Best for: geographic and mapping data

    Academic and Research Data

    21. Zenodo

    URL: zenodo.org
    Cost: free
    Best for: research datasets from academic papers

    Zenodo is CERN’s open data repository. researchers deposit their datasets here alongside their papers, ensuring reproducibility.

    22. Figshare

    URL: figshare.com
    Cost: free to access
    Best for: research data, figures, and supplementary materials

    23. Papers With Code Datasets

    URL: paperswithcode.com/datasets
    Cost: free
    Best for: ML benchmark datasets linked to research papers

    24. Academic Torrents

    URL: academictorrents.com
    Cost: free
    Best for: large research datasets distributed via BitTorrent

    Building Your Own Datasets

    when existing datasets don’t meet your needs, web scraping lets you build custom datasets. here’s a framework for creating and maintaining your own data collection pipeline.

    from curl_cffi import requests
    from bs4 import BeautifulSoup
    import pandas as pd
    import json
    import hashlib
    from datetime import datetime
    
    class DatasetBuilder:
        def __init__(self, name, proxy=None):
            self.name = name
            self.session = requests.Session(impersonate="chrome124")
            self.proxy = {"http": proxy, "https": proxy} if proxy else None
            self.records = []
            self.metadata = {
                "name": name,
                "created": datetime.now().isoformat(),
                "source_count": 0,
                "record_count": 0,
            }
    
        def add_source(self, url, parser_func):
            """add a data source with its parser function"""
            response = self.session.get(
                url,
                headers={
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
                },
                proxies=self.proxy,
            )
    
            if response.status_code == 200:
                new_records = parser_func(response.text)
                self.records.extend(new_records)
                self.metadata["source_count"] += 1
                self.metadata["record_count"] = len(self.records)
                print(f"added {len(new_records)} records from {url}")
    
        def deduplicate(self, key_fields):
            """remove duplicate records based on key fields"""
            seen = set()
            unique = []
            for record in self.records:
                key = hashlib.md5(
                    json.dumps({k: record.get(k) for k in key_fields}, sort_keys=True).encode()
                ).hexdigest()
                if key not in seen:
                    seen.add(key)
                    unique.append(record)
    
            removed = len(self.records) - len(unique)
            self.records = unique
            self.metadata["record_count"] = len(unique)
            print(f"removed {removed} duplicates, {len(unique)} unique records remain")
    
        def export(self, format="csv"):
            """export the dataset"""
            df = pd.DataFrame(self.records)
    
            if format == "csv":
                filename = f"{self.name}.csv"
                df.to_csv(filename, index=False)
            elif format == "json":
                filename = f"{self.name}.json"
                df.to_json(filename, orient="records", indent=2)
            elif format == "parquet":
                filename = f"{self.name}.parquet"
                df.to_parquet(filename)
    
            # save metadata
            with open(f"{self.name}_metadata.json", "w") as f:
                json.dump(self.metadata, f, indent=2)
    
            print(f"exported {len(df)} records to {filename}")
            return filename
    

    when building your own datasets through scraping, residential proxies help you collect data reliably without getting blocked. use the Proxy Cost Calculator to estimate costs based on your data volume needs.

    How to Choose the Right Data Source

    Need Best Option Why
    ML training data Hugging Face, Kaggle purpose-built for ML workflows
    market research Statista, Datarade curated, verified data
    financial data Quandl, FRED authoritative sources
    government statistics data.gov, World Bank official, free
    custom web data build your own + proxy service exactly what you need
    company/startup data Crunchbase comprehensive coverage
    geospatial data OpenStreetMap open, community-maintained
    academic/research Zenodo, Figshare peer-reviewed, reproducible

    Data Quality Checklist

    before using any dataset, verify these points:

    1. recency – when was the data last updated? stale data leads to wrong conclusions
    2. completeness – what percentage of fields are filled? look for null rates
    3. consistency – are formats consistent across records? check date formats, currency symbols, encoding
    4. provenance – where did the data originally come from? can you trace it back to the source?
    5. license – what are you allowed to do with the data? check commercial use restrictions
    6. sample first – always examine a sample before committing to a dataset or a purchase

    Summary

    the data marketplace ecosystem in 2026 offers options for every budget and use case. free repositories like Kaggle, Hugging Face, and government portals cover a massive range of topics. commercial marketplaces like Datarade, Snowflake Marketplace, and AWS Data Exchange provide premium, enterprise-grade data. and when no existing dataset fits your needs, web scraping with the right proxy infrastructure lets you build exactly what you want.

    start with free sources to validate your data needs, then move to commercial options when you need higher quality, freshness, or coverage. and always verify data quality before building anything important on top of it.