Weeks 7–10 — Web Scraping · NumPy & Pandas · Packaging & Distribution · Advanced Capstone: Async REST Client
Extract structured data from web pages ethically. Use requests for HTTP and BeautifulSoup for parsing HTML. Implement rate limiting, respect robots.txt, and cache results. Accelerate numerical computation with NumPy arrays and explore tabular data with Pandas. Combine them with Matplotlib to visualize results. Turn your Python code into a distributable package. Learn pyproject.toml, build tools (hatch/setuptools), create CLI entry points, and publish to PyPI (or TestPyPI). Combine the full advanced track into a production-grade async REST API client library, complete with type hints, retry logic, caching, pytest test suite, and a pip-installable package.
What you'll learn
- 1Send HTTP requests and inspect responses with requests
- 2Parse HTML trees with BeautifulSoup4
- 3Handle pagination and dynamic content
- 4Respect robots.txt and implement polite rate limiting
- 5Cache responses to avoid redundant requests
- 6Create and manipulate NumPy arrays with vectorized operations
- 7Understand broadcasting rules
- 8Load, clean, and filter data with Pandas DataFrames
- 9Aggregate with groupby and pivot_table
- 10Create line, bar, and scatter plots with Matplotlib
- 11Structure a Python package with src/ layout
- 12Write pyproject.toml with metadata and dependencies
- 13Build wheel and sdist distributions
- 14Create CLI commands with entry_points.scripts
- 15Publish to TestPyPI and install with pip
- 16Design a clean async client API with type hints
- 17Implement retry, timeout, and rate-limiting middleware
- 18Cache responses with TTL using asyncio
- 19Write 90%+ coverage tests with pytest-asyncio
- 20Package and install as a distributable library
Week 7 — Web Scraping
Extract structured data from web pages ethically. Use requests for HTTP and BeautifulSoup for parsing HTML. Implement rate limiting, respect robots.txt, and cache results.
1. requests
import requests
resp = requests.get(
"https://httpbin.org/get",
params={"key": "value"},
headers={"User-Agent": "my-scraper/1.0"},
timeout=10,
)
print(resp.status_code) # 200
print(resp.headers["Content-Type"])
data = resp.json()
print(data["args"]) # {'key': 'value'}2. BeautifulSoup
from bs4 import BeautifulSoup
import requests
html = requests.get("https://news.ycombinator.com").text
soup = BeautifulSoup(html, "html.parser")
# Select all story titles
for item in soup.select(".titleline > a")[:5]:
print(item.text)
print(" ", item.get("href"))3. Polite Scraping
import time, random, requests
class PoliteScraper:
def __init__(self, min_delay=1.0, max_delay=3.0):
self.session = requests.Session()
self.session.headers["User-Agent"] = "MyBot/1.0 (+https://example.com)"
self._min = min_delay
self._max = max_delay
self._last = 0
def get(self, url):
wait = self._min + random.random() * (self._max - self._min)
elapsed = time.time() - self._last
if elapsed < wait:
time.sleep(wait - elapsed)
self._last = time.time()
return self.session.get(url, timeout=10)Always check robots.txt before scraping. Never hammer a server with rapid requests. Identify your bot with a descriptive User-Agent.
Week 8 — NumPy & Pandas
Accelerate numerical computation with NumPy arrays and explore tabular data with Pandas. Combine them with Matplotlib to visualize results.
1. NumPy Arrays
import numpy as np
a = np.array([1, 2, 3, 4, 5])
print(a * 2) # [2 4 6 8 10] — vectorized
print(a[a > 3]) # [4 5] — boolean indexing
# 2D array
m = np.arange(9).reshape(3, 3)
print(m)
print(m.T) # transpose
print(m @ m) # matrix multiply2. Pandas DataFrame
import pandas as pd
df = pd.DataFrame({
"name": ["Alice","Bob","Carol","Dave"],
"dept": ["Eng","Eng","HR","HR"],
"score": [92, 85, 78, 88]
})
print(df.describe())
print(df[df["score"] >= 85])
print(df.groupby("dept")["score"].mean())3. Data Cleaning
df = pd.read_csv("data.csv")
# Inspect
print(df.shape, df.dtypes)
print(df.isnull().sum())
# Clean
df = df.dropna(subset=["score"]) # drop rows with no score
df["score"] = pd.to_numeric(df["score"], errors="coerce")
df = df[df["score"].between(0, 100)] # valid range
df = df.drop_duplicates()Week 9 — Packaging & Distribution
Turn your Python code into a distributable package. Learn pyproject.toml, build tools (hatch/setuptools), create CLI entry points, and publish to PyPI (or TestPyPI).
1. Package Structure
mypackage/
├── src/
│ └── mypackage/
│ ├── __init__.py
│ ├── core.py
│ └── cli.py
├── tests/
│ └── test_core.py
├── pyproject.toml
└── README.md2. pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mypackage"
version = "0.1.0"
description = "My first Python package"
requires-python = ">=3.10"
dependencies = ["requests>=2.28"]
[project.scripts]
mypkg = "mypackage.cli:main"
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"]3. Build & Publish
pip install hatch
hatch build # creates dist/*.whl and dist/*.tar.gz
# Publish to TestPyPI
pip install twine
twine upload --repository testpypi dist/*
# Install from TestPyPI
pip install --index-url https://test.pypi.org/simple/ mypackageWeek 10 — Advanced Capstone: Async REST Client
Combine the full advanced track into a production-grade async REST API client library, complete with type hints, retry logic, caching, pytest test suite, and a pip-installable package.
1. Project Specification
# Usage example
from apiclient import AsyncClient, RetryPolicy
async def main():
policy = RetryPolicy(max_retries=3, backoff=2.0)
async with AsyncClient("https://api.example.com", retry=policy) as client:
users = await client.get("/users", params={"page": 1})
post = await client.post("/posts", json={"title": "Hello"})
print(users, post)2. Architecture
- AsyncClient — main client class with session management
- RetryPolicy dataclass — max_retries, backoff, retry_on (status codes)
- ResponseCache — asyncio-based TTL cache
- Middleware protocol — pluggable request/response hooks
- Exceptions — APIError, RateLimitError, TimeoutError
3. Key Implementation Points
import asyncio, aiohttp
from dataclasses import dataclass, field
from typing import Any, Optional
@dataclass
class RetryPolicy:
max_retries: int = 3
backoff: float = 1.0
retry_on: tuple = (429, 500, 502, 503)
async def _request_with_retry(
session: aiohttp.ClientSession,
method: str,
url: str,
policy: RetryPolicy,
**kwargs: Any,
) -> aiohttp.ClientResponse:
last_exc: Optional[Exception] = None
for attempt in range(policy.max_retries + 1):
try:
resp = await session.request(method, url, **kwargs)
if resp.status in policy.retry_on and attempt < policy.max_retries:
await asyncio.sleep(policy.backoff * (2 ** attempt))
continue
return resp
except aiohttp.ClientError as e:
last_exc = e
if attempt < policy.max_retries:
await asyncio.sleep(policy.backoff * (2 ** attempt))
raise last_exc or RuntimeError("Request failed")💻 Examples
Run these examples and check the output yourself.
import requests, json
from bs4 import BeautifulSoup
import time
BASE = "https://news.ycombinator.com"
def scrape_page(page=1):
url = BASE if page == 1 else f"{BASE}/?p={page}"
time.sleep(1) # polite delay
soup = BeautifulSoup(requests.get(url, timeout=10).text, "html.parser")
stories = []
for el in soup.select(".athing"):
title_el = el.select_one(".titleline > a")
if title_el:
stories.append({"title": title_el.text, "url": title_el.get("href")})
return stories
stories = scrape_page(1)
print(json.dumps(stories[:3], indent=2))
import numpy as np
import pandas as pd
# Create sample data
np.random.seed(42)
df = pd.DataFrame({
"name": [f"Student{i}" for i in range(50)],
"math": np.random.randint(40, 100, 50),
"eng": np.random.randint(40, 100, 50),
"sci": np.random.randint(40, 100, 50),
})
df["avg"] = df[["math", "eng", "sci"]].mean(axis=1)
df["grade"] = pd.cut(df["avg"], bins=[0,60,70,80,90,100],
labels=["F","D","C","B","A"])
print(df.groupby("grade").agg(count=("name","count"), mean_avg=("avg","mean")))
print("\nTop 5 students:")
print(df.nlargest(5, "avg")[["name","avg","grade"]])
import argparse
from .core import process
def main():
parser = argparse.ArgumentParser(description="MyPackage CLI")
parser.add_argument("input", help="Input file")
parser.add_argument("-o", "--output", default="output.txt")
parser.add_argument("-v", "--verbose", action="store_true")
args = parser.parse_args()
result = process(args.input, verbose=args.verbose)
with open(args.output, "w") as f:
f.write(result)
print(f"Wrote {args.output}")
if __name__ == "__main__":
main()
📝 Exercises
Try them yourself first, then open the solution to compare.
Quote scraper
Goal: Scrape all quotes and authors from quotes.toscrape.com (all pages).
- Follow 'Next' pagination links until none remain
- Store as list of {quote, author, tags} dicts
- Save to quotes.json with json.dump
▶Toggle solution
import requests, json, time
from bs4 import BeautifulSoup
BASE = "http://quotes.toscrape.com"
all_quotes = []
url = BASE
while url:
soup = BeautifulSoup(requests.get(url, timeout=10).text, "html.parser")
for q in soup.select(".quote"):
all_quotes.append({
"quote": q.select_one(".text").text.strip('\u201c\u201d'),
"author": q.select_one(".author").text,
"tags": [t.text for t in q.select(".tag")],
})
nxt = soup.select_one("li.next a")
url = BASE + nxt["href"] if nxt else None
time.sleep(0.5)
print(f"Scraped {len(all_quotes)} quotes")
with open("quotes.json", "w") as f:
json.dump(all_quotes, f, indent=2)
Sales Analysis
Goal: Load sales.csv and produce a monthly revenue report.
- Load CSV with pd.read_csv
- Parse date column, extract year-month
- Group by month, sum revenue
- Plot bar chart with matplotlib
- Print top 3 months
▶Toggle solution
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv("sales.csv", parse_dates=["date"])
df["month"] = df["date"].dt.to_period("M")
monthly = df.groupby("month")["revenue"].sum().sort_index()
print("Top 3 months:")
print(monthly.nlargest(3))
monthly.plot(kind="bar", title="Monthly Revenue")
plt.tight_layout()
plt.savefig("monthly_revenue.png")
print("Chart saved.")
Build your own package
Goal: Package and distribute the text analyzer from Week 10 capstone.
- src/ layout with pyproject.toml
- CLI entry point: analyze <file> [--top N] [--format text|csv]
- Unit tests with pytest
- Build a wheel with hatch build
- · Package installs with pip — 30%
- · CLI entry point works — 30%
- · Tests pass — 30%
- · Wheel file created — 10%
Implement the client
Goal: Build the async REST client library described above.
- AsyncClient as async context manager
- RetryPolicy with exponential backoff
- Response cache with TTL
- pytest-asyncio test suite (90%+ coverage)
- Installable with pip (pyproject.toml)
- · Core client works — 30%
- · Retry logic correct — 25%
- · Cache implementation — 20%
- · Tests 90%+ coverage — 25%
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗Comments 0
No comments yet. Be the first.
Only members can comment.
Comments with ads, abuse, personal information or illegal content are hidden or removed. Comments are shown with your nickname. Privacy Policy