How to Handle Cookies and Sessions When Web Scraping
Handle cookies and sessions when web scraping: cookie jars, Set-Cookie parsing, CSRF tokens, login state, disk persistence, and sticky proxy IPs, in Python.

Handling cookies and sessions when web scraping is what separates a scraper that reads one public page from one that can walk a multi-step checkout, keep a currency preference, clear a consent wall, or stay inside a logged-in account without getting bounced. Most tutorials stop at requests.get(). Real targets hand you a cookie jar on the first response, rotate a token on every form, and expect the exact same cookies (from the exact same IP) on request two.
This is the layer beneath authentication. If your only goal is getting past a login wall, read how to scrape a website behind a login for the full auth walkthrough. This guide is about the cookie jar itself: what a Set-Cookie header actually contains, why a cookie you clearly received never gets sent back, how to save a session to disk, and how to reconcile a stateless scraping API with a stateful target.
What "Session State" Really Means for a Scraper
A "session" is just a set of cookies the server uses to remember you between otherwise independent HTTP requests. HTTP is stateless. The only reason a site knows request two came from the same visitor as request one is a cookie it set on the first response and expects back on the second.
Login is the famous case, but it is not the only one. You need session handling any time state carries between requests:
- A cookie-consent or GDPR banner sets
consent=1and refuses to render content until it sees it. - A store keeps your cart, currency, or shipping country in a
cart_idorgeocookie. - An A/B testing framework pins you to a variant with a bucketing cookie, and pages break if it flips mid-crawl.
- A rate limiter or bot-management layer drops an anonymous session token on the landing page and rejects any deep link that arrives without it.
- Anti-CSRF systems set a token cookie that a later form submission has to echo back.
Every one of these fails the same way: request one works, request three returns a redirect, a 403, or an empty page, and the scraper "randomly" breaks. The fix is always the same discipline. Capture what the server sets, store it correctly, and send it back with matching attributes from a stable IP.
Scraping at scale? Skip the blocks.
Fast, unblockable datacentre proxies with unlimited bandwidth.
Reusing an Authenticated Session
Once you have a valid jar, the goal is to log in as rarely as possible. Reload the saved cookies, probe a page that requires auth, and only re-authenticate when the probe fails. A cheap, reliable probe is a request to a protected URL with redirects disabled:
def is_logged_in(session):
r = session.get(
"https://www.sparkproxy.io/account/dashboard",
allow_redirects=False,
)
# A bounce to /login means the session is dead
return not (r.is_redirect and "login" in r.headers.get("Location", ""))
if not is_logged_in(session):
do_login(session) # fetch token, POST credentials, then save the jar again
session.cookies.save(ignore_discard=True, ignore_expires=True)
Watch the signals a site uses to say "your session expired." A 302 to /login, a 401, a JSON body like {"error": "unauthenticated"}, or a login form showing up where content should be are all the same event. Detect it explicitly instead of scraping a login page by accident and writing garbage to your dataset. The full login mechanics, including a headless-browser login for JavaScript-heavy sites, live in the scrape behind a login guide.
Pin the Session to One Sticky Proxy IP
Here is the part most cookie tutorials skip. A session is not just cookies. Many sites bind the session to the IP that created it. Log in on IP A, then send request two through IP B from a rotating pool, and the server may invalidate the session as suspicious. Your cookies are perfect and the site still logs you out.
The fix is a sticky session: hold one exit IP for the life of the authenticated session. With requests, that means pointing the whole session object at one sticky proxy endpoint and keeping it there:
session = requests.Session()
session.proxies = {
"http": "http://USER:PASS@proxy.sparkproxy.io:PORT",
"https": "http://USER:PASS@proxy.sparkproxy.io:PORT",
}
# Every request in this session now exits from the same sticky IP,
# matching the IP that was used at login time.
The cookie jar and the exit IP are one unit. Save them together, rotate them together, retire them together. When the sticky window ends and the IP changes, treat the session as potentially dead and be ready to re-authenticate. For how sticky windows work and how long they hold, see what is a sticky session proxy. If you also rotate for unauthenticated crawling, the rotate proxies in Python guide covers keeping rotation and sticky sessions separate so you never rotate an IP out from under a live login.
Frequently asked questions
FAQ
A cookie is a single name=value pair with matching rules that the server sets and the client sends back. A session is the collection of cookies (usually including one session-id cookie) that lets a server recognize repeated requests as coming from the same visitor. You handle cookies to maintain a session.
Almost always one of three things: you are using bare requests.get() calls instead of a requests.Session, so cookies are never reused; the session cookie was not saved because you omitted ignore_discard=True; or you rotated to a new proxy IP mid-session and the site invalidated the login. Use one session object on one sticky IP.
Assign an http.cookiejar.LWPCookieJar to your session's cookies attribute, then call .save(ignore_discard=True, ignore_expires=True) after login and .load(...) with the same flags on the next run. The flags are required or session cookies (which have no expiry) are silently dropped.
Fetch the form page with your session, read the token from the hidden input field or from the token cookie, then submit it back on the same session, either as a form field or a header like X-XSRF-TOKEN. The token is bound to the session cookie, so always fetch and submit with the same session and never cache the token across runs.
No. Each API request runs a fresh browser profile, so cookies do not persist across separate calls, and the session_id parameter only labels requests in your logs. Maintain the cookie jar in your own code and inject it on each request through the cookies parameter, which takes a list of {name, value, domain} objects.
Many sites bind a session to the IP that created it, so requests arriving on a different IP look like session hijacking and get logged out. A sticky proxy holds one exit IP for the life of the session, so the IP that logged in is the same IP that makes every follow-up request.
Get 50% off your first purchase
Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.
Offer ends soon โ claim it before it's gone
Related articles

Async Web Scraping in Python: A Concurrency Guide
Master async web scraping in Python with asyncio, httpx and aiohttp: bound concurrency with semaphores, size connection pools, and isolate failures.

How to Scrape Google News: RSS, Headlines, and Feeds
Scrape Google News with its free RSS feeds: pull headlines and articles, decode the redirected article links, and geo-target editions with hl, gl, and ceid.

How to Scrape Craigslist Listings
Learn how to scrape Craigslist listings across city subdomains: search results, categories, and posting details, plus the RSS trick and rate-limit fixes.
