Firecrawl vs Scrapy vs Playwright vs BeautifulSoup

Firecrawl vs Scrapy vs Playwright vs BeautifulSoup for Python web scraping: the same task in each, how to combine them, and which suits LLM pipelines.

Search for how to scrape a website with Python and you’ll keep running into the same names. They don’t do the same job, though. BeautifulSoup parses HTML you've already fetched. Playwright drives a real browser. Scrapy is a framework for crawling thousands of pages. And Firecrawl does the whole job as an API. The right one depends on what your pages look like, how many you need, and what you do with the text afterwards.

The short version: use BeautifulSoup for static pages, Playwright for JavaScript-heavy sites, Scrapy for large or recurring crawls, and Firecrawl when you want clean markdown or JSON without running browsers or crawlers yourself.

To compare them properly, each tool below gets the same task: fetch a documentation page and turn it into markdown. Markdown is what you'd want if the text is going to a language model, and it shows how much cleanup each tool leaves to you.

Firecrawl vs Scrapy vs Playwright vs BeautifulSoup at a Glance

Managed API

Firecrawl

Least code. Markdown, JSON and crawling from one call.

Crawling framework

Scrapy

Built for large crawls: scheduling, retries, throttling. No JavaScript by default.

Browser automation

Playwright

Renders JavaScript. You write the extraction yourself.

HTML parser

BeautifulSoup

Fast and simple for static pages. Doesn't fetch or render.

Swipe the table sideways to compare all four →

Feature Firecrawl Scrapy Playwright BeautifulSoup
What it is Hosted API, with an open-source core Python crawling framework Browser automation library HTML parsing library
Licence AGPL-3.0 BSD-3-Clause Apache-2.0 MIT
JavaScript rendering ✓ Yes ✗ Not without a plugin ✓ Yes ✗ No
Markdown for LLMs ✓ Built in ✗ Convert it yourself ✗ Convert it yourself ✗ Convert it yourself
Structured extraction ✓ JSON schema or prompt CSS and XPath selectors, item pipelines Selectors you write Selectors you write
Site crawling ✓ Crawl and map ✓ Built in, with scheduling and retries Write it yourself Write it yourself
Anti-bot handling Managed (hosted) Middleware; bring your own proxies Bring your own proxies None
Cost 1,000 free credits a month; from $16 a month Free, plus your compute Free, plus your compute Free

Plans and prices checked November 2026. They change often, so confirm before you sign up.

BeautifulSoup: Simple and Fast for Static Pages

BeautifulSoup only parses. You fetch the page with requests, strip the parts you don't want, and convert the rest to markdown with a library such as markdownify:

pip install requests==2.34.2 beautifulsoup4==4.15.0 markdownify==1.2.3
import requests
from bs4 import BeautifulSoup
from markdownify import markdownify

URL = "https://docs.python.org/3/library/asyncio.html"


def to_markdown(html: str) -> str:
    soup = BeautifulSoup(html, "html.parser")
    for tag in soup(["script", "style", "nav", "header", "footer", "aside"]):
        tag.decompose()
    main = soup.find("main") or soup.find(attrs={"role": "main"}) or soup.body
    return markdownify(str(main), heading_style="ATX")


html = requests.get(URL, timeout=30).text
print(to_markdown(html))

On server-rendered pages like Python's docs, this works well, and it's fast because there's no browser involved. However, on pages built by JavaScript, requests gets an empty shell. The cleanup rules only fit the layout you wrote them for, so every new site means adjusting them. And a site with bot protection returns a challenge page instead of the content.

Playwright: Scrape JavaScript Websites with a Real Browser

Playwright fixes the JavaScript problem by loading the page in a real browser. It installs the browser binary separately:

pip install playwright==1.64.1
playwright install chromium

The fetch changes, but the cleanup reuses the same to_markdown function:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto(URL, wait_until="networkidle")
    html = page.content()
    browser.close()

print(to_markdown(html))

You now get the page as a user sees it, and you can click, scroll or log in before reading it. What you don't get is help with anything else: the markdown conversion, following links, retrying failures and dealing with bot detection are still your code. A browser per page is also heavy. Each one needs a few hundred megabytes of memory, so running many in parallel takes real infrastructure.

If you're coming from Selenium, Playwright does the same job with a simpler API and automatic waiting for elements. If you’d rather not run browsers yourself, hosted browser services such as Steel and Browserbase give you remote browsers you can drive with the same Playwright code.

Scrapy: Web Crawling at Scale

Scrapy is a different kind of tool. Instead of fetching one page, you write a spider that starts at a URL, extracts what it needs, and decides which links to follow. Scrapy handles the scheduling, concurrency, retries, throttling and robots.txt rules for you:

pip install scrapy==2.19.0 markdownify==1.2.3

This spider converts the asyncio page to markdown, then follows links to the other asyncio pages, up to 20 in total:

import scrapy
from markdownify import markdownify


