9 Python Scripts That Automate My Everyday Tasks
Practical Python automation scripts for file organization, image resizing, web scraping, PDF merging, and more. Complete code included for each script.

Most of us spend hours every week on tasks a computer should be doing: renaming files, resizing images for blog posts, copying data between CSVs and spreadsheets, and sending the same kind of email to the same people with only a few words changed. Each takes five or ten minutes. Across a week, that's hours.
Python scripts can take over the boring parts. Some save 30 seconds. Others save an hour every single week. Once they're written they just work: run them, grab your chai, come back to a finished task.
Here are 9 practical scripts, with full code you can copy, modify, and start running today. Each one is self-contained, so you can drop it into a .py file and go. Where one needs a third-party library, the pip install command is noted.
1. File organizer by extension
My Downloads folder used to look like a warzone. This script scans a folder and moves each file into a subfolder based on its extension, so the PDFs, the screenshots and the random .zip files from three months ago each end up in their own place instead of piled together.
The code
import os
import shutil
from pathlib import Path
def organize_folder(target_dir):
"""Organize files into subfolders based on their extension."""
target_path = Path(target_dir)
if not target_path.exists():
print(f"Directory {target_dir} does not exist.")
return
extension_map = {
'Images': ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg', '.webp'],
'Documents': ['.pdf', '.doc', '.docx', '.txt', '.xlsx', '.xls', '.pptx', '.csv'],
'Videos': ['.mp4', '.mkv', '.avi', '.mov', '.wmv', '.flv'],
'Audio': ['.mp3', '.wav', '.flac', '.aac', '.ogg'],
'Archives': ['.zip', '.rar', '.7z', '.tar', '.gz'],
'Code': ['.py', '.js', '.ts', '.html', '.css', '.java', '.cpp', '.c'],
'Installers': ['.exe', '.msi', '.dmg', '.deb', '.rpm'],
}
# Reverse map: extension -> folder name
ext_to_folder = {}
for folder, extensions in extension_map.items():
for ext in extensions:
ext_to_folder[ext] = folder
moved_count = 0
for item in target_path.iterdir():
if item.is_file():
ext = item.suffix.lower()
folder_name = ext_to_folder.get(ext, 'Others')
dest_folder = target_path / folder_name
dest_folder.mkdir(exist_ok=True)
dest_file = dest_folder / item.name
if dest_file.exists():
# Add a number suffix to avoid overwriting
base = item.stem
counter = 1
while dest_file.exists():
dest_file = dest_folder / f"{base}_{counter}{ext}"
counter += 1
shutil.move(str(item), str(dest_file))
moved_count += 1
print(f"Moved: {item.name} -> {folder_name}/")
print(f"\nDone! Moved {moved_count} files.")
if __name__ == "__main__":
folder = input("Enter the folder path to organize: ").strip()
organize_folder(folder)
How to use it
It runs as a scheduled task on my Windows machine every Sunday evening. By Monday the folder is pristine. cron on Linux or launchd on macOS works too. The extension map is easy to customize: add categories or move file types around to match how you think about your files.
2. Bulk image resizer
When I'm writing blog posts or preparing presentations, I constantly need to resize batches of images to a specific width. Doing it one at a time is painful. This script handles a whole folder in seconds.
The code
from PIL import Image
from pathlib import Path
def resize_images(input_dir, output_dir, max_width=800):
"""Resize all images in a folder to a maximum width, preserving aspect ratio."""
input_path = Path(input_dir)
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
supported = {'.jpg', '.jpeg', '.png', '.bmp', '.webp'}
for img_file in input_path.iterdir():
if img_file.suffix.lower() in supported:
with Image.open(img_file) as img:
if img.width > max_width:
ratio = max_width / img.width
new_height = int(img.height * ratio)
img_resized = img.resize((max_width, new_height), Image.LANCZOS)
else:
img_resized = img.copy()
output_file = output_path / img_file.name
img_resized.save(output_file, quality=85, optimize=True)
print(f"Resized: {img_file.name} ({img.width}x{img.height} -> {img_resized.width}x{img_resized.height})")
if __name__ == "__main__":
src = input("Source folder: ").strip()
dest = input("Output folder: ").strip()
width = int(input("Max width (default 800): ").strip() or "800")
resize_images(src, dest, width)
You'll need Pillow for this:
pip install Pillow
quality=85 with optimize=True is a good balance between file size and visual quality. Below 80 you start seeing compression artifacts, especially on photographs. Going lower isn't worth it.
3. Automated email sender
A weekly status email is a classic example: same recipients, same format, only the bullet points change. This script uses Python's built-in smtplib to send HTML emails through Gmail.
The code
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime
def send_email(sender, password, recipient, subject, body_html):
"""Send an HTML email via Gmail SMTP."""
msg = MIMEMultipart('alternative')
msg['From'] = sender
msg['To'] = recipient
msg['Subject'] = subject
html_part = MIMEText(body_html, 'html')
msg.attach(html_part)
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
server.login(sender, password)
server.send_message(msg)
print(f"Email sent to {recipient}")
if __name__ == "__main__":
SENDER = "your.email@gmail.com"
APP_PASSWORD = "your-16-char-app-password" # Use Gmail App Password, NOT your regular password
today = datetime.now().strftime("%B %d, %Y")
updates = [
"Completed the API refactoring for the payment module",
"Fixed 3 critical bugs reported by QA team",
"Started working on the dashboard redesign",
]
bullet_points = "".join(f"<li>{item}</li>" for item in updates)
html_body = f"""
<html>
<body style="font-family: Arial, sans-serif; line-height: 1.6;">
<h2>Weekly Status Update - {today}</h2>
<p>Hi Team,</p>
<p>Here is my update for this week:</p>
<ul>{bullet_points}</ul>
<p>Best regards,<br>Anurag</p>
</body>
</html>
"""
send_email(SENDER, APP_PASSWORD, "team@company.com", f"Weekly Update - {today}", html_body)
Never put your real Gmail password in the script. Go to Google Account settings, enable 2-Step Verification, and generate an App Password, then put that 16-character code into the script, ideally read from an environment variable rather than typed into the file, because hardcoding credentials is terrible practice even for personal scripts that you swear nobody else will ever see.
4. Web scraper for price tracking
I've tracked gadget prices on Amazon and Flipkart for months with this script. It fetches the product page, pulls out the current price, and logs it to a CSV. Then it alerts me when the price drops below my target.
The code
import requests
from bs4 import BeautifulSoup
import csv
from datetime import datetime
from pathlib import Path
def get_price(url, headers):
"""Scrape product price from a webpage."""
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.content, 'html.parser')
# Amazon India price selector
price_element = soup.select_one('span.a-price-whole')
if price_element:
price_text = price_element.get_text().replace(',', '').replace('.', '').strip()
return int(price_text)
# Flipkart price selector (fallback)
price_element = soup.select_one('div._30jeq3')
if price_element:
price_text = price_element.get_text().replace('₹', '').replace(',', '').strip()
return int(price_text)
return None
def track_price(url, product_name, target_price):
"""Track price and log to CSV."""
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept-Language': 'en-IN,en;q=0.9',
}
price = get_price(url, headers)
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
if price:
log_file = Path("price_log.csv")
file_exists = log_file.exists()
with open(log_file, 'a', newline='') as f:
writer = csv.writer(f)
if not file_exists:
writer.writerow(['Timestamp', 'Product', 'Price (INR)', 'Target', 'Below Target'])
writer.writerow([timestamp, product_name, price, target_price, price <= target_price])
print(f"[{timestamp}] {product_name}: ₹{price:,}")
if price <= target_price:
print(f" ALERT: Price is at or below your target of ₹{target_price:,}!")
return True
else:
print(f"Could not fetch price for {product_name}")
return False
if __name__ == "__main__":
products = [
{
'url': 'https://www.amazon.in/dp/PRODUCT_ID',
'name': 'Sony WH-1000XM5',
'target': 22000
},
]
for product in products:
track_price(product['url'], product['name'], product['target'])
You'll need:
pip install requests beautifulsoup4
Scrapers break whenever a site changes its HTML. I check this script every couple of weeks and update the CSS selectors if needed. Be respectful too, and don't hit the same page hundreds of times an hour. Once a day via cron has been fine.
5. PDF merger
This one combines multiple PDF files into one, which I need surprisingly often for invoices, scanned documents, and project reports. Order is up to you.
The code
from PyPDF2 import PdfMerger
from pathlib import Path
def merge_pdfs(pdf_list, output_filename="merged_output.pdf"):
"""Merge multiple PDF files into one."""
merger = PdfMerger()
for pdf_path in pdf_list:
path = Path(pdf_path)
if path.exists() and path.suffix.lower() == '.pdf':
merger.append(str(path))
print(f"Added: {path.name}")
else:
print(f"Skipped (not found or not PDF): {pdf_path}")
merger.write(output_filename)
merger.close()
print(f"\nMerged PDF saved as: {output_filename}")
def merge_folder(folder_path, output_filename="merged_output.pdf"):
"""Merge all PDFs in a folder, sorted by filename."""
folder = Path(folder_path)
pdf_files = sorted(folder.glob("*.pdf"))
if not pdf_files:
print("No PDF files found in the folder.")
return
print(f"Found {len(pdf_files)} PDF files:")
for f in pdf_files:
print(f" - {f.name}")
merge_pdfs([str(f) for f in pdf_files], output_filename)
if __name__ == "__main__":
choice = input("Merge (1) specific files or (2) entire folder? ").strip()
if choice == '1':
files = input("Enter PDF file paths (comma-separated): ").strip().split(',')
files = [f.strip() for f in files]
output = input("Output filename (default: merged_output.pdf): ").strip() or "merged_output.pdf"
merge_pdfs(files, output)
else:
folder = input("Enter folder path: ").strip()
output = input("Output filename (default: merged_output.pdf): ").strip() or "merged_output.pdf"
merge_folder(folder, output)
Install the dependency:
pip install PyPDF2
merge_folder is the one you'll use most. It picks up all PDFs in alphabetical order. Name files with numeric prefixes like 01_intro.pdf, 02_chapter1.pdf and the order takes care of itself.
6. CSV to Excel converter with formatting
Raw CSVs are ugly. When I share data with non-technical colleagues, I convert it to a formatted Excel file with styled headers, auto-adjusted column widths, and alternating row colours.
The code
import pandas as pd
from openpyxl import load_workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from pathlib import Path
def csv_to_excel(csv_path, output_path=None):
"""Convert CSV to a formatted Excel file."""
csv_file = Path(csv_path)
if output_path is None:
output_path = csv_file.with_suffix('.xlsx')
# Read CSV
df = pd.read_csv(csv_file)
# Write to Excel
df.to_excel(output_path, index=False, sheet_name='Data')
# Now apply formatting
wb = load_workbook(output_path)
ws = wb['Data']
# Header styling
header_font = Font(bold=True, color="FFFFFF", size=11)
header_fill = PatternFill(start_color="2563EB", end_color="2563EB", fill_type="solid")
header_alignment = Alignment(horizontal="center", vertical="center")
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
for cell in ws[1]:
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_alignment
cell.border = thin_border
# Auto-adjust column widths
for column in ws.columns:
max_length = 0
column_letter = column[0].column_letter
for cell in column:
if cell.value:
max_length = max(max_length, len(str(cell.value)))
cell.border = thin_border
adjusted_width = min(max_length + 4, 50)
ws.column_dimensions[column_letter].width = adjusted_width
# Alternate row coloring
light_fill = PatternFill(start_color="F0F7FF", end_color="F0F7FF", fill_type="solid")
for row_idx, row in enumerate(ws.iter_rows(min_row=2, max_row=ws.max_row), start=2):
if row_idx % 2 == 0:
for cell in row:
cell.fill = light_fill
wb.save(output_path)
print(f"Formatted Excel file saved: {output_path}")
if __name__ == "__main__":
csv_file = input("Enter CSV file path: ").strip()
csv_to_excel(csv_file)
Dependencies:
pip install pandas openpyxl
The output looks far more professional than it has any right to, and people will assume you're using an expensive reporting tool rather than a short Python script.
7. System backup script
This creates compressed backups of important folders and keeps only the last N, so it doesn't eat all your disk space. Everyone should run something like it.
The code
import shutil
from pathlib import Path
from datetime import datetime
def create_backup(source_dirs, backup_root, max_backups=5):
"""Create a timestamped compressed backup of specified directories."""
backup_root = Path(backup_root)
backup_root.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
for source in source_dirs:
source_path = Path(source)
if not source_path.exists():
print(f"Source not found, skipping: {source}")
continue
folder_name = source_path.name
archive_name = f"{folder_name}_{timestamp}"
archive_path = backup_root / archive_name
print(f"Backing up: {source_path} ...")
shutil.make_archive(
str(archive_path),
'zip',
root_dir=str(source_path.parent),
base_dir=source_path.name
)
archive_size = Path(f"{archive_path}.zip").stat().st_size / (1024 * 1024)
print(f" Created: {archive_name}.zip ({archive_size:.1f} MB)")
# Cleanup old backups
cleanup_old_backups(backup_root, max_backups)
def cleanup_old_backups(backup_root, max_backups):
"""Remove old backups, keeping only the most recent ones per source folder."""
all_zips = sorted(backup_root.glob("*.zip"), key=lambda f: f.stat().st_mtime, reverse=True)
# Group by source folder name (everything before the timestamp)
from collections import defaultdict
groups = defaultdict(list)
for zip_file in all_zips:
# Extract the folder name part (before _YYYYMMDD_HHMMSS)
parts = zip_file.stem.rsplit('_', 2)
if len(parts) >= 3:
group_name = parts[0]
else:
group_name = zip_file.stem
groups[group_name].append(zip_file)
for group_name, files in groups.items():
if len(files) > max_backups:
for old_file in files[max_backups:]:
old_file.unlink()
print(f" Removed old backup: {old_file.name}")
if __name__ == "__main__":
folders_to_backup = [
r"C:\Users\anurag\Documents\Projects",
r"C:\Users\anurag\Documents\Notes",
r"C:\Users\anurag\Desktop\WorkFiles",
]
backup_destination = r"D:\Backups"
create_backup(folders_to_backup, backup_destination, max_backups=5)
print("\nBackup complete!")
It uses only the standard library. Schedule it nightly, for example at 11 PM via Windows Task Scheduler or cron. max_backups=5 keeps the five most recent backups per folder and deletes older ones automatically, so you set it up once and forget about it.
8. WhatsApp message scheduler (concept)
Automating WhatsApp is a gray area. WhatsApp doesn't officially support bots for personal accounts, and unofficial APIs can get your number banned. This concept script using pywhatkit is still handy for one-off scheduled messages, as long as you use it responsibly.
The code
import pywhatkit as kit
from datetime import datetime, timedelta
def schedule_whatsapp_message(phone_number, message, send_time=None):
"""
Schedule a WhatsApp message using pywhatkit.
phone_number should include country code (e.g., +91XXXXXXXXXX)
send_time is a datetime object; if None, sends 2 minutes from now.
"""
if send_time is None:
send_time = datetime.now() + timedelta(minutes=2)
hour = send_time.hour
minute = send_time.minute
print(f"Scheduling message to {phone_number} at {hour:02d}:{minute:02d}")
print(f"Message: {message}")
kit.sendwhatmsg(
phone_no=phone_number,
message=message,
time_hour=hour,
time_min=minute,
wait_time=15,
tab_close=True
)
print("Message sent successfully!")
if __name__ == "__main__":
# Example: Send birthday wishes at midnight
schedule_whatsapp_message(
phone_number="+919876543210",
message="Happy Birthday! 🎂 Wishing you an amazing year ahead!",
send_time=datetime(2026, 2, 15, 0, 0) # Feb 15 at midnight
)
pip install pywhatkit
It works by opening WhatsApp Web in your browser and typing the message for you, so you need to be logged into WhatsApp Web with your computer running at the scheduled time. It isn't elegant. For birthday wishes, it works. Anything serious? Use the WhatsApp Business API, the official route.
9. Password generator
Yes, password managers can generate passwords. Sometimes, though, you need a quick batch of strong ones for test accounts, temporary access, or offline storage, and this script generates them with customizable rules.
The code
import secrets
import string
from typing import Optional
def generate_password(
length: int = 20,
uppercase: bool = True,
lowercase: bool = True,
digits: bool = True,
special: bool = True,
exclude_ambiguous: bool = True,
min_uppercase: int = 1,
min_digits: int = 1,
min_special: int = 1
) -> str:
"""Generate a cryptographically secure password."""
chars = ""
if lowercase:
chars += string.ascii_lowercase
if uppercase:
chars += string.ascii_uppercase
if digits:
chars += string.digits
if special:
chars += "!@#$%^&*()-_=+[]{}|;:,.<>?"
if exclude_ambiguous:
ambiguous = "0OoIl1"
chars = ''.join(c for c in chars if c not in ambiguous)
if not chars:
raise ValueError("At least one character type must be enabled")
# Keep generating until all minimums are met
while True:
password = ''.join(secrets.choice(chars) for _ in range(length))
has_upper = sum(1 for c in password if c in string.ascii_uppercase) >= min_uppercase
has_digit = sum(1 for c in password if c in string.digits) >= min_digits
has_special = sum(1 for c in password if c in "!@#$%^&*()-_=+[]{}|;:,.<>?") >= min_special
if has_upper and has_digit and has_special:
return password
def generate_batch(count: int = 10, **kwargs) -> list:
"""Generate multiple passwords."""
return [generate_password(**kwargs) for _ in range(count)]
if __name__ == "__main__":
print("=== Password Generator ===\n")
count = int(input("How many passwords? (default 5): ").strip() or "5")
length = int(input("Password length? (default 20): ").strip() or "20")
passwords = generate_batch(count=count, length=length)
print(f"\nGenerated {count} passwords ({length} chars each):\n")
for i, pwd in enumerate(passwords, 1):
print(f" {i}. {pwd}")
# Calculate entropy
import math
charset_size = 72 # approximate usable charset after ambiguous removal
entropy = length * math.log2(charset_size)
print(f"\n Estimated entropy: {entropy:.0f} bits")
print(f" (128+ bits is considered very strong)")
It uses Python's secrets module rather than random. That matters. random isn't cryptographically secure because it relies on a predictable pseudorandom number generator, while secrets pulls from your OS's cryptographic random source, so the passwords are truly unpredictable. The exclude_ambiguous flag removes characters like 0/O and 1/l/I that look alike in many fonts, which is a lifesaver when you have to read a password aloud or type it by hand.
How I keep them organized
All my automation scripts live in one folder, ~/scripts/, with a simple naming convention:
| Script | Filename | Schedule |
|---|---|---|
| File Organizer | organize_downloads.py | Weekly (Sunday 8 PM) |
| Image Resizer | resize_images.py | On demand |
| Email Sender | weekly_update.py | Weekly (Friday 5 PM) |
| Price Tracker | track_prices.py | Daily (9 AM) |
| PDF Merger | merge_pdfs.py | On demand |
| CSV to Excel | csv_to_xlsx.py | On demand |
| System Backup | backup.py | Daily (11 PM) |
| WhatsApp Scheduler | wa_schedule.py | On demand |
| Password Generator | gen_passwords.py | On demand |
Scheduled scripts run through Windows Task Scheduler on Windows or cron on Linux and macOS. A good editor helps. Our best VS Code extensions for 2026 list includes several that noticeably speed up Python development. Send all output to log files so you can check when something goes wrong:
# Example cron entry (Linux)
0 9 * * * /usr/bin/python3 /home/anurag/scripts/track_prices.py >> /home/anurag/scripts/logs/price_tracker.log 2>&1
Tips I've learned the hard way
Keep scripts simple. The best automation scripts are the ones you can read six months later and understand immediately, so resist the urge to over-engineer them.
Add error handling. A script that crashes silently is worse than no script, so at minimum wrap the main logic in a try-except and log the error somewhere, even if it is only a plain text file sitting in the same folder as the script itself.
Prefer pathlib over os.path. It's cleaner, and there's almost no reason to use os.path.join() anymore when Path(folder) / "subfolder" / "file.txt" exists.
Store secrets properly. Use environment variables or a .env file with python-dotenv for API keys, passwords, and other sensitive data, and never hardcode them, even for "personal use only." Habits matter more than intent.
Put your scripts under version control. I keep my scripts folder in a private Git repo. It's saved me more than once. If you're not comfortable with Git yet, our Git and GitHub mastery guide covers everything you'd need.
Start with whatever annoys you
Automation pays back fast. A script like the file organizer takes maybe 30 minutes to write and keeps saving time for years, and a price tracker catches deals you'd otherwise miss.
You don't need to be an advanced Python developer to write scripts like these. Most of them are plain loops, conditionals and file operations plus a few well-chosen libraries. Python has a library for almost anything you'd want to automate. That's a big reason it consistently tops the charts among the most popular programming languages. If you're running scripts on a Linux server, our Linux for developers guide covers the command-line basics that make managing cron jobs much easier.
Pick the task that annoys you most, the one where you think "ugh, I have to do this again?" That's your first automation candidate. Write it. Test it a few times, then schedule it or keep it within reach. Once you've had that specific satisfaction of a task finishing while you were making coffee, you'll start noticing automation opportunities everywhere. It's a little addictive.
Anurag Sinha
Founder & Editor
Founder and editor of Tech Tips India. Writes tech guides, buying guides and explainers for Indian readers, with rupee pricing and local availability. Product roundups are researched from manufacturer specs and published reviews, not lab testing.
Stay Ahead in Tech
Get the latest tech news, tutorials, and reviews delivered straight to your inbox every week.
No spam ever. Unsubscribe anytime.
Comments (0)
Leave a Comment
All comments are moderated before appearing. Please be respectful and follow our community guidelines.
Related Articles

API Design: REST and GraphQL Patterns That Scale
API design guide covering REST, GraphQL, tRPC, authentication, rate limiting, error handling, and testing with Node.js examples.

DSA Roadmap for Placements: What Matters in 2026
Practical DSA roadmap for campus placements in India: topic priorities, platform choices, company patterns, and time management.

Redis Caching: Speed Up Your App in Practice
Redis caching strategies, data structures, and cache invalidation with Node.js, Next.js, and Upstash integration guide.