requests, Sessions & Headers
State Management in Web Scraping
While standard HTTP requests are stateless, web applications heavily rely on state management—specifically Cookies—to track users, manage logins, and maintain CSRF tokens.
When building a scraper in Python, using requests.get() repeatedly treats every single request as a brand new, incognito visitor. This is highly inefficient and quickly flags your bot. The solution is the requests.Session() object.
The Power of requests.Session()
A Session object provides three massive benefits for robust web scraping:
1. Cookie Persistence: It automatically captures Set-Cookie headers from the server and attaches those cookies to all subsequent requests.
2. Connection Pooling (Keep-Alive): It reuses the underlying TCP connection to the server. This dramatically reduces network latency, making your scrapers significantly faster.
3. Default Headers: You can apply headers (like User-Agent) once to the session, and they will be sent automatically with every request.
Handling Authentication
Many scrapers require logging into a portal before extracting data. A Session makes this trivial. You send a POST request to the login endpoint with your credentials, and the session automatically stores the resulting auth cookies.
import requests
# 1. Initialize the Session
session = requests.Session()
# 2. Set default headers for all requests
session.headers.update({
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept-Encoding": "gzip, deflate, br"
})
# 3. Perform Login (Session saves the cookie automatically)
login_url = "https://example.com/api/login"
credentials = {"username": "scraper_bot", "password": "secure_password"}
session.post(login_url, json=credentials)
# 4. Access protected data
data_url = "https://example.com/api/protected-data"
response = session.get(data_url)
print("Scraped Data:", response.json())
Managing CSRF Tokens
Modern web frameworks (like Django, Laravel, and Rails) protect POST requests with Cross-Site Request Forgery (CSRF) tokens. To scrape forms on these sites:
1. Make a GET request to the form page using your session.
2. Parse the HTML to extract the hidden <input name="csrf_token" value="...">.
3. Include that token in your subsequent POST payload.
Sessions Into the Wild
The session is your identity and your TCP budget, so keep it configured in one place. A few session-level settings you will use constantly:
import requests
with requests.Session() as session:
session.headers.update({"User-Agent": "Mozilla/5.0 ... Chrome/125 Safari/537.36"})
session.params["locale"] = "en-US" # default query params on every call
session.trust_env = False # ignore ambient proxy variables
resp = session.get("https://example.com/products", timeout=(3, 10))
print(resp.json() if "json" in resp.headers.get("content-type", "") else resp.text[:200])
The with block closes the connection pool cleanly when the operation is done — the polite counterpart of session.close(). Using the context manager means one session, one identity, one pool per crawl, which is exactly the shape every later lesson builds on.
What a Session Does Not Give You
It is worth being explicit about the ceilings, so you stop bumping into them later: a session does not retry failed requests, does not check robots.txt, does not rotate IPs, does not slow down when the server hurts, and does not persist its cookies across process restarts. Each of those gaps has a dedicated lesson (error handling, robots, proxies, rate limiting, caching). Sessions make the request layer correct; the surrounding lessons make the crawl dependable.