class DocsSpider(scrapy.Spider):
    name = "docs"
    start_urls = ["https://docs.python.org/3/library/asyncio.html"]
    custom_settings = {
        "ROBOTSTXT_OBEY": True,
        "AUTOTHROTTLE_ENABLED": True,
        "CLOSESPIDER_PAGECOUNT": 20,
    }

    def parse(self, response):
        main = response.css("[role=main]").get() or response.css("body").get()
        yield {"url": response.url, "markdown": markdownify(main, heading_style="ATX")}

        for href in response.css("[role=main] a::attr(href)").getall():
            if href.startswith("asyncio"):
                yield response.follow(href, self.parse)

Run it with scrapy runspider docs_spider.py -o docs.jsonl, and you get one JSON line per page. Scrapy only follows each URL once, and it spreads requests out so you don't hammer the site.

Scrapy is the right tool for thousands or millions of pages, where you need control over every request. Like BeautifulSoup, it doesn't run JavaScript. The scrapy-playwright plugin adds a browser for the pages that need one. The markdown cleanup is still yours to write.

Firecrawl: Scrape Any Page to Markdown with One API Call

Firecrawl does the fetching, rendering, cleanup and markdown conversion on its own servers. The same task is a single call:

pip install firecrawl==4.55.0
from firecrawl import Firecrawl

firecrawl = Firecrawl()  # reads FIRECRAWL_API_KEY
doc = firecrawl.scrape(URL, formats=["markdown"], only_main_content=True)
print(doc.markdown)

Firecrawl also crawls and maps whole sites, extracts JSON against a schema, and handles bot protection on the hosted service. The free plan includes 1,000 credits a month, with one credit per page, and paid plans start at $16 a month, billed yearly. The core is open source under AGPL-3.0 if you'd rather self-host, but some of the anti-bot handling is only in the hosted version. My Firecrawl guide covers crawling, mapping and extraction in more depth.

Using Scrapy, Playwright and BeautifulSoup Together

These tools aren't mutually exclusive, and production scrapers often use two. Playwright renders a page and BeautifulSoup parses page.content(), which suits people who prefer BeautifulSoup's API to Playwright's locators. Scrapy with the scrapy-playwright plugin crawls most pages over plain HTTP and opens a browser only for the ones that need JavaScript, so you don't pay the browser cost on every request. And Firecrawl can replace just the hard part: use Scrapy or your own code to decide which URLs to fetch, and send the difficult ones to Firecrawl.

Before reaching for a browser at all, open the page with your browser's developer tools and check the Network tab. Many JavaScript-heavy sites load their data from a JSON endpoint, and calling that directly with requests is faster and more reliable than rendering the page.

Web Scraping for LLMs and RAG

If the scraped text is going to a language model, the output format matters as much as the fetch. HTML wastes most of a context window on markup, navigation and scripts. Markdown with only the main content keeps the structure the model uses, such as headings, lists and code blocks, at a fraction of the tokens.

That's the dividing line in this comparison. BeautifulSoup, Playwright and Scrapy all leave the markdown conversion to you, and the cleanup rules in the examples above need to be adjusted for every site you add. Firecrawl returns markdown that's already cleaned.

Firecrawl Alternatives and Other Scraping Tools

Selenium is the older standard for browser automation and still had over 28 million Python downloads last month, though Playwright now has more than three times that. It’s mainly used for testing now, and for new scraping work Playwright is usually the easier choice.

Crawl4AI is an open-source crawler built on Playwright, aimed at LLM pipelines. It returns cleaned markdown and supports deep crawls and LLM-based extraction. It's the closest self-hosted alternative to Firecrawl.

Jina Reader is the quickest option to try: put https://r.jina.ai/ in front of any URL and you get markdown back. It works without an API key at 20 requests a minute.

Frequently Asked Questions

What's the best Python library for web scraping? There isn't one best library, because they solve different problems. BeautifulSoup is the easiest to start with, Playwright handles JavaScript, Scrapy is the most capable for large crawls, and Firecrawl needs the least code.

Is Firecrawl better than Scrapy? For small to medium jobs where you want clean output fast, Firecrawl needs far less code and handles JavaScript and bot protection for you. For very large crawls where cost per page matters and you want full control, Scrapy is usually cheaper to run.

Is Scrapy better than BeautifulSoup? They do different jobs. BeautifulSoup parses a page you've already downloaded. Scrapy downloads, schedules and crawls pages, and includes its own selectors. Many projects use BeautifulSoup for small scripts and Scrapy once they're crawling at scale.

Can BeautifulSoup or Scrapy scrape JavaScript websites? Not on their own, because neither runs JavaScript. Fetch the page with Playwright first, or add the scrapy-playwright plugin to Scrapy.

Is Playwright better than Selenium for web scraping? For most new projects, yes. Playwright waits for elements automatically, supports Chromium, Firefox and WebKit through one API, and handles modern single-page apps well. Selenium is still widely used, especially in existing test suites.

Subscribe to alphasec

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe