Cookbook: Web Scraping & Crawling Pipeline
On this page
Recipes for turning a list of URLs into indexed documents using WebLoader/MultiURLLoader, respecting rate limits and handling failures gracefully.
Recipe: Polite bulk fetching of a known URL list
from zerda.document_loaders import MultiURLLoader, WebLoaderConfig
urls = [
"https://example.com/docs/page1",
"https://example.com/docs/page2",
"https://example.com/docs/page3",
]
config = WebLoaderConfig(timeout=15, max_retries=3, retry_delay=1.0)
docs = MultiURLLoader(
urls,
config=config,
max_workers=4,
delay_between_requests=1.0, # minimum 1s between request starts, globally
continue_on_error=True,
).load()
print(f"Loaded {len(docs)} documents from {len(urls)} URLs")
Recipe: Single-page fetch with custom headers (e.g. API token)
from zerda.document_loaders import WebLoader
docs = WebLoader(
"https://internal-wiki.example.com/page/42",
headers={"Authorization": "Bearer YOUR_TOKEN"},
timeout=20,
).load()
Recipe: Fetching HTML you already have (from a scraper, browser automation, or an API response)
WebLoader always performs its own HTTP fetch. If you already have HTML text (e.g. from Selenium, Playwright, or a webhook payload), skip straight to HTMLStringLoader instead:
from zerda.document_loaders import HTMLStringLoader, HTMLLoaderConfig
# html_text obtained from your own scraper/automation tool
config = HTMLLoaderConfig(extract_links=True, tags_to_extract=["article", "main"])
docs = HTMLStringLoader(html_text, source="https://example.com/page", config=config).load()
Recipe: Custom HTML extraction for web-fetched pages
WebLoader hardcodes its internal HTMLLoaderConfig (fixed tags_to_remove, extract_links=False, extract_tables=True) — see the WebLoader warning. To customize extraction from a URL, fetch manually and parse yourself:
import requests
from zerda.document_loaders import HTMLStringLoader, HTMLLoaderConfig
response = requests.get("https://example.com/article", timeout=10)
response.raise_for_status()
config = HTMLLoaderConfig(
tags_to_extract=["article"],
extract_links=True,
extract_tables=False,
)
docs = HTMLStringLoader(response.text, source=response.url, config=config).load()
Recipe: Crawling a rate-limited internal API/wiki gently
from zerda.document_loaders import MultiURLLoader, WebLoaderConfig
# Conservative settings for a rate-sensitive target
config = WebLoaderConfig(
timeout=20,
max_retries=2,
retry_delay=3.0,
headers={"User-Agent": "MyCompanyBot/1.0 (contact: team@example.com)"},
)
docs = MultiURLLoader(
url_list,
config=config,
max_workers=2, # low concurrency
delay_between_requests=2.0, # 2s minimum between request starts
).load()
Recipe: Error visibility on large batches
MultiURLLoader with continue_on_error=True (the default) never raises for individual URL failures — failures only appear in logs. Enable logging to see them:
import logging
logging.basicConfig(level=logging.INFO)
docs = MultiURLLoader(large_url_list, continue_on_error=True).load()
# Watch for:
# ✗ Failed: {url} - {error}
# "Failed to load N/M URLs. Failed: [...]"
If you need programmatic access to which URLs failed (not just log lines), wrap the loop yourself instead of using MultiURLLoader's built-in aggregation:
from zerda.document_loaders import WebLoader
def fetch_with_tracking(urls, config=None):
succeeded, failed = [], []
for url in urls:
try:
succeeded.extend(WebLoader(url, config=config).load())
except Exception as e:
failed.append((url, str(e)))
return succeeded, failed
docs, failures = fetch_with_tracking(url_list)
print(f"{len(failures)} URLs failed:", failures)
Recipe: Strict mode — abort the whole batch on first failure
try:
docs = MultiURLLoader(url_list, continue_on_error=False).load()
except Exception as e:
print(f"Aborted: {e}")
# No documents from any URL are usable here — handle upstream retry/alerting
SSRF caution for user-submitted URLs
If any of these URLs come from untrusted user input (rather than a fixed, developer-controlled list), read Security → No URL allowlisting or SSRF protection before deploying this pattern — WebLoader/MultiURLLoader perform no host validation of their own.