NEWScrapingAnt MCP for Claude Code, Cursor & Windsurf — try it free →
Skip to main content

Selenium Cookies in Python: Set, Save and Validate Scraped Data

· 12 min read
Oleg Kulyk
Co-Founder @ ScrapingAnt

Selenium Cookies in Python: Set, Save and Validate Scraped Data

Updated 2026-09-27

Replaced mixed-language API examples, cookie insertion on about:blank and untested session recipes with runnable extraction tests. The evidence includes Chrome and Firefox cookie restoration, incorrect-data cases, and a separate ScrapingAnt test that receives and resends synthetic cookies. The original URL and publication date are preserved.

To set a cookie in Selenium Python, first navigate to its target origin, then call driver.add_cookie() with a name/value dictionary. Navigate or refresh afterward so the next request uses it. To reuse a session, save the relevant cookies, restore them on the same origin in the new browser, and check the resulting data.

That last step matters. In our controlled catalog, every extraction returned HTTP 200 and four product rows. Restoring only the session cookie still selected member prices—but in USD when the intended dataset was EUR. A successful navigation, an authenticated-looking page and the expected row count all missed the error.

This guide shows the cookie operations, a complete runnable save/restore example, and the measured failures behind that distinction. The catalog and accounts are synthetic; the results demonstrate mechanisms, not production-site success rates.

Set, read and delete cookies on the correct origin​

Python's methods are called directly on driver. There is no driver.manage().cookies() chain in these Python examples.

TaskSelenium Python methodWhat to check
Add or replace a cookiedriver.add_cookie(cookie_dict)Open the intended origin first; keep domain and path narrow
Read an associated cookiedriver.get_cookie(name)The result can be None; presence does not prove a valid server session
Read associated cookiesdriver.get_cookies()This is a snapshot for the current document, not every cookie in the browser
Delete a named cookiedriver.delete_cookie(name)Revisit the data page and verify the resulting state
Delete associated cookiesdriver.delete_all_cookies()Use the intended origin; this does not clear local storage

The Selenium cookie guide documents these operations. The WebDriver specification defines them in relation to the active document. In both tested browsers, adding a cookie on about:blank, or specifying an unrelated domain, raised InvalidCookieDomainException.

A reliable sequence is: navigate to a lightweight page on the intended origin, confirm you stayed on that origin, add the cookie, then load the extraction page. An unexpected redirect to a different login host is a reason to stop and inspect the flow. Broadening a cookie's domain is not a fix for that mismatch.

Run a complete save-and-restore example​

Download or check out the tested example revision, then change into examples/selenium-set-cookies before running the commands below. That directory includes the fixture, browser setup and cookie helpers. These commands require Python 3.12, Chrome and openssl; Selenium Manager may download the matching browser driver:

python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-lock.txt
python quickstart.py
./run.sh

The measured environment used Selenium 4.49.0 and Python 3.12.10. The primary browser runs used Chrome 154.0.8037.57 and Firefox 156.0.1 on macOS 26.6.2 arm64. The dependency lock, browser/driver versions and raw outputs are in the packet; these are the tested versions, not a promise that they remain current.

Here is the complete quickstart.py. fixture_server, browser, extract and the snapshot functions are helpers supplied in that directory; the cookie methods themselves are ordinary Selenium calls.

"""Run with: python quickstart.py. Only synthetic localhost data is used."""
import json
import tempfile
from pathlib import Path

from catalog_fixture import fixture_server
from browser_matrix import browser, extract
from cookie_state import save_snapshot, restore_snapshot


def main():
with fixture_server() as (server, base), tempfile.TemporaryDirectory() as tmp:
snapshot = Path(tmp) / 'cookies.json'
with browser('chrome') as driver:
# This fixture issues a demo session; no real login is performed.
driver.get(base + '/seed')
driver.get(base + '/catalog/')
driver.delete_cookie('demo_region')
driver.add_cookie({
'name': 'demo_region', 'value': 'eu', 'path': '/',
'secure': True, 'sameSite': 'Lax',
})
save_snapshot(driver, snapshot, base)

