Python POST Requests and Proxies: Elevate Your Web Scraping

Sarah Whitmore

Last edited on May 4, 2025
Last edited on May 4, 2025

Scraping Techniques

Understanding Python POST Requests for Smarter Web Scraping

When we venture into the world of web scraping, GET requests are usually our first tool of choice. We ask a server for a specific resource, get the HTML or data back, and then parse out the juicy bits we need. Simple enough, right?

However, there's another HTTP workhorse that often flies under the radar for basic scraping but is crucial for more complex interactions: the POST request. POST requests let our scripts send data to a server, mimicking actions like filling out forms or submitting information, and often getting a specific response back based on that submission.

This guide will dive into what POST requests are, demonstrate how to craft them using Python's excellent Requests library, and show you how they can level up your web scraping game, especially when combined with proxies.

So, What Exactly is a POST Request?

Think of the web as a conversation between your browser (or script) and a server. This conversation uses different verbs, known as HTTP methods, to define the purpose of each message.

GET is the most frequent method – it's like asking "Can I get the content of this page?". But sometimes, you need to send information, not just receive it. That's where POST comes in. It signals that the client is sending data to the server, often to create or update a resource.

A classic example? Website forms. When you log in, sign up, or submit a comment, your browser typically packages that data into a POST request and sends it off. This method is preferred for sensitive data (like passwords) because, unlike GET requests where data can sometimes end up visible in the URL, POST requests tuck the data away securely in the message body.

For web scrapers, this means we can use POST requests to programmatically interact with forms. Instead of firing up a full browser automation tool like Selenium (which has its place but can be overkill), we can often achieve the same result – like logging into a site – using the much lighter and faster Requests library.

Crafting POST Requests with Python's Requests Library

Let's get practical. We'll use the popular Requests library to simulate logging into a practice website, Quotes to Scrape, designed specifically for honing scraping skills.

We'll assume you have some familiarity with Requests for basic GET operations. If not, spending a little time with its documentation first wouldn't hurt!

Setting Up Your Environment

First, ensure you have the necessary tools. You'll need the `requests` library itself and `BeautifulSoup` for parsing the HTML responses later. Install them using pip:



Now, create a Python file (let's call it `post_example.py`) and import the libraries at the top:

import requests
from bs4 import BeautifulSoup

Sending Your First POST Request

Making a POST request is straightforward with `requests.post()`. This function primarily needs two things:

  • The URL endpoint that expects the POST data.

  • A `data` argument, which is a Python dictionary containing the key-value pairs you want to send.

Let's try sending some structured data to a test API, like httpbin, which is great for experimenting:

import requests

# The URL that accepts POST requests
post_url = "https://httpbin.org/post"

# The data we want to send (as a dictionary)
payload = {
    'item_name': 'Wireless Mouse',
    'quantity': 2,
    'priority': 'High'
}

# Make the POST request
response = requests.post(post_url, data=payload)

# Print the response (httpbin echoes the data sent)
print(response.json())

Running this script sends our `payload` dictionary to `httpbin.org/post`. The server processes it and sends back a JSON response, which usually includes the data it received, confirming our POST request worked.

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "item_name": "Wireless Mouse",
    "priority": "High",
    "quantity": "2"
  },
  "headers": { ... },
  "json": null,
  "origin": "YOUR_IP_ADDRESS",
  "url": "https://httpbin.org/post"
}

See how our data appears under the `form` key? That's POST in action. Now, let's apply this to a real web scraping scenario.

Maintaining State with Sessions

By default, each `requests` call is independent. If you log in with one request, the next request won't remember you. To handle logins and stay authenticated, we need to use Session Objects.

Sessions persist information, like login cookies, across multiple requests made from the same session object, just like a web browser does.

Here's how you create and use one:

# Create a session object
session = requests.Session()

# Now, use the session object to make requests
# instead of the base requests module.

# Example GET request using the session
# response = session.get("http://quotes.toscrape.com/")

# Example POST request using the session
login_url = "http://quotes.toscrape.com/login"
login_payload = {'username': 'user', 'password': 'password'} # Placeholder data
response = session.post(login_url, data=login_payload)

