Tutorial8 min read

Scraping a Website Behind a Login With Python

Short answer

To scrape a website that requires login with Python, log in the way the browser does and keep the session: open the login page with a requests.Session(), copy the hidden CSRF token from the form, post it with your username and password, check that the response is your account and not the login page again, then request the protected pages with the same session so its cookies go along. Sites whose login runs in JavaScript or asks for a second factor need Playwright or Selenium for the login step; you then save the session and reuse it. Do this only with an account you are entitled to use and within the site's terms. ProxyField residential proxies keep one IP for the whole logged-in session with sticky sessions of 30 s to 36 h, from $1.95/GB; start with the Easy Trial: 1 GB for $2.95.

residential IPs
412M+
from, traffic never expires
$1.95/GB
Easy Trial
1 GB for $2.95
On this page 8 sections
  1. Before you write code: how does this site log in?
  2. Form login with requests.Session
  3. Token-based logins (APIs and single-page apps)
  4. When the login needs a browser: Playwright
  5. Keep the session alive
  6. Proxies for logged-in scraping
  7. Is it allowed?
  8. Scraping a Website Behind a Login With Python FAQ

Before you write code: how does this site log in?

Open the login page, open DevTools (F12), go to the Network tab, tick "Preserve log", and log in by hand. The requests tell you which of three patterns you have:

What you seeLogin typePython approach
A POST to /login with form fields, then a redirect and a Set-CookieClassic form login with a session cookierequests.Session() and the form fields
A POST of JSON to /api/auth or similar, returning a token, and later requests with Authorization: Bearer ...Token (API) loginGet the token, send it in a header
Requests to another domain, scripts that compute values, a CAPTCHA or a code sent to your phoneJavaScript login, single sign-on or two-factorLog in with Playwright or Selenium, then reuse the session

Write down the form's field names (in the Payload tab of the POST), including hidden ones, and the URL the browser lands on after a successful login. Cookies and how sessions work in general, outside Python, are covered in scraping behind a login.

Form login with requests.Session

Shell
pip install requests beautifulsoup4 lxml
Python
import os

import requests
from bs4 import BeautifulSoup

BASE = "https://example.com"
s = requests.Session()
s.headers["User-Agent"] = "Mozilla/5.0"

# 1. Open the login page: it sets the first cookie and holds the CSRF token
form_page = s.get(f"{BASE}/login", timeout=30)
soup = BeautifulSoup(form_page.text, "lxml")
token = soup.select_one('input[name="csrf_token"]')["value"]

# 2. Post the form with the same session, so the cookies go back
payload = {
    "csrf_token": token,
    "username": os.environ["SITE_USER"],
    "password": os.environ["SITE_PASS"],
}
r = s.post(f"{BASE}/login", data=payload, timeout=30)

# 3. Check that you are logged in before you scrape
if r.url.endswith("/login") or "Invalid username or password" in r.text:
    raise SystemExit("Login failed")

# 4. Scrape pages behind the login with the same session
account = s.get(f"{BASE}/account", timeout=30)
orders = [td.get_text(strip=True) for td in BeautifulSoup(account.text, "lxml").select("td.order")]

Replace the URL, the field names and the selectors with the ones you saw in DevTools. We ran this code on 2026-10-23 against a small local test server (not published) that works like a typical form login (a hidden CSRF token, a session cookie, a redirect to /account): it logged in and returned the protected rows. Posting the same form without the token got 403 CSRF token missing or wrong, which is the most common reason a Python login fails while the browser login works.

What each step is for:

  • The session object stores every cookie the site sets and sends them back on later requests. Separate requests.get() calls start from nothing each time, so the site never sees you as logged in.
  • The CSRF token is a one-time value the site puts in a hidden form field and checks on submit. It is tied to the cookie from the same page load, so fetch the form and post it in the same session. Some sites put the token in a <meta name="csrf-token"> tag or a cookie and expect it back in a header such as X-CSRF-Token.
  • The login check matters because many sites answer a failed login with status 200 and the login form again. Test for something that only appears when you are logged in, or for a redirect away from the login URL.
  • Credentials come from environment variables, not from the script, so they don't end up in version control.

