Skip to content

Python Networking — requests, urllib & sockets

Networking in Python spans two levels: high-level HTTP (talking to web APIs and sites) and low-level sockets (raw TCP/UDP connections). Most apps live at the HTTP level.

ToolLevelNotes
requestsrequestsHigh (HTTP)Third-party, the de-facto standard. pip install requestspip install requests.
urllib.requesturllib.requestHigh (HTTP)Built in; more verbose, no install needed.
urllib.parseurllib.parseHelperBuild and parse URLs (pure, no network).
socketsocketLow (TCP/UDP)Raw connections; build your own protocols.

requests — the friendly HTTP client

requestsrequests makes HTTP calls readable. Install it first (pip install requestspip install requests).

requests_get.py
import requests
 
resp = requests.get("https://api.example.com/users", params={"page": 2})
print(resp.status_code)     # 200
print(resp.headers["Content-Type"])
data = resp.json()          # parse a JSON body into Python
print(data)
requests_get.py
import requests
 
resp = requests.get("https://api.example.com/users", params={"page": 2})
print(resp.status_code)     # 200
print(resp.headers["Content-Type"])
data = resp.json()          # parse a JSON body into Python
print(data)
requests_post.py
import requests
 
# Send JSON in the body
resp = requests.post(
    "https://api.example.com/users",
    json={"name": "Ada", "role": "admin"},
    headers={"Authorization": "Bearer TOKEN"},
    timeout=10,
)
resp.raise_for_status()     # raise if status is 4xx/5xx
print(resp.json())
requests_post.py
import requests
 
# Send JSON in the body
resp = requests.post(
    "https://api.example.com/users",
    json={"name": "Ada", "role": "admin"},
    headers={"Authorization": "Bearer TOKEN"},
    timeout=10,
)
resp.raise_for_status()     # raise if status is 4xx/5xx
print(resp.json())
requestsrequests piecePurpose
requests.get/post/put/deleterequests.get/post/put/deleteThe HTTP verbs.
params={...}params={...}Query-string parameters.
json={...}json={...}Send a JSON body.
headers={...}headers={...}Custom request headers.
resp.status_coderesp.status_codeThe HTTP status (200, 404, …).
resp.json()resp.json() / resp.textresp.textParsed JSON / raw text.
resp.raise_for_status()resp.raise_for_status()Turn error statuses into exceptions.
timeout=timeout=Fail fast instead of hanging forever.

urllib — the standard-library option

When you can’t add dependencies, urllib.requesturllib.request does the same job with more ceremony.

urllib_get.py
import urllib.request
import json
 
req = urllib.request.Request(
    "https://api.example.com/data",
    headers={"User-Agent": "my-app"},
)
with urllib.request.urlopen(req, timeout=10) as resp:
    body = resp.read().decode("utf-8")
    data = json.loads(body)
print(data)
urllib_get.py
import urllib.request
import json
 
req = urllib.request.Request(
    "https://api.example.com/data",
    headers={"User-Agent": "my-app"},
)
with urllib.request.urlopen(req, timeout=10) as resp:
    body = resp.read().decode("utf-8")
    data = json.loads(body)
print(data)

urllib.parse — build and parse URLs

This part of urlliburllib is pure (no network) and extremely useful for assembling query strings and dissecting URLs.

urllib_parse.py
from urllib.parse import urlencode, urlparse, parse_qs
 
# Build a query string from a dict
qs = urlencode({"q": "python", "page": 2})
print(qs)                     # q=python&page=2
 
# Dissect a URL
parts = urlparse("https://example.com/search?q=python&page=2")
print(parts.scheme)           # https
print(parts.netloc)           # example.com
print(parts.path)             # /search
print(parse_qs(parts.query))  # {'q': ['python'], 'page': ['2']}
urllib_parse.py
from urllib.parse import urlencode, urlparse, parse_qs
 
# Build a query string from a dict
qs = urlencode({"q": "python", "page": 2})
print(qs)                     # q=python&page=2
 
# Dissect a URL
parts = urlparse("https://example.com/search?q=python&page=2")
print(parts.scheme)           # https
print(parts.netloc)           # example.com
print(parts.path)             # /search
print(parse_qs(parts.query))  # {'q': ['python'], 'page': ['2']}

socket — low-level TCP

For custom protocols or learning how the network really works, use raw sockets. Here’s a minimal echo server and client.

tcp_server.py
import socket
 
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 9000))
server.listen()
print("listening on 9000")
 
conn, addr = server.accept()      # blocks until a client connects
with conn:
    data = conn.recv(1024)        # read up to 1024 bytes
    conn.sendall(data)            # echo it back
tcp_server.py
import socket
 
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 9000))
server.listen()
print("listening on 9000")
 
conn, addr = server.accept()      # blocks until a client connects
with conn:
    data = conn.recv(1024)        # read up to 1024 bytes
    conn.sendall(data)            # echo it back
tcp_client.py
import socket
 
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(("127.0.0.1", 9000))
    s.sendall(b"hello")
    reply = s.recv(1024)
print(reply)                      # b'hello'
tcp_client.py
import socket
 
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(("127.0.0.1", 9000))
    s.sendall(b"hello")
    reply = s.recv(1024)
print(reply)                      # b'hello'
socket callPurpose
socket(AF_INET, SOCK_STREAM)socket(AF_INET, SOCK_STREAM)Create a TCP socket.
bind((host, port))bind((host, port))Attach a server to an address.
listen()listen() / accept()accept()Wait for and accept a connection.
connect((host, port))connect((host, port))Client connects to a server.
sendall(bytes)sendall(bytes) / recv(n)recv(n)Send / receive raw bytes.

Sockets speak bytes, not strings — encode with .encode().encode() and decode with .decode().decode().

Choosing a level

decision.txt
Talking to a web API or website?   -> requests (or urllib if no installs)
Just building/parsing a URL?       -> urllib.parse
Custom protocol / raw TCP/UDP?     -> socket
decision.txt
Talking to a web API or website?   -> requests (or urllib if no installs)
Just building/parsing a URL?       -> urllib.parse
Custom protocol / raw TCP/UDP?     -> socket

Common pitfalls

  • No timeout — network calls can hang forever; always pass timeout=timeout=.
  • Ignoring status codes — check resp.status_coderesp.status_code or call raise_for_status()raise_for_status().
  • Sending strings over sockets — sockets need bytes; encode/decode explicitly.
  • requestsrequests isn’t built in — it needs pip install requestspip install requests; urlliburllib does not.

Practice Exercises

These exercises use urllib.parseurllib.parse, which runs without any network access.

Exercise 1 – Build a query string

Exercise 2 – Extract the host from a URL

Exercise 3 – Parse query parameters

Summary

  • For HTTP, reach for requestsrequests (clean API) or urllib.requesturllib.request (built in, verbose).
  • Always set a timeouttimeout and check the status code.
  • urllib.parseurllib.parse builds and dissects URLs with urlencodeurlencode, urlparseurlparse, and parse_qsparse_qs.
  • socketsocket gives raw TCP/UDP for custom protocols — remember it speaks bytes.
  • Pick the level that matches the task: APIs → requests, URLs → urllib.parse, raw connections → socket.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did