Any cookies set by the server after the POST request (like a session cookie upon successful login) will be automatically stored in the `session` object and sent with subsequent requests made using `session.get()` or `session.post()`. This is key for scraping pages that require you to be logged in.

Logging Into a Website

To log in using a POST request, we need to figure out what data the login form expects. Usually, this involves inspecting the form's HTML source code on the target page (Quotes to Scrape login in our case).

Look for the `` elements within the `

`. You'll need the `name` attribute of each input field you want to fill (like username and password).


<!-- Simplified example from quotes.toscrape.com/login -->
<form method="post" action="/login">
    <input type="hidden" name="csrf_token" value="...."> <!-- Important! See below -->
    <div>
        <label for="username">Username</label>
        <input type="text" id="username" name="username"> <!-- name="username" -->
    </div>
    <div>
        <label for="password">Password</label>
        <input type="password" id="password" name="password"> <!-- name="password" -->
    </div>
    <input type="submit" value="Login">
</form>

The `name` attributes (`username`, `password`) become the keys in our `data` dictionary. We also see a hidden input named `csrf_token`. Many modern websites use CSRF tokens to prevent cross-site request forgery. You often need to first make a GET request to the login page, extract this token value from the HTML, and include it in your POST data dictionary. (For simplicity, the Quotes to Scrape login doesn't strictly enforce this, but be aware of it for real-world sites).

Let's build the login payload:

login_payload = {
    "username": "YourActualUsername",  # Replace with valid credentials if testing
    "password": "YourSecretPassword",  # Replace with valid credentials if testing
    # "csrf_token": "extracted_token_value" # Add if needed
}

# Make the login request using our session
response = session.post("http://quotes.toscrape.com/login", data=login_payload)

# Check if login was successful (e.g., by looking for a 'Logout' link)
soup = BeautifulSoup(response.text, 'html.parser')
logout_link = soup.find('a', href='/logout')

if logout_link:
    print("Login successful!")
else:
    print("Login failed.")
    # You might want to print response.text here to debug

After a successful login via `session.post()`, subsequent `session.get()` requests to protected pages should now work.

Integrating Proxies for Anonymity and Scale

Okay, we can send POST requests and log in. But what if you're scraping at scale or need to avoid revealing your own IP address? That's where proxies come in handy.

Proxies act as intermediaries. Your request goes to the proxy server, which then forwards it to the target website using its own IP address. The website sees the proxy's IP, not yours. This is essential for web scraping to avoid IP bans and to simulate traffic from different locations or devices.

Using proxies becomes particularly powerful when managing multiple accounts or tasks that require distinct identities. Imagine needing to log into hundreds of accounts – routing each through a different residential or mobile proxy from a provider like Evomi makes it look like legitimate, distributed user activity rather than a single script hammering the server.

Evomi offers various proxy types, including highly anonymous residential and mobile proxies, perfect for mimicking real users. Integrating them into your Requests session is simple. You'll get proxy access details (like host, port, username, password) from your provider dashboard.

Here's how you configure proxies within a `requests.Session`:

# Replace with your actual Evomi proxy credentials and endpoint
# Format: protocol://username:password@host:port
proxy_url = "http://your_username:your_password@rp.evomi.com:1000"  # Example for Evomi Residential HTTP

session.proxies = {
    "http": proxy_url,
    "https": proxy_url,  # Use the same proxy for HTTPS traffic
}

# Now, any request made with 'session' will route through the proxy
# response = session.get("https://httpbin.org/ip") # Check the IP seen by the server
# print(response.json()) # Should show the proxy's IP

Remember to replace the placeholder credentials and endpoint (`rp.evomi.com:1000` is for residential HTTP) with your specific details provided by Evomi. We offer competitive pricing starting from $0.49/GB for residential proxies and even provide a free trial so you can test them out.

Here is the complete example combining session, POST login, and proxies:

import requests
from bs4 import BeautifulSoup

# --- Configuration ---
LOGIN_URL = "http://quotes.toscrape.com/login"
USERNAME = "YourActualUsername"  # Use real credentials for testing
PASSWORD = "YourSecretPassword"

# Evomi Proxy configuration (replace with your details)
# Get yours from evomi.com - check our free trial!
PROXY_USER = "your_username"
PROXY_PASS = "your_password"
PROXY_HOST_PORT = "rp.evomi.com:1000"  # Example: Residential HTTP endpoint
PROXY_URL = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST_PORT}"


# --- Script Logic ---
session = requests.Session()

# Configure proxies for the session
session.proxies = {
    "http": PROXY_URL,
    "https": PROXY_URL,
}

# Optional: Verify proxy is working (uncomment to test)
# try:
#     ip_check_response = session.get("https://httpbin.org/ip")
#     print("IP Seen by Server:", ip_check_response.json())
# except requests.exceptions.RequestException as e:
#     print(f"Proxy connection failed: {e}")
#     exit()

# Prepare login data
login_payload = {
    "username": USERNAME,
    "password": PASSWORD
    # Add csrf_token here if needed for the target site
}

print(f"Attempting login to {LOGIN_URL}...")
try:
    # Perform the login POST request through the proxy
    response = session.post(LOGIN_URL, data=login_payload)
    response.raise_for_status()  # Raise an exception for bad status codes (4xx or 5xx)

    # Check for successful login indicator
    soup = BeautifulSoup(response.text, 'html.parser')
    logout_link = soup.find('a', href='/logout')

    if logout_link:
        print("Login successful via proxy!")
        # Now you can use 'session.get()' to scrape protected pages
        # protected_page = session.get("http://quotes.toscrape.com/page/2/") # Example
        # print("Successfully accessed page requiring login.")
    else:
        print("Login failed. Check credentials or website structure.")
        # print("Response content:", response.text[:500]) # Print part of response for debugging

except requests.exceptions.RequestException as e:
    print(f"An error occurred during the request: {e}")

Wrapping Up

In this walkthrough, we explored the power of POST requests in Python using the Requests library. You've seen how they differ from GET requests and how they enable interactions like submitting forms – a crucial capability for scraping websites that require logins or other data submissions.

Using `requests.post()` combined with `requests.Session` for state management offers a significantly lighter alternative to full browser automation tools when your primary goal is to interact with forms or APIs. Add proxies into the mix, like those offered by Evomi, and you gain anonymity and the ability to scale your scraping operations effectively.

If you're looking to dive deeper into the capabilities of the Requests library, including handling headers, timeouts, and more advanced scenarios, check out our comprehensive guide to the Python Requests module.

Understanding Python POST Requests for Smarter Web Scraping

When we venture into the world of web scraping, GET requests are usually our first tool of choice. We ask a server for a specific resource, get the HTML or data back, and then parse out the juicy bits we need. Simple enough, right?

However, there's another HTTP workhorse that often flies under the radar for basic scraping but is crucial for more complex interactions: the POST request. POST requests let our scripts send data to a server, mimicking actions like filling out forms or submitting information, and often getting a specific response back based on that submission.

This guide will dive into what POST requests are, demonstrate how to craft them using Python's excellent Requests library, and show you how they can level up your web scraping game, especially when combined with proxies.

So, What Exactly is a POST Request?

Think of the web as a conversation between your browser (or script) and a server. This conversation uses different verbs, known as HTTP methods, to define the purpose of each message.

GET is the most frequent method – it's like asking "Can I get the content of this page?". But sometimes, you need to send information, not just receive it. That's where POST comes in. It signals that the client is sending data to the server, often to create or update a resource.

A classic example? Website forms. When you log in, sign up, or submit a comment, your browser typically packages that data into a POST request and sends it off. This method is preferred for sensitive data (like passwords) because, unlike GET requests where data can sometimes end up visible in the URL, POST requests tuck the data away securely in the message body.

For web scrapers, this means we can use POST requests to programmatically interact with forms. Instead of firing up a full browser automation tool like Selenium (which has its place but can be overkill), we can often achieve the same result – like logging into a site – using the much lighter and faster Requests library.

Crafting POST Requests with Python's Requests Library

