Cheat sheet — Automating the Web¶
Companion to Module 07 — Automating the Web · CC BY 4.0 — print it, pin it, share it.
Last reviewed: 2026-07
Scrape only hosts you own or have explicit written permission to test. Respect
robots.txtand scope.
httpx.Client — a session that carries cookies¶
import httpx
with httpx.Client(
base_url="http://localhost:5000",
headers={"User-Agent": "plaintext-scraper/1.0"}, # identify yourself
timeout=10.0, # never scrape without a timeout
follow_redirects=True, # follow 3xx (default is False on Client!)
) as client:
r = client.get("/")
r = client.post("/login", data={"user": "x", "pass": "y"}) # cookies now stored on client
r = client.get("/dashboard") # session cookie resent automatically
Cookies & redirect inspection¶
client.cookies # the session cookie jar (dict-like)
client.cookies.get("session") # read a specific cookie
r.cookies # cookies set by THIS response
r = client.get("/old", follow_redirects=False) # DON'T follow — inspect the hop
r.status_code # 301 / 302
r.headers["location"] # where it points
r.history # list of intermediate responses when following
BeautifulSoup — parse the HTML¶
from bs4 import BeautifulSoup
soup = BeautifulSoup(r.text, "html.parser") # stdlib parser; "lxml" if installed & faster
soup.find("a") # first <a> or None
soup.find_all("a") # every <a> (a list)
soup.find_all("a", href=True) # only <a> that HAVE an href
soup.find_all("input", {"type": "hidden"}) # attribute filter (finds CSRF tokens etc.)
soup.select("div.result a") # CSS selector -> list
soup.select_one("#main") # CSS selector -> first match
Pulling data out of tags¶
for a in soup.find_all("a", href=True):
a["href"] # attribute access (KeyError if absent)
a.get("href") # safe access -> None if absent
a.attrs # dict of all attributes
a.text # visible text of the tag and descendants
a.get_text(strip=True) # text with whitespace collapsed/trimmed
soup.title.string # <title> text
[img["src"] for img in soup.find_all("img", src=True)]
Find what isn't linked — regex the raw source¶
import re
# find_all misses paths buried in <script>, comments, and attributes — regex the raw text.
paths = set(re.findall(r"/[\w/.-]+", r.text)) # /admin, /api/v2/internal, ...
comments = re.findall(r"<!--(.*?)-->", r.text, re.DOTALL)
Scope guard — the non-negotiable¶
from urllib.parse import urljoin, urlparse
TARGET_HOST = "localhost"
def in_scope(url) -> bool:
return urlparse(url).netloc in ("", TARGET_HOST) # "" = relative/same-host link
# Resolve relative hrefs against the current page before checking scope.
for a in soup.find_all("a", href=True):
full = urljoin(str(r.url), a["href"])
if in_scope(full):
queue.append(full) # follow only in-scope links
A bounded crawl loop¶
seen, queue, MAX_DEPTH = set(), [(start, 0)], 3
while queue:
url, depth = queue.pop()
if url in seen or depth > MAX_DEPTH: # dedup + depth cap -> the crawl terminates
continue
seen.add(url)
try:
resp = client.get(url)
except httpx.HTTPError: # a 404/timeout must not kill the crawl
continue
... # parse, enqueue in-scope links at depth+1
Gotchas worth remembering¶
- A crawler with no scope guard becomes unauthorized access. Without
urlparse(url).netloc == target_hostand a depth limit, one off-domain link walks your scoped recon onto someone else's host. This is the single most common — and most dangerous — scraper bug. follow_redirectsdefaults toFalseonhttpx.Client. Set it explicitly. Forgetting means your scraper silently stops at every 302 login redirect and "finds nothing".find_allonly sees the parse tree. Endpoints hide in<script>bodies, comments, and JS strings — regex the rawresponse.textto surface the paths BeautifulSoup won't expose.- Use
.get()/href=Truefor attributes that may be absent.tag["href"]raisesKeyErroron a link without one; filter withhref=Trueor read with.get("href"). .textincludes descendant text.<a>Home<span>(new)</span></a>.textis"Home(new)"— use.get_text(strip=True)when you want it cleaned, and don't assume it's just the anchor label.- Identify yourself and rate-limit. A real
User-Agent, a small delay between requests, and readingrobots.txtare what separate responsible automation from a hostile flood.
Comments
Sign in with GitHub to comment. Choose the type: Feedback (errors or suggestions on this page) · Hints (help for fellow learners — no spoilers) · General (anything else).