Automate the Boring Stuff with Python: 5 Modern Scripts (2026)
The original version of this post carried five scripts; two of them mostly duplicated library documentation. Here are the three that earn their place on a hard drive — the downloads-folder organiser, the async scraper, and the bulk image resizer — plus the one command that stitches them into a pipeline.
Setup, once
Python 3.13 from python.org, then a virtual environment with uv, the fast modern installer:
uv venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
uv pip install httpx beautifulsoup4 Pillow
1. Tame the Downloads folder with pathlib
from pathlib import Path
from shutil import move
def organize(directory: Path) -> None:
for file in directory.iterdir():
if file.is_file() and file.suffix:
target = directory / file.suffix.lstrip(".").lower()
target.mkdir(exist_ok=True)
move(file, target / file.name)
organize(Path.home() / "Downloads")
Sixty files of chaos become folders named pdf, zip and xlsx. pathlib keeps the code cross-platform — Path.home() resolves correctly on Windows, macOS and Linux without a single conditional.
2. Scrape without the wait, with httpx
import asyncio, httpx
from bs4 import BeautifulSoup
async def fetch_headlines() -> list[str]:
async with httpx.AsyncClient(timeout=10.0) as client:
r = await client.get("https://news.ycombinator.com")
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
return [a.get_text(strip=True) for a in soup.select("span.titleline > a")]
for i, title in enumerate(asyncio.run(fetch_headlines()), 1):
print(f"{i}. {title}")
The synchronous requests habit works, but the async habit scales: point the same AsyncClient at a list of two hundred URLs with asyncio.gather and the loop finishes before the kettle does. Wrap the call in a try/except httpx.HTTPError and the network's worst day becomes a log line instead of a crash.
3. Resize a whole folder with Pillow
from pathlib import Path
from PIL import Image
def resize_all(source: Path, output: Path, width: int) -> None:
output.mkdir(parents=True, exist_ok=True)
for img_path in source.glob("*"):
if img_path.suffix.lower() in {".png", ".jpg", ".jpeg", ".webp"}:
with Image.open(img_path) as img:
ratio = width / img.width
new = img.resize((width, int(img.height * ratio)),
Image.Resampling.LANCZOS)
new.save(output / img_path.name)
The with block matters more than it looks — it releases the file handle even when an image is corrupt. LANCZOS is the resampling filter that keeps text in screenshots readable. Point it at a source_images folder, run it once, and every Reel-sized asset your shop needs is ready in seconds.
Stitch them together
These three are LEGO bricks: scrape a page of prices, resize the product photos, organise the output by date, and you have the bones of an e-commerce back office in an afternoon. Schedule the script with cron on Linux or Task Scheduler on Windows, and the boring stuff simply stops appearing on your desk.
Somewhere in Gaza tonight, a developer is shipping working code between power cuts, because automation is not a hobby when nothing runs on schedule. Their discipline humbles every clever script above.
Every hour a script saves is an hour with your name on it — and hours, in our experience, become weekends. When enough of them pile up, HTG Travels knows exactly what to do with a Thursday-to-Sunday window: the northern road trip your calendar keeps deleting. Automate the boring; keep the mountains manual.