Let's get practical. We'll use the popular Requests library to simulate logging into a practice website, Quotes to Scrape, designed specifically for honing scraping skills.

We'll assume you have some familiarity with Requests for basic GET operations. If not, spending a little time with its documentation first wouldn't hurt!

Setting Up Your Environment

First, ensure you have the necessary tools. You'll need the `requests` library itself and `BeautifulSoup` for parsing the HTML responses later. Install them using pip:



Now, create a Python file (let's call it `post_example.py`) and import the libraries at the top:

import requests
from bs4 import BeautifulSoup

Sending Your First POST Request

Making a POST request is straightforward with `requests.post()`. This function primarily needs two things:

  • The URL endpoint that expects the POST data.

  • A `data` argument, which is a Python dictionary containing the key-value pairs you want to send.

Let's try sending some structured data to a test API, like httpbin, which is great for experimenting:

import requests

# The URL that accepts POST requests
post_url = "https://httpbin.org/post"

# The data we want to send (as a dictionary)
payload = {
    'item_name': 'Wireless Mouse',
    'quantity': 2,
    'priority': 'High'
}

# Make the POST request
response = requests.post(post_url, data=payload)

# Print the response (httpbin echoes the data sent)
print(response.json())

Running this script sends our `payload` dictionary to `httpbin.org/post`. The server processes it and sends back a JSON response, which usually includes the data it received, confirming our POST request worked.

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "item_name": "Wireless Mouse",
    "priority": "High",
    "quantity": "2"
  },
  "headers": { ... },
  "json": null,
  "origin": "YOUR_IP_ADDRESS",
  "url": "https://httpbin.org/post"
}

See how our data appears under the `form` key? That's POST in action. Now, let's apply this to a real web scraping scenario.

Maintaining State with Sessions

By default, each `requests` call is independent. If you log in with one request, the next request won't remember you. To handle logins and stay authenticated, we need to use Session Objects.

Sessions persist information, like login cookies, across multiple requests made from the same session object, just like a web browser does.

Here's how you create and use one:

# Create a session object
session = requests.Session()

# Now, use the session object to make requests
# instead of the base requests module.

# Example GET request using the session
# response = session.get("http://quotes.toscrape.com/")

# Example POST request using the session
login_url = "http://quotes.toscrape.com/login"
login_payload = {'username': 'user', 'password': 'password'} # Placeholder data
response = session.post(login_url, data=login_payload)

Any cookies set by the server after the POST request (like a session cookie upon successful login) will be automatically stored in the `session` object and sent with subsequent requests made using `session.get()` or `session.post()`. This is key for scraping pages that require you to be logged in.

Logging Into a Website

To log in using a POST request, we need to figure out what data the login form expects. Usually, this involves inspecting the form's HTML source code on the target page (Quotes to Scrape login in our case).

Look for the `` elements within the `

`. You'll need the `name` attribute of each input field you want to fill (like username and password).


<!-- Simplified example from quotes.toscrape.com/login -->
<form method="post" action="/login">
    <input type="hidden" name="csrf_token" value="...."> <!-- Important! See below -->
    <div>
        <label for="username">Username</label>
        <input type="text" id="username" name="username"> <!-- name="username" -->
    </div>
    <div>
        <label for="password">Password</label>
        <input type="password" id="password" name="password"> <!-- name="password" -->
    </div>
    <input type="submit" value="Login">
</form>