with browser('chrome') as driver:
driver.get(base + '/blank')
restored = restore_snapshot(driver, snapshot, base)
result = extract(driver, server, base, 'quickstart', 4)
print(json.dumps({
'restored': restored,
'matching_records': result['matching_records'],
'records': result['records'],
}, indent=2))
assert result['assertion_passed']


if __name__ == '__main__':
main()

The fixture's /seed route issues a synthetic session and region preference. The example explicitly replaces the region cookie, saves the state, closes the first browser, and restores the snapshot into a fresh browser. /blank establishes the destination origin before restoration. extract() then navigates to /catalog/ and checks the four expected SKU/currency/price tuples.

Selected fields from the actual quickstart output:

{
"restored": {
"restored": 2,
"skipped_expired": 0
},
"matching_records": 4
}

The complete capture also contains the four extracted records. Prices use integer minor units: the expected EUR member values are 800, 1600, 2400 and 3200. These are fixed synthetic price schedules, not currency conversions or observations from a shop.

The temporary snapshot is removed on exit. For repeated jobs, the helper accepts a caller-chosen private file path; exclude that path from Git and logs. The local fixture generates a temporary self-signed certificate, and its test browsers accept it. Use normal certificate verification for real target sites.

We ran three rounds in each primary browser. Each round contained nine extraction cases and four cookie diagnostics: 54 extraction observations and 78 browser checks in total. Each extraction case therefore has six observations, and every one of those observations returned four rows with HTTP 200.

The correctness check matches each expected SKU, currency and price once. Duplicate copies of one product cannot substitute for missing products. The offline regression tests cover that distinction, alongside missing products, wrong currency and reordered records.

Cookie stateRows returnedCorrect member-EUR recordsWhat happened
No cookies40/4Retail USD schedule
Server-issued session and region44/4Intended member EUR schedule
Complete snapshot, fresh browser44/4Intended state restored
Session cookie only40/4Member prices, wrong currency: USD
JavaScript-visible cookies only40/4EUR preference retained; HttpOnly session omitted, so retail prices
Session cookie with the wrong path40/4Session not sent to the catalog path
Expired session filtered before insertion40/4Only the remaining preference was restored
Server-revoked session40/4Cookie still present; fixture no longer accepted the session
Session cookie deleted40/4Retail pricing returned

All six observations per case agreed. See the full report, Chrome captures and Firefox captures. Repeating a deterministic fixture on one machine does not estimate how often these problems occur on real websites.

Two failures deserve different recovery paths. A missing region preference calls for restoring the required extraction state. A revoked session calls for re-establishing a valid session through the site's intended flow. Re-inserting the same cookie bytes did not make our revoked demo session valid.

For a production catalog, define checks appropriate to the data contract: currency and region, expected account role, required columns, unique product identifiers, and plausible values. The test here uses exact known prices because it owns the fixture. A live collector should not blindly apply those synthetic constants to changing prices.

Save the attributes and scope you actually need​

The packet's JSON snapshot helper records the source origin and the cookies returned for the current document. On restore, it checks the destination scheme, host and port, validates the snapshot, filters already-expired entries, and preserves supported path, Secure, HttpOnly, SameSite and expiry fields.

It deliberately accepts only the exact source host and omits domain during insertion to keep restoration tied to that host. It refuses parent-domain or foreign-domain cookies instead of guessing their intended scope. This makes it a narrow example for this catalog, not an exporter for arbitrary browser sessions. Applications that need legitimate multi-subdomain cookies require an explicit domain policy.

Attribute or stateConsequence for extraction
Domain and pathDetermine where the browser can send the cookie; a catalog request can miss a cookie that exists elsewhere
HttpOnlyHides the cookie from document.cookie; it does not hide it from the tested WebDriver cookie API
ExpiryAn expired snapshot entry is skipped by this helper; refreshing the file does not renew the server's session
SecureRestricts transmission to secure contexts, subject to browser rules; the fixture uses HTTPS
SameSiteControls cross-site cookie sending; copying the field does not test an entire cross-site login flow
Server-side session validityThe server can reject a session even while its cookie remains in the browser

The Set-Cookie reference describes the attribute rules. In our HttpOnly diagnostic, WebDriver returned both demo cookie names, while document.cookie exposed only the region cookie. That is why a JavaScript-only export lost the member session.