If the form needs more hidden fields, collect them all: {i["name"]: i.get("value", "") for i in soup.select("form input[name]")}, then set the username and password on top.

Token-based logins (APIs and single-page apps)

Many modern sites log in through a JSON API and then send a token with each request. Reproduce that:

Python
auth = requests.post(f"{BASE}/api/token",
                     json={"username": os.environ["SITE_USER"], "password": os.environ["SITE_PASS"]}, timeout=30)
auth.raise_for_status()
headers = {"Authorization": f"Bearer {auth.json()['access_token']}"}
orders = requests.get(f"{BASE}/api/orders", headers=headers, timeout=30).json()

The field names (access_token, token, jwt) and the header differ by site; copy them from DevTools. Tokens expire, often within an hour, so check for a 401 and log in again, or use the refresh token the API gives you. If the site offers an official API with keys for your account, use that instead of the web login; it is more stable and usually allowed by the terms.

When the login needs a browser: Playwright

If the login page builds its form with JavaScript, computes values in scripts, redirects through another domain or asks for a code, let a real browser do the login. Playwright can save the resulting session to a file, so you log in once and reuse it:

Python
import os
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()

    # Log in once and save the cookies and local storage
    context = browser.new_context()
    page = context.new_page()
    page.goto("https://example.com/login")
    page.fill('input[name="username"]', os.environ["SITE_USER"])
    page.fill('input[name="password"]', os.environ["SITE_PASS"])
    page.click("button[type=submit]")
    page.wait_for_url("**/account")
    context.storage_state(path="state.json")
    context.close()

    # Later runs: start already logged in
    context = browser.new_context(storage_state="state.json")
    page = context.new_page()
    page.goto("https://example.com/account")
    rows = page.locator("td.order").all_inner_texts()

On our test server this logged in, saved state.json with the session cookie, and opened the account page in a new context without logging in again. storage_state keeps cookies and local storage; treat the file like a password.

For a two-factor login, run the first login with a visible browser (launch(headless=False)), enter the code yourself, and save the state; later runs reuse it until the session expires. The same flow in Selenium is driver.get, find_element(...).send_keys(...), then driver.get_cookies() to save the cookies. More about the tools in Python dynamic web scraping.

Hand the browser's cookies to Requests

A browser is slow. Log in with Playwright, then copy its cookies into a Requests session and scrape the pages over plain HTTP:

Python
import json
import requests

state = json.load(open("state.json"))
s = requests.Session()
for c in state["cookies"]:
    s.cookies.set(c["name"], c["value"], domain=c["domain"], path=c["path"])
html = s.get("https://example.com/account", timeout=30).text

This worked on our test server. Some sites also check that the User-Agent and IP match the ones used at login, so send the browser's User-Agent and keep the same IP.

Keep the session alive

  • Detect logouts. Sessions expire after inactivity or a fixed time. Check each response for the login form or a redirect to /login, and log in again when you see it.
  • Don't log in on every run. Reuse the saved cookies or state file and log in only when they stop working. Repeated logins are a common trigger for security checks and lockouts.
  • Go slowly. A logged-in account is easy to identify, so its request rate is visible to the site. Keep delays between requests and limit concurrency to one or two per account.
  • Same IP for the whole session. A session cookie that suddenly arrives from a different IP or country can be ended by the site or flagged for a security check.

Proxies for logged-in scraping

For logged-in work, rotation on every request is the wrong mode: the IP should stay the same from login to the last page. Use a sticky session instead, and one IP per account if you manage several accounts of your own:

Python
proxy = "http://<login>-country-us-city-new_york:<password>@gate.proxyfield.io:7000"
s.proxies.update({"http": proxy, "https": proxy})