The `name` attributes (`username`, `password`) become the keys in our `data` dictionary. We also see a hidden input named `csrf_token`. Many modern websites use CSRF tokens to prevent cross-site request forgery. You often need to first make a GET request to the login page, extract this token value from the HTML, and include it in your POST data dictionary. (For simplicity, the Quotes to Scrape login doesn't strictly enforce this, but be aware of it for real-world sites).

Let's build the login payload:

login_payload = {
    "username": "YourActualUsername",  # Replace with valid credentials if testing
    "password": "YourSecretPassword",  # Replace with valid credentials if testing
    # "csrf_token": "extracted_token_value" # Add if needed
}

# Make the login request using our session
response = session.post("http://quotes.toscrape.com/login", data=login_payload)

# Check if login was successful (e.g., by looking for a 'Logout' link)
soup = BeautifulSoup(response.text, 'html.parser')
logout_link = soup.find('a', href='/logout')

if logout_link:
    print("Login successful!")
else:
    print("Login failed.")
    # You might want to print response.text here to debug

After a successful login via `session.post()`, subsequent `session.get()` requests to protected pages should now work.

Integrating Proxies for Anonymity and Scale

Okay, we can send POST requests and log in. But what if you're scraping at scale or need to avoid revealing your own IP address? That's where proxies come in handy.

Proxies act as intermediaries. Your request goes to the proxy server, which then forwards it to the target website using its own IP address. The website sees the proxy's IP, not yours. This is essential for web scraping to avoid IP bans and to simulate traffic from different locations or devices.

Using proxies becomes particularly powerful when managing multiple accounts or tasks that require distinct identities. Imagine needing to log into hundreds of accounts – routing each through a different residential or mobile proxy from a provider like Evomi makes it look like legitimate, distributed user activity rather than a single script hammering the server.

Evomi offers various proxy types, including highly anonymous residential and mobile proxies, perfect for mimicking real users. Integrating them into your Requests session is simple. You'll get proxy access details (like host, port, username, password) from your provider dashboard.

Here's how you configure proxies within a `requests.Session`:

# Replace with your actual Evomi proxy credentials and endpoint
# Format: protocol://username:password@host:port
proxy_url = "http://your_username:your_password@rp.evomi.com:1000"  # Example for Evomi Residential HTTP

session.proxies = {
    "http": proxy_url,
    "https": proxy_url,  # Use the same proxy for HTTPS traffic
}

# Now, any request made with 'session' will route through the proxy
# response = session.get("https://httpbin.org/ip") # Check the IP seen by the server
# print(response.json()) # Should show the proxy's IP

Remember to replace the placeholder credentials and endpoint (`rp.evomi.com:1000` is for residential HTTP) with your specific details provided by Evomi. We offer competitive pricing starting from $0.49/GB for residential proxies and even provide a free trial so you can test them out.

Here is the complete example combining session, POST login, and proxies:

import requests
from bs4 import BeautifulSoup

# --- Configuration ---
LOGIN_URL = "http://quotes.toscrape.com/login"
USERNAME = "YourActualUsername"  # Use real credentials for testing
PASSWORD = "YourSecretPassword"

# Evomi Proxy configuration (replace with your details)
# Get yours from evomi.com - check our free trial!
PROXY_USER = "your_username"
PROXY_PASS = "your_password"
PROXY_HOST_PORT = "rp.evomi.com:1000"  # Example: Residential HTTP endpoint
PROXY_URL = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST_PORT}"


# --- Script Logic ---
session = requests.Session()

# Configure proxies for the session
session.proxies = {
    "http": PROXY_URL,
    "https": PROXY_URL,
}

# Optional: Verify proxy is working (uncomment to test)
# try:
#     ip_check_response = session.get("https://httpbin.org/ip")
#     print("IP Seen by Server:", ip_check_response.json())
# except requests.exceptions.RequestException as e:
#     print(f"Proxy connection failed: {e}")
#     exit()

# Prepare login data
login_payload = {
    "username": USERNAME,
    "password": PASSWORD
    # Add csrf_token here if needed for the target site
}

print(f"Attempting login to {LOGIN_URL}...")
try:
    # Perform the login POST request through the proxy
    response = session.post(LOGIN_URL, data=login_payload)
    response.raise_for_status()  # Raise an exception for bad status codes (4xx or 5xx)

    # Check for successful login indicator
    soup = BeautifulSoup(response.text, 'html.parser')
    logout_link = soup.find('a', href='/logout')

    if logout_link:
        print("Login successful via proxy!")
        # Now you can use 'session.get()' to scrape protected pages
        # protected_page = session.get("http://quotes.toscrape.com/page/2/") # Example
        # print("Successfully accessed page requiring login.")
    else:
        print("Login failed. Check credentials or website structure.")
        # print("Response content:", response.text[:500]) # Print part of response for debugging

