Web Scraping With LangChain Loaders
LangChain web scraping tested: what WebBaseLoader really returns on a live page, and two measured ways to wire in a local scraper via MCP or the CLI.

On this page
Web Scraping With LangChain Loaders
LangChain ships a shelf of document loaders that fetch web pages, and every tutorial on langchain web scraping stops at WebBaseLoader without asking what it actually hands your model. I measured it: on a live NPR article, 52% of the loader’s output was navigation junk your RAG pipeline will embed anyway. One thing up front, because every honest guide should say it in the first paragraph: Magpie has no LangChain loader. There is no pip install langchain-magpie. The two supported paths are its MCP server and its CLI, and both are below, with numbers from runs I did today.
Test setup, so you know what the numbers mean: Python 3.12, langchain-community 0.4.2, langchain-mcp-adapters 0.3.2, one article on npr.org (152 KB of raw HTML), home connection, 2026-10-16.
| Test | Tool | Time | Output | Junk in output |
|---|---|---|---|---|
| Load the NPR article | WebBaseLoader |
2.50s | 9,024 chars (~2,250 est. tokens) | ~52% nav, captions, whitespace |
| Scrape the same article | magpie scrape CLI |
2.81s | 5,559 chars of clean markdown | None found |
| Wire Magpie into LangChain | langchain-mcp-adapters |
0.35s connect, 3.22s first scrape | Same 5,559-char markdown over MCP | None |
What LangChain gives you for scraping, in 2026
The web loaders live in langchain_community.document_loaders, a package whose own import now prints a warning you should read:
DeprecationWarning: `langchain-community` is being sunset and is no
longer actively maintained. See https://github.com/langchain-ai/langchain-community/issues/674
Sunset means the loaders still work, but they are frozen. The package mostly re-exports langchain-classic 1.0.8 now, and LangChain’s own docs have moved on: the old use-case page at python.langchain.com/docs/use_cases/web_scraping/ redirects to a Deep Agents RAG tutorial that skips HTML entirely. LangChain publishes its own documentation as markdown (docs.langchain.com/{path}.md plus an llms.txt), and their example fetches those files with plain requests.
That redirect is the current state of the art in one sentence: the LangChain team stopped scraping HTML pages and started asking sites for markdown. Most sites won’t give it to you. So the loaders:
| Loader | What it really is | The catch |
|---|---|---|
WebBaseLoader |
requests + BeautifulSoup, one page per call |
Static HTML only; returns one text blob with nav junk in it |
AsyncChromiumLoader |
Real Chromium via Playwright | You install browser binaries; output still needs a cleaning transformer |
RecursiveUrlLoader |
Follows links from a root URL, regex-driven | Depth explodes fast; no politeness budget |
SitemapLoader |
Reads sitemap.xml, fans out per URL |
Only as good as the sitemap; same junk problem per page |
UnstructuredURLLoader |
Delegates to the unstructured package | Extra dependency with its own system requirements |
SpiderLoader |
Client for the Spider SaaS API | Your pages travel to a third party |
For a quick prototype against a static page, WebBaseLoader is fine. The problems start when the payload reaches a model.
Measured: what WebBaseLoader hands your model
Same article, loader first:
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://www.npr.org/2026/10/15/nx-s1-5996678/midlife-wall-reset-test-guide")
docs = loader.load() # 2.50s, one Document, 9,024 chars
The document’s first 200 characters are whitespace, then this, before a word of the article:
Ready for a midlife reset? First, take stock of your strengths : NPR
Accessibility links
Skip to main content
Keyboard shortcuts for audio player
Open Navigation Menu
Newsletters
NPR Shop
More junk trails the article’s last line: “hide caption”, “toggle caption”, footer links. Of 9,024 characters, about 4,272 were the article. The other 4,752 (52%) is boilerplate. At the usual four-characters-per-token estimate, you pay for roughly 2,250 tokens per page and embed about 1,200 of junk. Multiply by a 500-page crawl and the bill is 600,000 wasted tokens, before retrieval mixes them into every prompt.
LangChain’s own cleanup tool, BeautifulSoupTransformer (in document_transformers, re-exported through community), takes a list of tags to strip. Tag surgery is not content detection: “Skip to main content” sits in the same div soup as the article, so the transformer removes what you name and keeps what you didn’t.
Two more details I hit while running this. The loader’s default user agent is the literal string DefaultLangchainUserAgent, and the import prints a suggestion to set USER_AGENT so sites can identify you. A placeholder that names no real client is worse than a normal bot string; it looks like something pretending not to be a bot. And WebBaseLoader fetches static HTML only. It got the article here because NPR serves the text server-side. On a JS-built page it returns the empty shell; the fixes are in scraping dynamic websites.
Now the same URL through Magpie’s CLI:
$ magpie scrape "https://www.npr.org/2026/10/15/nx-s1-5996678/midlife-wall-reset-test-guide" \
--page-format markdown
# 2.81s → JSON record: title, markdown, JSON-LD sidecar
The markdown field is 5,559 characters, 802 words, article text and image captions, nothing else. No nav, no captions chrome, no 40 blank lines. The record also carries the page’s NewsArticle JSON-LD, which is where the model could read publish date and author without guessing. Fewer characters, same content: the boilerplate was never information.
The limit, stated plainly
There is no official Magpie loader for LangChain, and I’m not going to pretend a 20-line pip package would change that. magpie init writes config stanzas for claude-code, claude-desktop, cursor, and generic clients; LangChain is not in that list because LangChain is a library, not an agent client. You wire it yourself, through one of two doors:
- MCP: Magpie’s
servecommand exposes the pipeline as 13 MCP tools over stdio or HTTP, and LangChain talks to it throughlangchain-mcp-adapters. This is the path for agents. - CLI subprocess: you call
magpie scrapefrom Python and wrap the output in aDocument. This is the path for pipelines that already speak Documents.
If your pages are static and you need the text once, stay with WebBaseLoader; it’s 15 seconds of work. The MCP and CLI paths earn their setup when pages are heavy on boilerplate, built by JavaScript, or numerous enough that junk tokens cost real money.
Path 1: LangChain’s MCP client against magpie serve
pip install langchain-mcp-adapters (0.3.2 tested — it pins the mcp SDK to 1.x, so don’t be surprised that it installs mcp 1.30.0 next to a 2.3.0 latest on PyPI). Build the binary first: clone the repo, go build -o magpie ./cmd/magpie, Go 1.26+.
The stanza LangChain needs is the generic one:
$ magpie init --client generic
{
"command": "/home/you/.local/bin/magpie",
"args": ["serve"]
}
stdio, which means the server starts when your Python process connects and dies with it. Nothing listens on a port. Feed that stanza to the adapters client:
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
"magpie": {
"command": "/home/you/.local/bin/magpie",
"args": ["serve"],
"transport": "stdio",
}
})
async def main():
tools = await client.get_tools()
print(len(tools), [t.name for t in tools])
asyncio.run(main())
I ran that. Connect took 0.35s and the tool list came back complete:
13 ['batch', 'brand', 'crawl_site', 'diff', 'extract_structured',
'get_cached_selectors', 'list_extractors', 'map', 'research',
'scrape_url', 'search', 'summarize', 'vertical_scrape']
Each tool arrives as a LangChain tool with its input schema attached; scrape_url alone carries 16 parameters (render, cookies, actions, and so on). You can call one directly, which is how I verified the round trip without involving a model:
scrape = next(t for t in tools if t.name == "scrape_url")
out = await scrape.ainvoke({"url": "https://www.npr.org/2026/10/15/nx-s1-5996678/midlife-wall-reset-test-guide"})
# 3.22s first call, 4.02s second; out = [{'type': 'text', 'text': '<JSON record>'}]
The text block is the same JSON record the CLI prints: final_url, from_cache, markdown with the identical 5,559 characters. In an agent, the loop looks like this, and this is the one step I did not execute because it needs a live model key on your side:
from langchain.agents import create_agent
agent = create_agent("openai:gpt-5.5", tools) # any tool-calling model
result = agent.invoke({"messages": [{"role": "user",
"content": "Summarize https://example.com/article in five bullets"}]})
Give the agent scrape_url, crawl_site, map, and summarize and it can fetch, index, and answer against a site with the markdown path costing zero LLM tokens. The client-agnostic version of this setup, with per-tool notes, is on the MCP web scraping overview; the same server drives Claude and Cursor with the init command. Deploying the agent server-side instead? magpie serve --transport http --addr :8080 runs stateless, no sticky sessions.
Path 2: a custom loader around the CLI
If your code already moves Document objects and you don’t want an MCP client, wrap the CLI. This is the loader I tested:
import json
import subprocess
from langchain_core.document_loaders import BaseLoader
from langchain_core.documents import Document
class MagpieLoader(BaseLoader):
"""One Document per URL; page_content is cleaned markdown."""
def __init__(self, urls: list[str], render: str = "auto"):
self.urls = urls
self.render = render
def lazy_load(self):
for url in self.urls:
proc = subprocess.run(
["magpie", "scrape", url, "--page-format", "markdown",
"--render", self.render],
capture_output=True, text=True, timeout=120,
)
if proc.returncode != 0:
raise RuntimeError(f"magpie failed on {url}: {proc.stderr.strip()}")
rec = json.loads(proc.stdout)
yield Document(
page_content=rec["markdown"],
metadata={"source": rec["url"], "title": rec.get("title", "")},
)
Running it over two URLs (one static, one NPR):
docs = list(MagpieLoader(urls).lazy_load())
# 2 docs in 7.39s
# - 'Quotes to Scrape' | 1,679 chars
# - 'Ready for a midlife reset? …' | 5,559 chars
Clean markdown in page_content, titles in metadata, and a failed scrape raises instead of silently yielding an empty Document, which is the failure mode that ruins a batch job. At higher volume, don’t loop this serially: magpie batch takes up to 100 URLs with bounded concurrency, and magpie map finds the URLs from a sitemap first. The markdown lands ready for RecursiveCharacterTextSplitter, and the case for feeding models cleaned markdown instead of raw HTML is its own comparison.
Where LangChain web scraping still loses
With this wiring, three walls remain, and none of them is Magpie’s pricing.
Sites that wall scrapers stay walled. Magpie detects bot challenges and reports them; it does not defeat CAPTCHAs, by design. X/Twitter, Instagram, Facebook, TikTok, LinkedIn behind login, YouTube comments, Amazon review text: all on the can’t-extract list as of the October 2026 tests. A cloud extraction API with premium proxy pools will get some of those; Magpie won’t, and WebBaseLoader gets none of them.
Login-gated pages need session plumbing either way. Magpie saves cookies per site (magpie profile save) and the MCP scrape_url accepts a cookies parameter, but you are still managing credentials, and that work exists in every architecture. The how-to is in scraping a website with login.
And langchain-community is a dying package. Your loader imports work today, but plan the migration: keep your integration at the edges (like the 40-line loader above, or the MCP client) so the day the loaders move to standalone packages, you change one import.
FAQ
Does LangChain have a built-in web scraper?
It has document loaders: WebBaseLoader and friends fetch a page and return text. They don’t crawl politely, render JavaScript, clean boilerplate, or extract structured fields. Fine for a static page and a prototype; past that you’re gluing those pieces yourself.
Can WebBaseLoader render JavaScript?
No. It fetches raw HTML with requests. AsyncChromiumLoader renders through Playwright if you install its browser binaries, or route the URL through something that already renders, like scrape_url with render: "browser" over MCP.
Is there a Magpie loader for LangChain?
No. There is no official LangChain integration package. The supported paths are the 13-tool MCP server via langchain-mcp-adapters and the CLI wrapped in a custom loader, both shown above with real runs.
Does the Magpie MCP server need an API key? Not for the markdown path: scraping, crawling, mapping, and brand extraction run with zero LLM calls and zero keys. Keys enter the picture only for schema-driven extraction, summarizing, search, and research, and those are bring-your-own. The core is AGPL-3.0-or-later or commercial, free to run locally.
Is it safe to feed scraped pages straight to an agent? Treat every page as untrusted input. Prompt-injection payloads ride in page text, and LangChain’s own docs warn about this. Keep retrieved content as data, never as instructions, and you’ve removed most of the risk.
Pick by the shape of the job
One static page, tonight, no budget: WebBaseLoader and done. A RAG corpus over a real site, where junk tokens compound: the CLI loader, so every Document starts clean. An agent that should decide for itself what to fetch: the MCP server, 13 tools, one stanza. And if someone pitches you a hosted loader API for pages you could scrape locally, price the junk tokens first; 52% of 2,250 per page is real money at volume.


