Web Scraping + Summarization Pipeline with LangChain
Posted on Wed 19 August 2026 in GenAI
You don't always need a fancy dataset. Sometimes the data you need is already live on the web — you just have to go get it and make sense of it. That's exactly what this pipeline does: scrape a URL, chunk the content, and summarize it using an LLM, all wired together with LangChain.
What you're building
The pipeline has three stages:
- Fetch raw HTML from a URL
- Parse and clean it into readable text
- Pass that text through an LLM with a summarization prompt
LangChain handles the glue — loaders, text splitters, chains — so you spend less time on plumbing and more time on what actually matters.
Dependencies
pip install langchain langchain-community langchain-openai \
beautifulsoup4 requests tiktoken
You'll also need an OpenAI API key (or swap in any LLM provider LangChain supports).
Step 1 — Scrape the page
LangChain's WebBaseLoader does this with one line:
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://example.com/article")
docs = loader.load()
Under the hood it fetches the page with requests, strips tags using BeautifulSoup, and returns a list of Document objects. Each document has .page_content (the text) and .metadata (source URL, title, etc.).
If you're scraping a JavaScript-heavy site, WebBaseLoader won't work — it only processes static HTML. Use SeleniumURLLoader or PlaywrightURLLoader instead:
from langchain_community.document_loaders import SeleniumURLLoader
loader = SeleniumURLLoader(urls=["https://js-heavy-site.com"])
docs = loader.load()
Step 2 — Split the text
LLMs have context limits. A long article will overflow the context window if you send it all at once. RecursiveCharacterTextSplitter breaks the content into chunks that fit:
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=2000,
chunk_overlap=200
)
chunks = splitter.split_documents(docs)
chunk_overlap keeps a little context between adjacent chunks so the LLM doesn't lose thread between them. Tune chunk_size based on your model's token limit — 2000 characters is a safe starting point for most GPT-3.5/4 setups.
Step 3 — Summarize with a chain
LangChain has built-in summarization chains. load_summarize_chain wires together the LLM and a prompt strategy:
from langchain_openai import ChatOpenAI
from langchain.chains.summarize import load_summarize_chain
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
chain = load_summarize_chain(llm, chain_type="map_reduce")
summary = chain.run(chunks)
print(summary)
stuff — Concatenates all chunks and sends them in one prompt. Works for short documents. Breaks on anything long.
map_reduce — Summarizes each chunk separately (map), then summarizes the summaries (reduce). Handles long content well, costs more tokens.
refine — Starts with the first chunk's summary, then iteratively refines it with each next chunk. Produces more coherent output than map_reduce but is slower.
For most web articles, map_reduce is the right default.
Full pipeline
import os
from langchain_community.document_loaders import WebBaseLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI
from langchain.chains.summarize import load_summarize_chain
os.environ["OPENAI_API_KEY"] = "your-key-here"
def summarize_url(url: str) -> str:
# Load
loader = WebBaseLoader(url)
docs = loader.load()
# Split
splitter = RecursiveCharacterTextSplitter(
chunk_size=2000,
chunk_overlap=200
)
chunks = splitter.split_documents(docs)
# Summarize
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
chain = load_summarize_chain(llm, chain_type="map_reduce")
return chain.run(chunks)
if __name__ == "__main__":
url = "https://en.wikipedia.org/wiki/Large_language_model"
print(summarize_url(url))
Custom prompt
The default prompt is generic. You can replace it with something that fits your use case:
from langchain.prompts import PromptTemplate
map_prompt = PromptTemplate(
input_variables=["text"],
template="""
Summarize the following content in 3-5 bullet points.
Focus on key facts, arguments, and conclusions.
Content:
{text}
Summary:
"""
)
combine_prompt = PromptTemplate(
input_variables=["text"],
template="""
You have summaries from sections of an article.
Write a single, coherent paragraph summarizing all of them.
Summaries:
{text}
Final Summary:
"""
)
chain = load_summarize_chain(
llm,
chain_type="map_reduce",
map_prompt=map_prompt,
combine_prompt=combine_prompt
)
This gives you control over tone, format, and what the model pays attention to.
Scaling it up
If you're summarizing many URLs, a few things to watch:
- Rate limits — Add
time.sleep()between requests, or use async loaders (AsyncHtmlLoader). - Token costs —
map_reduceon a 10,000-word article can rack up tokens fast. Cache results if you're hitting the same URLs repeatedly. - Paywalled / bot-protected sites —
WebBaseLoaderwill get blocked. You need session cookies, headers spoofing, or a scraping API like Browserless or ScrapingBee. - Structured extraction — If you want specific fields (author, date, key claims) rather than a freeform summary, swap the summarization chain for an extraction chain or use
PydanticOutputParserto force structured JSON output.
Swapping the LLM
LangChain's abstraction makes it easy to drop in a different model. For a local setup:
from langchain_community.llms import Ollama
llm = Ollama(model="llama3")
The rest of the chain stays the same. This matters if you're building something where data can't leave your machine — which is exactly the kind of setup that makes sense for projects handling sensitive internal docs.