except requests.exceptions.RequestException as e:
    print(f"An error occurred during the request: {e}")

Wrapping Up

In this walkthrough, we explored the power of POST requests in Python using the Requests library. You've seen how they differ from GET requests and how they enable interactions like submitting forms – a crucial capability for scraping websites that require logins or other data submissions.

Using `requests.post()` combined with `requests.Session` for state management offers a significantly lighter alternative to full browser automation tools when your primary goal is to interact with forms or APIs. Add proxies into the mix, like those offered by Evomi, and you gain anonymity and the ability to scale your scraping operations effectively.

If you're looking to dive deeper into the capabilities of the Requests library, including handling headers, timeouts, and more advanced scenarios, check out our comprehensive guide to the Python Requests module.

Understanding Python POST Requests for Smarter Web Scraping

When we venture into the world of web scraping, GET requests are usually our first tool of choice. We ask a server for a specific resource, get the HTML or data back, and then parse out the juicy bits we need. Simple enough, right?

However, there's another HTTP workhorse that often flies under the radar for basic scraping but is crucial for more complex interactions: the POST request. POST requests let our scripts send data to a server, mimicking actions like filling out forms or submitting information, and often getting a specific response back based on that submission.

This guide will dive into what POST requests are, demonstrate how to craft them using Python's excellent Requests library, and show you how they can level up your web scraping game, especially when combined with proxies.

So, What Exactly is a POST Request?

Think of the web as a conversation between your browser (or script) and a server. This conversation uses different verbs, known as HTTP methods, to define the purpose of each message.

GET is the most frequent method – it's like asking "Can I get the content of this page?". But sometimes, you need to send information, not just receive it. That's where POST comes in. It signals that the client is sending data to the server, often to create or update a resource.

A classic example? Website forms. When you log in, sign up, or submit a comment, your browser typically packages that data into a POST request and sends it off. This method is preferred for sensitive data (like passwords) because, unlike GET requests where data can sometimes end up visible in the URL, POST requests tuck the data away securely in the message body.

For web scrapers, this means we can use POST requests to programmatically interact with forms. Instead of firing up a full browser automation tool like Selenium (which has its place but can be overkill), we can often achieve the same result – like logging into a site – using the much lighter and faster Requests library.

Crafting POST Requests with Python's Requests Library

Let's get practical. We'll use the popular Requests library to simulate logging into a practice website, Quotes to Scrape, designed specifically for honing scraping skills.

We'll assume you have some familiarity with Requests for basic GET operations. If not, spending a little time with its documentation first wouldn't hurt!

Setting Up Your Environment

First, ensure you have the necessary tools. You'll need the `requests` library itself and `BeautifulSoup` for parsing the HTML responses later. Install them using pip:



Now, create a Python file (let's call it `post_example.py`) and import the libraries at the top:

import requests
from bs4 import BeautifulSoup

Sending Your First POST Request

Making a POST request is straightforward with `requests.post()`. This function primarily needs two things:

  • The URL endpoint that expects the POST data.

  • A `data` argument, which is a Python dictionary containing the key-value pairs you want to send.

Let's try sending some structured data to a test API, like httpbin, which is great for experimenting:

import requests

# The URL that accepts POST requests
post_url = "https://httpbin.org/post"

# The data we want to send (as a dictionary)
payload = {
    'item_name': 'Wireless Mouse',
    'quantity': 2,
    'priority': 'High'
}

# Make the POST request
response = requests.post(post_url, data=payload)

# Print the response (httpbin echoes the data sent)
print(response.json())

Running this script sends our `payload` dictionary to `httpbin.org/post`. The server processes it and sends back a JSON response, which usually includes the data it received, confirming our POST request worked.

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "item_name": "Wireless Mouse",
    "priority": "High",
    "quantity": "2"
  },
  "headers": { ... },
  "json": null,
  "origin": "YOUR_IP_ADDRESS",
  "url": "https://httpbin.org/post"
}

See how our data appears under the `form` key? That's POST in action. Now, let's apply this to a real web scraping scenario.