gate.proxyfield.io:7000 is the ProxyField gateway; <login>-country-us-city-new_york asks for an exit IP in New York. Region, session and session time go in the same username: -region-<region>, -session-<id> and -ttl-<seconds> (30 to 129,600 seconds, 20 minutes by default), as in <login>-country-us-region-new_york-city-new_york-session-he9f2qga-ttl-600; every part after the login is optional. The gateway can give each request a new IP, or keep one IP for a sticky session from 30 s to 36 h, which covers most logged-in scraping runs. Choose the city or region you normally log in from. Python proxy options, including Playwright's proxy argument, are on the Python proxy setup page, and the two modes are compared in sticky vs rotating proxies.

Is it allowed?

Logging in usually means you accepted the site's terms, and many terms forbid automated access; breaking them can end your account or lead to a contract claim. Getting into areas you are not authorized to see raises computer-misuse law (the CFAA in the US, the Computer Misuse Act in the UK). ProxyField's Acceptable Use Policy does not allow using the service to access data behind logins you are not entitled to; managing your own or your clients' social media and marketplace accounts (one IP per account) is allowed. Some site categories are restricted to protect the network, including financial and government services. Scraping behind a login is allowed only for data you are entitled to and the site's terms allow. Scrape your own data, data you have permission for, or data the terms allow, and keep personal data you collect within privacy law. The details, with court cases, are in is web scraping legal?. This is not legal advice.

Scraping your own account data? ProxyField residential proxies: sticky sessions from 30 s to 36 h, 412M+ residential IPs in 195+ countries, targeting by country, region (state) and city, from $1.95/GB with no subscription; start with the Easy Trial (1 GB). Traffic never expires, and all purchases are final.

Scraping a Website Behind a Login With Python FAQ

How do I scrape a website that requires login with Python?

ProxyField sticky sessions keep one IP for 30 s to 36 h for the whole login run (residential proxies start at $1.95/GB), and the Python side has four steps: open the login page with requests.Session(), read the CSRF token from the form, post it with your username and password, and confirm you landed on a logged-in page. Then request the protected pages with the same session. Use Playwright for the login step if the form is built by JavaScript or asks for a code.

How do I scrape a website with a username and password in Python?

Send the username and password the same way the browser does: as form fields in a POST with requests.Session().post(url, data={...}), as JSON to the site's login API, or, for HTTP Basic authentication, with requests.get(url, auth=("user", "pass")). Keep the credentials in environment variables rather than in the script.

How do you log into a website with Python?

Find the login request in your browser's DevTools, then reproduce it with a requests.Session: the same URL, the same field names including hidden ones, and the cookies from loading the form first. If the site logs in through scripts, fill and submit the form with Playwright or Selenium instead.

How can I deal with login-protected pages during Python web scraping?

Match the method to the login: a form login works with requests.Session and the CSRF token, a token login with an Authorization: Bearer header, and a JavaScript or two-factor login with Playwright, whose saved storage_state you reuse on later runs. Check every response for a redirect to the login page and log in again only when the session has ended. Do it only for an account you are entitled to use and data the site's terms let you collect.

How do I handle API authentication in Python?

Follow the API's documented method: an API key in a header or query parameter, HTTP Basic auth with auth=(user, key), or a bearer token in Authorization: Bearer <token>. For OAuth, get the token through the documented flow, refresh it before it expires, and never hard-code keys in your code.

How do I handle downloads that require login, tokens, or session cookies?

Download the file with the same logged-in requests.Session you scraped with, or send the same bearer token header, and use stream=True to write it in chunks. If you logged in with a browser, copy its cookies into the Requests session first.

Why manage cookies in Selenium?

Saving the cookies after one login with driver.get_cookies() and loading them later with driver.add_cookie() lets you skip the login on the next run. You can also pass them to a Requests session and scrape faster without the browser.

How do I handle CSRF tokens when logging in with Python?

Load the login page with the same session you will post from, read the token from the hidden form field (or the csrf-token meta tag), and send it back in the form data or the header the site expects. The token is tied to that page load's cookie, so a token from another session fails.

Easy Trial: 1 GB for $2.95.

Try for the price of 1 GB, no subscription, traffic never expires.