The best way to learn Python isn't to read a textbook — it's to build something that solves a real problem you have. Automation scripts are perfect for this: they're small enough to finish in an afternoon, they use real libraries, and when they work, they actually save you time. Here are five scripts worth writing this weekend, with complete working code for each.
python -m venv venv and activate it (source venv/bin/activate on Mac/Linux, venv\Scripts\activate on Windows). Install dependencies as you go with pip install.
Script 1: File Organiser — Sort Your Downloads Folder by Type
If your Downloads folder looks like a digital junk drawer, this script is for you. It scans the folder and moves every file into a subfolder based on its type — all images go to Images/, all PDFs go to Documents/, and so on. Uses only Python's standard library: os, shutil, and pathlib.
import os
import shutil
from pathlib import Path
# Map file extensions to folder names
EXTENSION_MAP = {
# Images
".jpg": "Images", ".jpeg": "Images", ".png": "Images",
".gif": "Images", ".webp": "Images", ".svg": "Images",
# Documents
".pdf": "Documents", ".docx": "Documents", ".doc": "Documents",
".xlsx": "Documents", ".csv": "Documents", ".txt": "Documents",
# Videos
".mp4": "Videos", ".mov": "Videos", ".avi": "Videos",
# Audio
".mp3": "Audio", ".wav": "Audio", ".flac": "Audio",
# Code
".py": "Code", ".js": "Code", ".html": "Code", ".css": "Code",
# Archives
".zip": "Archives", ".tar": "Archives", ".gz": "Archives",
}
def organise_folder(folder_path: str) -> None:
folder = Path(folder_path)
moved = 0
skipped = 0
for file in folder.iterdir():
if file.is_dir():
continue # skip folders
ext = file.suffix.lower()
subfolder_name = EXTENSION_MAP.get(ext, "Other")
destination = folder / subfolder_name
destination.mkdir(exist_ok=True)
dest_file = destination / file.name
if dest_file.exists():
print(f"Skipping (already exists): {file.name}")
skipped += 1
continue
shutil.move(str(file), str(dest_file))
print(f"Moved: {file.name} → {subfolder_name}/")
moved += 1
print(f"\nDone! Moved {moved} files, skipped {skipped}.")
if __name__ == "__main__":
downloads = str(Path.home() / "Downloads")
organise_folder(downloads)
Script 2: Email Sender — Personalised Bulk Email From a CSV
Need to email a list of contacts with personalised messages? This script reads a CSV of names and emails, and sends a customised email to each using Python's built-in smtplib. Works with Gmail (enable App Passwords in your Google account settings).
import smtplib
import csv
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# Your email config — use environment variables in production!
SENDER_EMAIL = "your@gmail.com"
APP_PASSWORD = "your_app_password" # NOT your regular password
SMTP_HOST = "smtp.gmail.com"
SMTP_PORT = 587
def send_email(recipient_name: str, recipient_email: str) -> None:
msg = MIMEMultipart("alternative")
msg["Subject"] = f"Hello {recipient_name}!"
msg["From"] = SENDER_EMAIL
msg["To"] = recipient_email
body = f"""Hi {recipient_name},
Thank you for joining our community! We're excited to have you.
Here are a few things to get you started:
- Check out our latest blog posts at vizionikra.ai/blog
- Join our community Discord for support
- Reply to this email with any questions
Best,
The VizioNikraAI Team"""
msg.attach(MIMEText(body, "plain"))
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
server.starttls()
server.login(SENDER_EMAIL, APP_PASSWORD)
server.sendmail(SENDER_EMAIL, recipient_email, msg.as_string())
def send_bulk_from_csv(csv_path: str) -> None:
with open(csv_path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
try:
send_email(row["name"], row["email"])
print(f"Sent to {row['email']}")
except Exception as e:
print(f"Failed for {row['email']}: {e}")
if __name__ == "__main__":
send_bulk_from_csv("contacts.csv")
# CSV format: name,email
# Maria,maria@example.com
# John,john@example.com
Script 3: Website Monitor — Get Alerted When a Site Goes Down
This script checks a list of URLs every 5 minutes and emails you if any of them return a non-200 status or become unreachable. Uses requests for HTTP and schedule for the timer loop.
import requests
import schedule
import time
import smtplib
from datetime import datetime
from email.mime.text import MIMEText
SITES_TO_MONITOR = [
"https://yoursite.com",
"https://api.yoursite.com/health",
]
SENDER_EMAIL = "monitor@gmail.com"
ALERT_EMAIL = "you@gmail.com"
APP_PASSWORD = "your_app_password"
def send_alert(url: str, status: str) -> None:
msg = MIMEText(f"ALERT: {url} is DOWN\nStatus: {status}\nTime: {datetime.now()}")
msg["Subject"] = f"Site Down: {url}"
msg["From"] = SENDER_EMAIL
msg["To"] = ALERT_EMAIL
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login(SENDER_EMAIL, APP_PASSWORD)
server.sendmail(SENDER_EMAIL, ALERT_EMAIL, msg.as_string())
def check_sites() -> None:
print(f"[{datetime.now().strftime('%H:%M:%S')}] Checking {len(SITES_TO_MONITOR)} sites...")
for url in SITES_TO_MONITOR:
try:
response = requests.get(url, timeout=10)
if response.status_code != 200:
print(f"DOWN: {url} — HTTP {response.status_code}")
send_alert(url, f"HTTP {response.status_code}")
else:
print(f"OK: {url}")
except requests.exceptions.RequestException as e:
print(f"DOWN: {url} — {e}")
send_alert(url, str(e))
schedule.every(5).minutes.do(check_sites)
check_sites() # Run immediately on start
while True:
schedule.run_pending()
time.sleep(30)
Script 4: PDF Merger — Combine Multiple PDFs Into One
Stop manually copying content between PDFs. This script takes every PDF in a folder (or a specific list) and merges them into a single file. Uses PyPDF2 (or the newer pypdf package).
import pypdf # pip install pypdf
from pathlib import Path
def merge_pdfs(input_folder: str, output_file: str) -> None:
folder = Path(input_folder)
pdf_files = sorted(folder.glob("*.pdf"))
if not pdf_files:
print("No PDF files found in the folder.")
return
writer = pypdf.PdfWriter()
for pdf_path in pdf_files:
print(f"Adding: {pdf_path.name}")
reader = pypdf.PdfReader(str(pdf_path))
for page in reader.pages:
writer.add_page(page)
with open(output_file, "wb") as out:
writer.write(out)
total_pages = sum(len(pypdf.PdfReader(str(p)).pages) for p in pdf_files)
print(f"\nMerged {len(pdf_files)} PDFs ({total_pages} pages) → {output_file}")
if __name__ == "__main__":
merge_pdfs(
input_folder="./reports", # folder containing PDFs to merge
output_file="merged_report.pdf"
)
argparse so you can run python merge_pdfs.py --input ./reports --output combined.pdf from any terminal.
Script 5: Daily News Digest — Scrape Headlines and Email a Summary
This script scrapes the top headlines from Hacker News (a JSON API, no parsing needed) every morning and emails you a formatted digest. Uses requests for the API call and schedule to run at a set time each day.
import requests
import schedule
import time
import smtplib
from email.mime.text import MIMEText
from datetime import datetime
SENDER_EMAIL = "digest@gmail.com"
RECIPIENT_EMAIL = "you@gmail.com"
APP_PASSWORD = "your_app_password"
TOP_N = 10 # number of stories to include
def fetch_hn_top_stories(n: int) -> list:
# Hacker News has a public JSON API — no scraping required
top_ids = requests.get(
"https://hacker-news.firebaseio.com/v0/topstories.json"
).json()[:50]
stories = []
for story_id in top_ids[:50]:
story = requests.get(
f"https://hacker-news.firebaseio.com/v0/item/{story_id}.json"
).json()
if story and story.get("url"):
stories.append(story)
if len(stories) == n:
break
return stories
def build_digest(stories: list) -> str:
date_str = datetime.now().strftime("%A, %B %d %Y")
lines = [f"Your Hacker News Digest — {date_str}\n", "=" * 50]
for i, story in enumerate(stories, 1):
lines.append(f"\n{i}. {story['title']}")
lines.append(f" {story['url']}")
lines.append(f" {story.get('score', 0)} points | {story.get('descendants', 0)} comments")
return "\n".join(lines)
def send_digest() -> None:
print("Fetching top stories...")
stories = fetch_hn_top_stories(TOP_N)
body = build_digest(stories)
msg = MIMEText(body, "plain")
msg["Subject"] = f"Daily Tech Digest — {datetime.now().strftime('%b %d')}"
msg["From"] = SENDER_EMAIL
msg["To"] = RECIPIENT_EMAIL
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login(SENDER_EMAIL, APP_PASSWORD)
server.sendmail(SENDER_EMAIL, RECIPIENT_EMAIL, msg.as_string())
print("Digest sent!")
# Schedule to run at 07:30 every morning
schedule.every().day.at("07:30").do(send_digest)
send_digest() # Run once immediately
while True:
schedule.run_pending()
time.sleep(60)
Each of these five scripts teaches you something different: file I/O and path manipulation, SMTP and CSV processing, HTTP requests and scheduled tasks, PDF manipulation, and API consumption. Together, they cover most of the patterns you'll use in real Python automation work.
Go From Scripts to Production Python
Our Python course takes you from beginner to writing clean, tested, production-grade Python — including automation, APIs, data processing, and AI integration.
View the Python Course →