Maintaining State with Sessions

By default, each `requests` call is independent. If you log in with one request, the next request won't remember you. To handle logins and stay authenticated, we need to use Session Objects.

Sessions persist information, like login cookies, across multiple requests made from the same session object, just like a web browser does.

Here's how you create and use one:

# Create a session object
session = requests.Session()

# Now, use the session object to make requests
# instead of the base requests module.

# Example GET request using the session
# response = session.get("http://quotes.toscrape.com/")

# Example POST request using the session
login_url = "http://quotes.toscrape.com/login"
login_payload = {'username': 'user', 'password': 'password'} # Placeholder data
response = session.post(login_url, data=login_payload)

Any cookies set by the server after the POST request (like a session cookie upon successful login) will be automatically stored in the `session` object and sent with subsequent requests made using `session.get()` or `session.post()`. This is key for scraping pages that require you to be logged in.

Logging Into a Website

To log in using a POST request, we need to figure out what data the login form expects. Usually, this involves inspecting the form's HTML source code on the target page (Quotes to Scrape login in our case).

Look for the `` elements within the `

`. You'll need the `name` attribute of each input field you want to fill (like username and password).


<!-- Simplified example from quotes.toscrape.com/login -->
<form method="post" action="/login">
    <input type="hidden" name="csrf_token" value="...."> <!-- Important! See below -->
    <div>
        <label for="username">Username</label>
        <input type="text" id="username" name="username"> <!-- name="username" -->
    </div>
    <div>
        <label for="password">Password</label>
        <input type="password" id="password" name="password"> <!-- name="password" -->
    </div>
    <input type="submit" value="Login">
</form>

The `name` attributes (`username`, `password`) become the keys in our `data` dictionary. We also see a hidden input named `csrf_token`. Many modern websites use CSRF tokens to prevent cross-site request forgery. You often need to first make a GET request to the login page, extract this token value from the HTML, and include it in your POST data dictionary. (For simplicity, the Quotes to Scrape login doesn't strictly enforce this, but be aware of it for real-world sites).

Let's build the login payload:

login_payload = {
    "username": "YourActualUsername",  # Replace with valid credentials if testing
    "password": "YourSecretPassword",  # Replace with valid credentials if testing
    # "csrf_token": "extracted_token_value" # Add if needed
}

# Make the login request using our session
response = session.post("http://quotes.toscrape.com/login", data=login_payload)

# Check if login was successful (e.g., by looking for a 'Logout' link)
soup = BeautifulSoup(response.text, 'html.parser')
logout_link = soup.find('a', href='/logout')

if logout_link:
    print("Login successful!")
else:
    print("Login failed.")
    # You might want to print response.text here to debug

After a successful login via `session.post()`, subsequent `session.get()` requests to protected pages should now work.

Integrating Proxies for Anonymity and Scale

Okay, we can send POST requests and log in. But what if you're scraping at scale or need to avoid revealing your own IP address? That's where proxies come in handy.

Proxies act as intermediaries. Your request goes to the proxy server, which then forwards it to the target website using its own IP address. The website sees the proxy's IP, not yours. This is essential for web scraping to avoid IP bans and to simulate traffic from different locations or devices.

Using proxies becomes particularly powerful when managing multiple accounts or tasks that require distinct identities. Imagine needing to log into hundreds of accounts – routing each through a different residential or mobile proxy from a provider like Evomi makes it look like legitimate, distributed user activity rather than a single script hammering the server.

Evomi offers various proxy types, including highly anonymous residential and mobile proxies, perfect for mimicking real users. Integrating them into your Requests session is simple. You'll get proxy access details (like host, port, username, password) from your provider dashboard.

Here's how you configure proxies within a `requests.Session`:

# Replace with your actual Evomi proxy credentials and endpoint
# Format: protocol://username:password@host:port
proxy_url = "http://your_username:your_password@rp.evomi.com:1000"  # Example for Evomi Residential HTTP

session.proxies = {
    "http": proxy_url,
    "https": proxy_url,  # Use the same proxy for HTTPS traffic
}

# Now, any request made with 'session' will route through the proxy
# response = session.get("https://httpbin.org/ip") # Check the IP seen by the server
# print(response.json()) # Should show the proxy's IP

Remember to replace the placeholder credentials and endpoint (`rp.evomi.com:1000` is for residential HTTP) with your specific details provided by Evomi. We offer competitive pricing starting from $0.49/GB for residential proxies and even provide a free trial so you can test them out.

Here is the complete example combining session, POST login, and proxies:

import requests
from bs4 import BeautifulSoup

# --- Configuration ---
LOGIN_URL = "http://quotes.toscrape.com/login"
USERNAME = "YourActualUsername"  # Use real credentials for testing
PASSWORD = "YourSecretPassword"

# Evomi Proxy configuration (replace with your details)
# Get yours from evomi.com - check our free trial!
PROXY_USER = "your_username"
PROXY_PASS = "your_password"
PROXY_HOST_PORT = "rp.evomi.com:1000"  # Example: Residential HTTP endpoint
PROXY_URL = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST_PORT}"