Saving cookie JSON also does not capture local storage, session storage, or an entire browser profile. If those are part of your application state, investigate them separately; see Selenium local storage. The fixture's expiry case tests filtering a past timestamp before insertion, not waiting for the browser to expire a cookie naturally. Cross-site SameSite behavior, partitioned cookies, SSO and MFA were not tested.

The helper writes owner-only files on POSIX systems and refuses a final-path symlink. JSON is not encryption. Store real cookie snapshots as credentials, avoid printing their values, and keep both them and API keys out of version control. Windows permissions and browser behavior were not tested in this experiment.

Receive and send cookies with ScrapingAnt​

If you want ScrapingAnt to retrieve pages for the extraction workflow, cookie state can travel between requests without running this local Selenium browser. ScrapingAnt accepts cookies as name/value pairs through the cookies parameter, with multiple pairs separated by semicolons. ScrapingAnt's extended endpoint returns received cookies in its cookies field, so you can pass the relevant pairs into a later request.

We tested that round trip separately against a public cookie echo endpoint using two synthetic values. The probe made four calls in each datacenter mode: a baseline, a cookie-setting URL, a new request without replay, and a request explicitly sending the received pairs. All eight API responses and target statuses were 200.

StepWithout a browserWith a browser
BaselineNo demo cookies echoedNo demo cookies echoed
Set endpointBoth expected pairs received and echoedBoth expected pairs received and echoed
Separate call without replayNo demo cookies echoedNo demo cookies echoed
Explicitly replay received pairsBoth values echoed by the targetBoth values echoed by the target
Total measured credits for four calls440

The capture contains all eight credit receipts, totaling 44 credits. The returned body field was html; the received cookie pairs were in cookies. The product credit rules for these settings are: A request without a browser through a datacenter proxy costs 1 API credit. A request with JavaScript rendering through a datacenter proxy costs 10 API credits.

Inspect the non-live plan first:

python scrapingant_cookies.py

To reproduce the paid probe, supply SCRAPINGANT_API_KEY through your environment or secret manager, then explicitly opt in:

./run.sh --scrapingant

It permits at most eight API attempts and makes no automatic retries. An existing output file blocks accidental reruns. The key goes in the API authentication header; only selected statuses, credit counts, synthetic cookie names and boolean comparisons are saved. The README explains output handling and deliberate reruns.

Use this as a receive/select/replay pattern. Keep only the cookie pairs needed by the intended target and let the HTTP client encode the cookies parameter. The name/value string does not carry a WebDriver cookie's domain, path, expiry, HttpOnly, Secure or SameSite metadata. Do not flatten cookies from several sites into one string or send them to a different target.

The echo test confirms cookie transport in the two measured modes. It does not establish that a real login survives a change of browser, network or other session state, and it does not renew expired or revoked authentication. ScrapingAnt is unnecessary for the local fixture or a direct Selenium workflow you already operate. For remote page retrieval with explicit cookie chaining, start with the ScrapingAnt cookie documentation.

Recheck the extraction contract when sessions change​

Run ./run.sh to execute the 16 offline tests and the local Chrome suite. It does not make ScrapingAnt calls, even when an API key is configured. The committed results remain intact; reruns write to ignored run_output/. The packet documents the optional Firefox run and exact measured version.

For a saved-results check without starting browsers or spending credits:

python summarize.py > run_output/rebuilt-comparison.json

When adapting the examples, keep three checks separate: did navigation succeed, was the expected session state established, and did the extracted records satisfy the data contract? Our session-only case passed the first two and still returned the wrong currency. Validate the data after restoration, expiry handling and session renewal, before accepting the batch downstream.

For the next part of the scraper, see finding elements with Selenium and cookie management with Python Requests.

Examples tested on 2026-09-27 with Selenium 4.49.0, Chrome 154.0.8037.57, Firefox 156.0.1 and requests 2.34.2. Code and outputs: pinned evidence packet.

This article was drafted with AI assistance from a tested evidence packet. The named author is responsible for the code, measurements and corrections.

Forget about getting blocked while scraping the Web

Try out ScrapingAnt Web Scraping API with thousands of proxy servers and an entire headless Chrome cluster