Python Automation Scripts for Beginners

Photo: Daniil Komov / Pexels
If you can spend an hour writing a script that saves you ten minutes every day, you break even in less than two weeks and every run after that is pure time saved. Python is the default language for that trade because it ships with a huge standard library, reads almost like plain English, and has a third-party package for nearly every task a beginner runs into. This guide walks through the categories that matter most — files, scraping, email, APIs, and scheduling — with working code, real error handling, and the mistakes that trip up first scripts.
Why Python for Automation?
Python's popularity for automation is not an accident of marketing. It is the combination of a few practical properties that matter more to a beginner than raw execution speed.
- "Batteries included." The standard library covers files, networking, email, dates, CSV, JSON, and zip archives without any installs at all.
- A huge ecosystem. PyPI, the Python Package Index, hosts several hundred thousand third-party packages as of 2026, and daily-updated trackers put the figure well above 700,000 (PyPI). Whatever you need to automate almost certainly already has a library.
- Cross-platform. The same script runs on Windows, macOS, and Linux with only minor path adjustments.
- Readable syntax. Automation scripts are often written once and revisited rarely — sometimes a year later, by someone who is not you. Python's clarity keeps that revisit short instead of painful.
- No compilation step. Run a
.pyfile directly and see results immediately, which matters when you're iterating on a scraper or debugging a scheduled job.
Python's usage numbers back this up: the 2025 Stack Overflow Developer Survey found that 57.9% of professional developers used Python in the past year, a seven-percentage-point jump from 2024, and it was the single most-desired language to learn next, at 39.3% (Stack Overflow Developer Survey 2025). The Python Software Foundation's own 2024 developer survey, run jointly with JetBrains and covering more than 30,000 respondents across nearly 200 countries, found that 72% of Python users write it for work, not just as a hobby (Python Software Foundation). Automation and scripting is consistently one of the top reported use cases in that survey, alongside data analysis and web development.
Python vs No-Code Tools
Before writing a line of code, it's worth asking whether Python is even the right tool. Automation splits roughly into two camps: connecting existing apps together (Slack posts a message when a form is submitted) and processing raw data or files (parse a CSV, rename 400 photos, scrape a price list). No-code platforms are built for the first camp. Python is built for the second, and it also handles the first camp fine once you're comfortable with HTTP requests.
| Factor | Python | n8n / Zapier / Make |
|---|---|---|
| Setup time | Higher — install Python, write code | Lower — browser-based, drag-and-drop |
| Flexibility | Effectively unlimited | Limited to available nodes and integrations |
| Cost | Free (open-source runtime and most libraries) | Often pay-per-task or per-workflow above a free tier |
| Data volume | Handles millions of rows without issue | Can slow down or hit limits at scale |
| Debugging | Full stack traces, breakpoints, logging | Limited to the platform's execution log |
| Best for | Custom logic, large data, files, scraping | App-to-app integrations, non-coders |
Table: How Python compares to no-code automation platforms across the factors that matter for a beginner's first project.
If your automation is mostly gluing existing apps together — Slack, Gmail, Notion, a CRM — a visual tool like n8n will likely get you to a working workflow faster and without maintaining code. If you need custom logic, file processing, or scraping data that no integration covers, Python is the better investment, and the time you spend learning it compounds: the same skills apply to data science work in Python or R and, eventually, to machine learning projects built on the same standard library habits covered in the complete machine learning guide.
1. File and Folder Automation
The pathlib and shutil modules, both part of the standard library, handle almost everything file-related without a single pip install. pathlib represents file paths as objects instead of raw strings, which avoids a whole category of bugs around forward slashes versus backslashes on Windows versus macOS/Linux.
Organize Downloads by Extension
from pathlib import Path
import shutil
DOWNLOADS = Path.home() / "Downloads"
DESTINATIONS = {
".pdf": DOWNLOADS / "PDFs",
".jpg": DOWNLOADS / "Images",
".png": DOWNLOADS / "Images",
".zip": DOWNLOADS / "Archives",
".xlsx": DOWNLOADS / "Spreadsheets",
".csv": DOWNLOADS / "Spreadsheets",
}
for file in DOWNLOADS.iterdir():
if file.is_file() and file.suffix.lower() in DESTINATIONS:
dest = DESTINATIONS[file.suffix.lower()]
dest.mkdir(exist_ok=True)
shutil.move(str(file), dest / file.name)
print(f"Moved {file.name} -> {dest.name}/")
Run this once a week via cron or Task Scheduler and your Downloads folder stays organized without you thinking about it. Note the .lower() call — file extensions arrive inconsistently capitalized (.JPG versus .jpg), and a beginner script that skips this normalization will silently miss half the files it's supposed to catch.
Batch Rename Files
from pathlib import Path
folder = Path("/path/to/photos")
for i, file in enumerate(sorted(folder.glob("*.jpg")), start=1):
new_name = f"vacation_2026_{i:03d}.jpg"
file.rename(folder / new_name)
print(f"{file.name} -> {new_name}")
glob("*.jpg") only matches lowercase .jpg files — if your camera or phone saves files as .JPG, add a second loop or use glob("*.[Jj][Pp][Gg]"). This is the kind of small edge case that separates a script that works on your test folder from one that works on the messy, real folder six months from now.
Find and Delete Duplicate Files
A slightly more advanced but genuinely useful pattern: hash every file's contents and flag exact duplicates before deleting anything.
import hashlib
from pathlib import Path
from collections import defaultdict
def file_hash(path, chunk_size=8192):
hasher = hashlib.sha256()
with open(path, "rb") as f:
while chunk := f.read(chunk_size):
hasher.update(chunk)
return hasher.hexdigest()
folder = Path("/path/to/photos")
seen = defaultdict(list)
for file in folder.rglob("*"):
if file.is_file():
seen[file_hash(file)].append(file)
for digest, paths in seen.items():
if len(paths) > 1:
print(f"Duplicate set ({len(paths)} files):")
for p in paths:
print(f" {p}")
This script only reports duplicates — it deliberately does not delete anything. Add deletion logic yourself once you've confirmed the report is correct on a folder you can afford to lose. Automation scripts that delete files should always default to "dry run" mode first.
2. Web Scraping Basics
Install the two most common libraries first:
pip install requests beautifulsoup4
requests handles the HTTP layer — fetching a page's raw HTML. beautifulsoup4 (imported as bs4) parses that HTML into a navigable tree so you can pull out specific elements by tag, class, or CSS selector.
Scrape a Webpage
import requests
from bs4 import BeautifulSoup
url = "https://news.ycombinator.com"
response = requests.get(url, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
titles = soup.select(".titleline > a")
for i, title in enumerate(titles[:10], 1):
print(f"{i}. {title.text}")
print(f" {title['href']}\n")
Two details matter here that beginner tutorials often skip. First, timeout=10 is not optional in practice — the requests library does not set a default timeout, so without one, a request to a slow or unresponsive server can hang your script indefinitely (Requests documentation). Second, raise_for_status() turns a silent HTTP error (like a 404 or 500) into a Python exception you can actually catch, instead of letting your script continue processing an error page as if it were real content.
Export to CSV
import csv
with open("hn_stories.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["rank", "title", "url"])
for i, title in enumerate(titles[:10], 1):
writer.writerow([i, title.text, title.get("href", "")])
A Note on What Can Go Wrong
Scraping scripts break more often than any other category in this guide, because they depend on a website's HTML structure staying stable — and sites redesign without warning. A robust scraper wraps the parsing step in error handling and logs what it couldn't find instead of crashing silently:
import requests
from bs4 import BeautifulSoup
import logging
logging.basicConfig(filename="scraper.log", level=logging.INFO)
try:
response = requests.get("https://example.com", timeout=10)
response.raise_for_status()
except requests.exceptions.Timeout:
logging.error("Request timed out")
except requests.exceptions.RequestException as e:
logging.error(f"Request failed: {e}")
else:
soup = BeautifulSoup(response.text, "html.parser")
items = soup.select(".target-class")
if not items:
logging.warning("Selector matched zero elements — site structure may have changed")
Important notes on scraping:
- Always check a site's
robots.txtbefore scraping, and read its terms of service. Some sites explicitly prohibit automated collection. - Add
time.sleep(1)or longer between requests to avoid hammering a server — a script that fires 50 requests per second can look like a denial-of-service attack from the receiving end. - Set a
User-Agentheader that honestly identifies your script, rather than pretending to be a browser. - For JavaScript-heavy sites where content loads after the initial page fetch,
requestsandBeautifulSoupwill not see the rendered content. Use a browser-automation library like Playwright instead, which actually executes the page's JavaScript.
3. Automating Emails with smtplib
Python's built-in smtplib module sends email over SMTP without any third-party install. For Gmail specifically, Google requires an App Password rather than your normal account password for any script-based login — a 16-character, single-purpose credential generated from Google Account → Security → 2-Step Verification → App passwords. App Passwords are only available once 2-Step Verification is turned on, and Google has been phasing out plain-password SMTP access entirely in favor of this model (Google Account Help).
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 587
SENDER = "you@gmail.com"
APP_PASSWORD = "your-16-char-app-password"
def send_email(to: str, subject: str, body: str):
msg = MIMEMultipart()
msg["From"] = SENDER
msg["To"] = to
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
server.starttls()
server.login(SENDER, APP_PASSWORD)
server.send_message(msg)
print(f"Email sent to {to}")
send_email(
to="colleague@example.com",
subject="Weekly report",
body="Hi, here is the weekly summary...",
)
Never hardcode APP_PASSWORD directly in a script you might commit to version control. Store it as an environment variable instead and read it with os.environ["GMAIL_APP_PASSWORD"], or use a .env file excluded from git via .gitignore. This is the single most common security mistake in beginner automation scripts — a credential accidentally pushed to a public GitHub repository.
Practical Use: Weekly CSV Report Emailer
Combine the CSV scraper from section 2 with send_email() to automatically deliver a Hacker News digest every Monday morning. The pattern generalizes: scrape or generate a report, attach it or paste it into the body, send on a schedule. The same structure works for a competitor price-tracking digest, a weekly stock summary, or a personal reading-list roundup.
Attaching Files
Sending the CSV as an actual attachment, rather than pasting its contents into the email body, uses MIMEApplication:
from email.mime.application import MIMEApplication
def send_email_with_attachment(to, subject, body, filepath):
msg = MIMEMultipart()
msg["From"] = SENDER
msg["To"] = to
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))
with open(filepath, "rb") as f:
attachment = MIMEApplication(f.read(), Name=filepath.split("/")[-1])
attachment["Content-Disposition"] = f'attachment; filename="{filepath.split("/")[-1]}"'
msg.attach(attachment)
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
server.starttls()
server.login(SENDER, APP_PASSWORD)
server.send_message(msg)
4. Working with APIs
Most modern services expose a REST API that returns structured JSON instead of HTML you have to scrape. Where an API exists, prefer it over scraping — it's faster, more stable across redesigns, and usually explicitly sanctioned by the provider.
Call a JSON API
import requests
API_KEY = "your_key"
response = requests.get(
"https://api.openweathermap.org/data/2.5/weather",
params={"q": "Istanbul", "appid": API_KEY, "units": "metric"},
timeout=10,
)
response.raise_for_status()
data = response.json()
print(f"Temperature: {data['main']['temp']}°C")
print(f"Conditions: {data['weather'][0]['description']}")
POST to an API (e.g., Send a Slack Message)
import requests
WEBHOOK_URL = "https://hooks.slack.com/services/xxx/yyy/zzz"
payload = {"text": "Automated report ready"}
response = requests.post(WEBHOOK_URL, json=payload, timeout=10)
response.raise_for_status()
print("Slack message sent")
Handling Rate Limits
APIs almost always cap how many requests you can make in a given window, and exceeding it returns an HTTP 429 status code. A production-grade script checks for that response and backs off instead of hammering the endpoint repeatedly:
import requests
import time
def call_with_backoff(url, params=None, max_retries=5):
for attempt in range(max_retries):
response = requests.get(url, params=params, timeout=10)
if response.status_code == 429:
wait = int(response.headers.get("Retry-After", 2 ** attempt))
print(f"Rate limited. Waiting {wait}s...")
time.sleep(wait)
continue
response.raise_for_status()
return response.json()
raise RuntimeError("Max retries exceeded")
This exponential-backoff pattern — waiting longer after each failed attempt — is standard practice across API clients and is explicitly recommended by the requests library's own maintainers for handling transient failures like timeouts and rate limits (Requests exceptions documentation).
Reading API Documentation Efficiently
A beginner's biggest time sink with a new API is not the code — it's figuring out authentication. Almost every API falls into one of three patterns: an API key passed as a query parameter or header, a bearer token passed in an Authorization: Bearer <token> header, or full OAuth2 for services acting on a user's behalf (like posting to their social account). Check the docs for which of the three before writing any code; guessing wastes far more time than reading the first page of the reference.
5. Scheduling Scripts
A script that has to be run manually every day is only half-automated. Scheduling closes the loop.
Using the schedule Library
pip install schedule
import schedule
import time
def job():
print("Running daily task...")
# your automation code here
schedule.every().day.at("08:00").do(job)
schedule.every().monday.at("09:00").do(job)
schedule.every(30).minutes.do(job)
while True:
schedule.run_pending()
time.sleep(60)
Keep this running in the background with screen or tmux on a server, or as a systemd service on Linux. Its main weakness is fragility: if the process crashes or the machine reboots, the schedule stops silently until someone notices and restarts it.
Using cron (Linux/macOS)
For anything that matters — a report someone actually reads, a backup someone actually needs — cron is more reliable than an always-running Python loop, because the operating system itself manages the timing rather than a single long-lived process that can crash.
Open crontab:
crontab -e
Add a job that runs your script every day at 8 AM and logs the output:
0 8 * * * /usr/bin/python3 /home/user/scripts/daily_report.py >> /home/user/logs/report.log 2>&1
The >> ... 2>&1 at the end redirects both standard output and error output to a log file — without it, cron jobs fail silently and you only find out weeks later when someone asks why the report stopped arriving.
Using Windows Task Scheduler
On Windows, the equivalent is Task Scheduler: search "Task Scheduler" in the Start menu, choose Create Basic Task, set a trigger (daily, at logon, and so on), and set the action to "Start a Program" pointing at python.exe with your script's path passed as the argument.
Cloud Scheduling for Scripts That Must Never Miss a Run
Running a script on a laptop that gets closed at night means every scheduled job silently fails to run. For anything that has to happen reliably — daily backups, monitoring checks — move the script to a small always-on server or a serverless scheduled function (AWS Lambda with EventBridge, Google Cloud Functions with Cloud Scheduler, or a cheap always-on VPS running cron). This is the point at which a personal automation script starts to resemble small-scale production infrastructure, and the same discipline that applies to those systems — logging, error alerts, retries — starts to pay off.
6. Real Automation Script Examples
Example A: Monitor a Website for Changes
import hashlib, requests, time
def get_hash(url):
r = requests.get(url, timeout=10)
return hashlib.md5(r.text.encode()).hexdigest()
url = "https://example.com/pricing"
last_hash = get_hash(url)
while True:
time.sleep(3600) # check every hour
current_hash = get_hash(url)
if current_hash != last_hash:
print("Page changed!")
# send_email(...) — reuse the function from section 3
last_hash = current_hash
A caveat worth flagging: hashing the entire page body means the script also triggers on irrelevant changes, like a rotating ad banner or a timestamp printed somewhere on the page. For a real price tracker, narrow the hash to just the specific HTML element containing the price, using the BeautifulSoup selector approach from section 2.
Example B: Bulk Rename and Compress Old Log Files
from pathlib import Path
import gzip, shutil
log_dir = Path("/var/log/myapp")
for log in log_dir.glob("*.log"):
if log.stat().st_size > 10 * 1024 * 1024: # > 10 MB
compressed = log.with_suffix(".log.gz")
with open(log, "rb") as f_in, gzip.open(compressed, "wb") as f_out:
shutil.copyfileobj(f_in, f_out)
log.unlink()
print(f"Compressed {log.name}")
Example C: Backup a Folder to a Timestamped Archive
A common request for anyone running a small server or a personal project: back up a working directory before making risky changes, keeping the last several versions.
from pathlib import Path
from datetime import datetime
import shutil
SOURCE = Path("/home/user/project")
BACKUP_DIR = Path("/home/user/backups")
BACKUP_DIR.mkdir(exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
archive_name = BACKUP_DIR / f"project_backup_{timestamp}"
shutil.make_archive(str(archive_name), "zip", SOURCE)
print(f"Backed up to {archive_name}.zip")
# Keep only the 5 most recent backups
backups = sorted(BACKUP_DIR.glob("project_backup_*.zip"))
for old_backup in backups[:-5]:
old_backup.unlink()
print(f"Removed old backup: {old_backup.name}")
Example D: Deduplicate and Merge Multiple CSV Reports
Useful when several scraping or export scripts each produce a CSV and you need one clean, deduplicated master file — a common step before feeding data into a spreadsheet or a downstream data-science pipeline like the ones compared in Python vs R for data science.
import csv
from pathlib import Path
seen_rows = set()
merged = []
for csv_file in Path("reports").glob("*.csv"):
with open(csv_file, newline="", encoding="utf-8") as f:
reader = csv.reader(f)
header = next(reader)
for row in reader:
row_key = tuple(row)
if row_key not in seen_rows:
seen_rows.add(row_key)
merged.append(row)
with open("merged_report.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(header)
writer.writerows(merged)
print(f"Merged {len(merged)} unique rows from {len(list(Path('reports').glob('*.csv')))} files")
Common Mistakes Beginners Make
A handful of mistakes account for most of the "my automation script broke and I don't know why" support questions online.
- No timeout on network calls. Every
requests.get()orrequests.post()should carry atimeout=argument. Without one, a single unresponsive server hangs the entire script forever. - Hardcoded absolute paths. A script that only works on your laptop because it references
/Users/yourname/Desktop/file.csvbreaks the moment it moves to a server or a colleague's machine. UsePath.home()and relative paths instead. - Credentials committed to version control. API keys, app passwords, and webhook URLs belong in environment variables or a
.envfile — never typed directly into a script that might end up on GitHub. - No logging. A script that only uses
print()produces output nobody sees once it's running unattended on a server. Python's built-inloggingmodule writes to a file you can check later, with severity levels (INFO,WARNING,ERROR) that let you filter noise from real problems. - Assuming the network never fails. Wi-Fi drops, DNS hiccups, and servers restart. Wrap network calls in
try/exceptblocks that catchrequests.exceptions.RequestExceptionand retry or log the failure instead of crashing the whole script. - Deleting before verifying. Any script that deletes or overwrites files should have a "dry run" mode — print what it would do first, run it, check the output, and only then remove the safety check.
Frequently Asked Questions
Q: Do I need to know Python well to start automating? A: No. The scripts in this guide require only basic Python knowledge — variables, loops, functions, and importing libraries. Half of the developers in the Python Software Foundation's 2024 survey had fewer than two years of professional coding experience, so a beginner writing their first automation script is closer to the norm than the exception (Python Software Foundation).
Q: How is Python automation different from using a tool like Zapier? A: Python gives you complete control and is free to run, but it requires writing and maintaining code yourself. Zapier and similar tools like n8n are faster to set up for connecting existing apps but typically cost money at scale and offer limited flexibility for custom logic or large data processing.
Q: Is web scraping legal?
A: It depends on the site's terms of service and the jurisdiction you're in. Scraping publicly available data for personal, non-commercial use is generally treated as low-risk, but ignoring a site's robots.txt, bypassing access controls, or reselling scraped data can create real legal exposure. Always review the target site's terms of service before building a scraper you plan to run regularly.
Q: How do I run a Python script automatically on Windows?
A: Use Windows Task Scheduler. Search for "Task Scheduler," choose Create Basic Task, set the trigger (daily, on login, and so on), then set the action to "Start a Program," pointing it at python.exe with your script's file path as the argument.
Q: What libraries should I install to get started?
A: requests for HTTP calls and APIs, beautifulsoup4 for HTML parsing, schedule for simple job scheduling, pandas for tabular data manipulation, and openpyxl for reading and writing Excel files. Together these five cover the large majority of common beginner automation tasks.
Q: Why does my script hang forever instead of failing with an error?
A: This is almost always a missing timeout argument on a network request. The requests library does not set a default timeout, so a request to an unresponsive server will wait indefinitely unless you specify one explicitly, such as requests.get(url, timeout=10) (Requests documentation).
Q: How do I handle errors so my script doesn't silently fail?
A: Wrap risky code — anything touching the network, the filesystem, or an external API — in try/except blocks, log errors to a file with Python's built-in logging module, and consider sending yourself an email or Slack message when an exception occurs. Never let an unattended automation script fail silently, since a silent failure is functionally identical to no automation at all.
Q: Should I use smtplib or a service like SendGrid for automated emails?
A: smtplib with a Gmail App Password is fine for personal projects and low volume — a handful of emails a day. For anything sent to customers, at higher volume, or where deliverability and spam-filter avoidance matter, a dedicated transactional email service handles authentication, reputation, and bounce management far better than a raw SMTP connection.
Q: What's the difference between the schedule library and cron?
A: schedule runs inside a single, always-running Python process, which is easy to set up but stops entirely if that process crashes or the machine restarts. Cron is managed by the operating system itself, so a scheduled job runs independently of any long-lived process — it is generally the more reliable choice for anything that has to run unattended for weeks or months at a time.
Q: Can Python automation scripts run in the cloud instead of on my own computer? A: Yes. Serverless platforms like AWS Lambda paired with EventBridge, or Google Cloud Functions with Cloud Scheduler, run a Python script on a schedule without you maintaining a server at all. This is the standard next step once a script needs to run reliably even when your own computer is off.
The Takeaway
The five categories in this guide — files, scraping, email, APIs, and scheduling — cover the overwhelming majority of what a beginner needs to turn a repetitive task into a script that runs without them. The code itself is rarely the hard part; reading a tutorial and copying a requests.get() call takes minutes. What separates a script that quietly works for a year from one that breaks the first time reality deviates from the happy path is the boring stuff: timeouts on every network call, logging instead of print(), credentials kept out of version control, and a dry-run habit before anything gets deleted. Master those five habits alongside the five categories, and the "automate your job" script stops being a toy project and starts being infrastructure you can actually depend on.
Read next:
Follow new articles
Add SciHub101 to your feed reader to see new science and technology stories.
About the Author
SciHub101 is an independent science and technology publication founded and edited by Buğra Sözeri, a software engineer and app developer based in Türkiye. AI tools assist with drafting and research. Source documentation in the existing archive is uneven, and articles are being reviewed and corrected. The Editorial Team byline identifies editorial responsibility; it does not certify specialist or independent human review. Where we compare products, anything built by our own team is labelled as such — see the editorial guidelines and About page for how corrections and disclosures work.
Related Articles

Form Automation 2026 — Typeform vs Tally vs Formspree
Form automation in 2026: Typeform, Tally, Fillout, Formspree, and Jotform compared on pricing, logic, and webhooks, plus how forms trigger reliable workflows.

n8n Workflow Automation — Beginner's Guide
Learn n8n from scratch: nodes, triggers, integrations, and how it compares to Zapier and Make for self-hosted workflow automation.

What Is No-Code Automation? A Beginner's Guide
No-code automation explained: how triggers, actions, and filters work, what Zapier, Make, n8n, and IFTTT cost, and which tasks to automate first.

How to Learn Python: A Complete Guide for Beginners in 2026
Python is a sensible first language: readable syntax, a huge library ecosystem, and use in web, data, and automation work. This guide covers setup, a phased learning path, free courses, debugging, learning with AI assistants, and a project ladder.

How to Build Your First ML Model — Step-by-Step Tutorial
Build your first ML model in Python with scikit-learn: load California housing data, split it, train, score with RMSE and R², then improve it honestly.

Webhook Design Patterns 2026 — Reliability and Security
Webhook design patterns for reliable, secure integrations: signature verification, idempotency, retries, queues, and dead-letter handling. Updated for 2026.