# --- Script Logic ---
session = requests.Session()

# Configure proxies for the session
session.proxies = {
    "http": PROXY_URL,
    "https": PROXY_URL,
}

# Optional: Verify proxy is working (uncomment to test)
# try:
#     ip_check_response = session.get("https://httpbin.org/ip")
#     print("IP Seen by Server:", ip_check_response.json())
# except requests.exceptions.RequestException as e:
#     print(f"Proxy connection failed: {e}")
#     exit()

# Prepare login data
login_payload = {
    "username": USERNAME,
    "password": PASSWORD
    # Add csrf_token here if needed for the target site
}

print(f"Attempting login to {LOGIN_URL}...")
try:
    # Perform the login POST request through the proxy
    response = session.post(LOGIN_URL, data=login_payload)
    response.raise_for_status()  # Raise an exception for bad status codes (4xx or 5xx)

    # Check for successful login indicator
    soup = BeautifulSoup(response.text, 'html.parser')
    logout_link = soup.find('a', href='/logout')

    if logout_link:
        print("Login successful via proxy!")
        # Now you can use 'session.get()' to scrape protected pages
        # protected_page = session.get("http://quotes.toscrape.com/page/2/") # Example
        # print("Successfully accessed page requiring login.")
    else:
        print("Login failed. Check credentials or website structure.")
        # print("Response content:", response.text[:500]) # Print part of response for debugging

except requests.exceptions.RequestException as e:
    print(f"An error occurred during the request: {e}")

Wrapping Up

In this walkthrough, we explored the power of POST requests in Python using the Requests library. You've seen how they differ from GET requests and how they enable interactions like submitting forms – a crucial capability for scraping websites that require logins or other data submissions.

Using `requests.post()` combined with `requests.Session` for state management offers a significantly lighter alternative to full browser automation tools when your primary goal is to interact with forms or APIs. Add proxies into the mix, like those offered by Evomi, and you gain anonymity and the ability to scale your scraping operations effectively.

If you're looking to dive deeper into the capabilities of the Requests library, including handling headers, timeouts, and more advanced scenarios, check out our comprehensive guide to the Python Requests module.

Author

Sarah Whitmore

Digital Privacy & Cybersecurity Consultant

About Author

Sarah is a cybersecurity strategist with a passion for online privacy and digital security. She explores how proxies, VPNs, and encryption tools protect users from tracking, cyber threats, and data breaches. With years of experience in cybersecurity consulting, she provides practical insights into safeguarding sensitive data in an increasingly digital world.

Like this article? Share it.
You asked, we answer - Users questions:
How do I send data in JSON format instead of form data with a Python POST request?+
What are common reasons for POST requests failing during web scraping, and how can I debug them?+
The article mentions CSRF tokens. How do I reliably extract and include them in my POST requests using Python?+
Besides the payload data, what other HTTP headers are often important when making POST requests for web scraping?+
For large-scale scraping tasks requiring many logins or submissions, how can I rotate through multiple proxies using Python Requests?+

In This Article

Read More Blogs