Build a Phishing Website Detector
Train a Random Forest model to classify phishing vs legitimate websites.
Introduction
30 Second Summary
Every day, millions of people click on links that look like their bank, their email provider, or their favorite shopping site. Behind those links are phishing websites designed to steal passwords and personal data.
In this project, you will build a machine learning model that detects phishing websites based on their URL structure and security features. You'll train a classifier on real cybersecurity data and discover which website characteristics are the biggest red flags.
What You'll Build
A Python-based ML pipeline that classifies websites as legitimate or phishing using 30 security-relevant features from a real UCI dataset.
By the end of this project, you'll have:
- A trained Random Forest classifier that detects phishing websites with high accuracy.
- A feature importance chart showing which URL characteristics are most predictive of phishing attempts.
- A confusion matrix visualization that breaks down your model's correct and incorrect predictions.
- Secret Mission: Build a prediction function that takes new website features and returns a phishing risk score with a confidence level.
Do I need machine learning experience?
Nope. This project assumes basic Python knowledge (variables, loops, and functions). You'll learn every ML concept as you go.
Before We Start
Before you can train a machine learning model to detect phishing websites, you need the right tools installed on your machine. Python is the language behind almost every ML project, and a few key libraries will handle everything from data loading to model training.
In this step, you will install Python, set up an isolated project environment, and launch Jupyter Notebook. By the end, you will have a blank notebook open and ready for your first line of ML code.
In this step, get ready to:
- Install Python 3.13 and verify it works.
- Create a project folder and virtual environment.
- Install ML packages and launch Jupyter Notebook.
Install Python and verify
This project requires Python 3.13 or higher. The ML libraries you will install later need at least Python 3.11, and 3.13 is the latest stable release with broad compatibility.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
- Check your Python version by running this command:
macOS
python3 --version
Windows
python --version
✔️ I see version 3.13 or higher
Python is ready to go. Skip ahead to the next substep.
ⓧ I see an older version
Your system has an older Python that will not work with the ML libraries in this project. You need to install Python 3.13 alongside it.
Why not just use the version I have?
The scikit-learn and pandas versions this project uses require Python 3.11 or higher. If your system Python is older (macOS often ships with 3.9), you will hit confusing installation errors later.
- Download the Python 3.13 installer from python.org/downloads/windows (Windows) or python.org/downloads/macos (macOS).
- Run the installer. On Windows, check Add Python to PATH before clicking Install Now.
- Close and reopen your terminal after installation.
- Run the version check command again to confirm you see 3.13.
On macOS, if python3 --version still shows the old version after installing, try python3.13 --version to confirm the new install is present. Use python3.13 in place of python3 for the rest of this project.
ⓧ Command not found
Python is not installed yet. Follow these steps to install it.
- Download the Python 3.13 installer from python.org/downloads/windows (Windows) or python.org/downloads/macos (macOS).
- Run the installer. On Windows, check Add Python to PATH before clicking Install Now.
- Close and reopen your terminal after installation.
- Run the version check command again to confirm you see 3.13.
Still not seeing 3.13?
On Windows, make sure you checked Add Python to PATH during installation. If you missed it, re-run the installer, select Modify, and enable the PATH option.
On macOS, the system Python may shadow the new install. Try running python3.13 --version directly.
Help me troubleshoot my Python installation
Create a project folder and virtual environment
A virtual environment keeps your project's packages separate from the rest of your system. This prevents version conflicts between different Python projects.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir phishing-detector
cd phishing-detector
- Create a virtual environment by running this command:
macOS
python3 -m venv phishing-env
Windows
python -m venv phishing-env
- Activate your virtual environment by running this command:
macOS
source phishing-env/bin/activate
Windows
phishing-env\Scripts\activate
How do I know it worked?
When the virtual environment is active, you will see (phishing-env) at the beginning of your terminal prompt. This confirms all packages you install from now on stay inside this project.
- Confirm the folder and environment exist by running:
ls
You should see phishing-env listed in the output.
Activation command not working?
On Windows, if you see a permissions error, try running your terminal as Administrator. On macOS, make sure you typed source before the path.
Help me activate my Python virtual environment
Install packages and launch Jupyter
You need five packages for this project: scikit-learn for the ML model, pandas for data handling, matplotlib for visualizations, jupyter for the notebook interface, and ucimlrepo to download the phishing dataset.
- Install all project dependencies by running this command:
pip install scikit-learn pandas matplotlib jupyter ucimlrepo
What does this command do?
pip is Python's package manager. It downloads and installs each library into your phishing-env virtual environment so they stay isolated from the rest of your system.
This installation may take a minute or two. You will see progress bars for each package.
✔️ I see Successfully installed...
All packages are installed. You are ready to launch Jupyter.
ⓧ I see an error
- Make sure you see (phishing-env) in your terminal prompt. If you do not, re-run the activation command from the previous substep.
- If you see a permissions error, confirm you are inside the virtual environment and not installing globally.
- Try running the install command again. Network interruptions can cause partial failures.
Help me fix my pip install error
- Launch Jupyter Notebook by running this command:
jupyter notebook
Your default browser will open with the Jupyter file browser. If it does not open automatically, look for a URL in your terminal output that starts with http://localhost:8888 and paste it into your browser.
- In the Jupyter file browser, click New in the top right corner.
- Select Python 3 from the dropdown.
- Click the title at the top of the notebook (it will say Untitled).
- Rename it to phishing_detector and click Rename.
You should now see a blank notebook called phishing_detector with an empty code cell ready for input.
Why Jupyter Notebook?
Jupyter lets you run code one cell at a time and see results immediately. For a machine learning project, this means you can load data, train a model, and view charts without re-running the entire script each time.
Browser did not open?
Check your terminal for a line containing http://localhost:8888. Copy the full URL (including the token) and paste it into your browser.
Help me launch Jupyter Notebook
Your environment is ready. Next up, you will load a real cybersecurity dataset and explore what phishing websites look like in data.
Load and Explore the Phishing Dataset
Your Python environment is ready and your notebook is open. Now it's time to get your hands on real cybersecurity data.
In this step, you'll download a dataset of over 11,000 websites (both legitimate and phishing) from the UCI Machine Learning Repository. You'll explore how the data is structured and visualize how many websites in the dataset are malicious versus safe.
In this step, get ready to:
- Fetch the Phishing Websites dataset from UCI.
- Explore the data shape, features, and target distribution.
- Visualize the class balance with a bar chart.
Fetch the dataset from UCI
Before you can train a model, you need data to learn from. The UCI Machine Learning Repository hosts a curated Phishing Websites dataset containing 11,055 websites described by 30 security-related features.
- Import all the libraries you'll need for this project by adding this code to the first cell of your phishing_detector.ipynb notebook:
# Cell 1: Imports
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (
classification_report,
accuracy_score,
confusion_matrix,
ConfusionMatrixDisplay,
)
from ucimlrepo import fetch_ucirepo
What do these imports do?
- pandas gives you DataFrames for organizing and inspecting tabular data.
- matplotlib handles all the charts and visualizations.
- scikit-learn (sklearn) provides the Random Forest classifier, data splitting, and evaluation metrics.
- ucimlrepo connects directly to the UCI Machine Learning Repository to download datasets.
- Run the cell by pressing Shift+Enter.
- You should see no error output. The cursor moves to a new empty cell below.
Seeing a ModuleNotFoundError?
This means one of the packages isn't installed in your virtual environment. Make sure your phishing-env virtual environment is active (you should see (phishing-env) in your terminal prompt). Then re-run the install command:
pip install scikit-learn pandas matplotlib jupyter ucimlrepo
Restart the Jupyter kernel after reinstalling by clicking Kernel in the menu bar, then Restart.
help me fix a ModuleNotFoundError in my Jupyter Notebook
- In the new cell below, load the Phishing Websites dataset from UCI by adding this code:
# Cell 2: Load the dataset
dataset = fetch_ucirepo(id=327)
X = dataset.data.features
y = dataset.data.targets
print(f"Dataset shape: {X.shape}")
print(f"Features: {list(X.columns)}")
print(f"\nFirst 5 rows:")
print(X.head())
What does this code do?
fetch_ucirepo(id=327) downloads the Phishing Websites dataset directly from the UCI repository. Dataset ID 327 is a collection of 11,055 websites classified as phishing or legitimate.
X holds the features (the 30 characteristics of each website). y holds the target labels (whether each website is phishing or legitimate).
- Run the cell by pressing Shift+Enter.
- You should see output showing Dataset shape: (11055, 30), a list of 30 feature names, and a table of the first 5 rows.
Seeing a ConnectionError or timeout?
The dataset downloads from the internet, so you need an active connection. If you're behind a corporate firewall or VPN, try disconnecting and running the cell again.
If the download hangs, restart the kernel and try again. The dataset is small (under 1 MB) so it should download in a few seconds.
help me troubleshoot a dataset download error in Jupyter
Explore the data structure
The dataset's features are all integer-encoded with values of -1, 0, or 1. Each value represents a different state of a URL characteristic. For example, having_ip_address is -1 if the URL uses a raw IP address (a phishing signal) and 1 if it uses a normal domain.
- In a new cell, explore the dataset size and target distribution by adding this code:
# Cell 3: Explore the data
print(f"\nDataset info:")
print(f"Total samples: {len(X)}")
print(f"Number of features: {X.shape[1]}")
print(f"\nTarget distribution:")
print(y.value_counts())
What does this code do?
This prints a summary of the dataset dimensions and shows how many websites fall into each category. The target column Result uses -1 for phishing websites and 1 for legitimate websites.
Checking the target distribution tells you whether the dataset is balanced. A heavily imbalanced dataset (e.g. 95% legitimate, 5% phishing) would need special handling. This dataset is roughly balanced, which makes training straightforward.
- Run the cell by pressing Shift+Enter.
- You should see the total sample count (11055), the number of features (30), and a value count showing how many websites are labeled 1 (legitimate) versus -1 (phishing).
Seeing a NameError for X or y?
This means Cell 2 hasn't run successfully yet. Jupyter cells must be executed in order. Go back to Cell 2, click on it, and press Shift+Enter to run it first. Then run Cell 3 again.
help me fix a NameError in my Jupyter Notebook cells
Visualize class distribution
Numbers are useful, but a chart makes class balance obvious at a glance. You'll create a bar chart that shows the count of legitimate versus phishing websites side by side.
- In a new cell, create a bar chart of the class distribution by adding this code:
# Cell 4: Visualize class distribution
target_counts = y.value_counts()
plt.figure(figsize=(6, 4))
plt.bar(
["Legitimate (1)", "Phishing (-1)"],
[target_counts.get(1, 0), target_counts.get(-1, 0)],
color=["green", "red"],
)
plt.title("Dataset Class Distribution")
plt.ylabel("Number of Websites")
plt.tight_layout()
plt.show()
What does this code do?
value_counts() counts how many websites have each target label. The chart plots those counts as two bars: green for legitimate, red for phishing.
plt.tight_layout() prevents labels from being cut off, and plt.show() renders the chart inline in your notebook.
- Run the cell by pressing Shift+Enter.
- You should see a bar chart with a green bar for legitimate websites and a red bar for phishing websites. Both bars should be roughly similar in height, confirming the dataset is balanced.
Chart not appearing?
If you see a text reference like <Figure size 600x400> instead of a chart, make sure you included plt.show() at the end of the cell. In some Jupyter configurations, charts only render when explicitly shown.
help me display a matplotlib chart in my Jupyter Notebook
✔️ Awesome, I've got everything!
Great. Make sure you've saved your notebook by pressing Ctrl+S (Windows) or Cmd+S (macOS).
ⓧ I'd like to double check the full code
Here's everything your notebook should contain so far across all four cells:
# Cell 1: Imports
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (
classification_report,
accuracy_score,
confusion_matrix,
ConfusionMatrixDisplay,
)
from ucimlrepo import fetch_ucirepo
# Cell 2: Load the dataset
dataset = fetch_ucirepo(id=327)
X = dataset.data.features
y = dataset.data.targets
print(f"Dataset shape: {X.shape}")
print(f"Features: {list(X.columns)}")
print(f"\nFirst 5 rows:")
print(X.head())
# Cell 3: Explore the data
print(f"\nDataset info:")
print(f"Total samples: {len(X)}")
print(f"Number of features: {X.shape[1]}")
print(f"\nTarget distribution:")
print(y.value_counts())
# Cell 4: Visualize class distribution
target_counts = y.value_counts()
plt.figure(figsize=(6, 4))
plt.bar(
["Legitimate (1)", "Phishing (-1)"],
[target_counts.get(1, 0), target_counts.get(-1, 0)],
color=["green", "red"],
)
plt.title("Dataset Class Distribution")
plt.ylabel("Number of Websites")
plt.tight_layout()
plt.show()
You've loaded real cybersecurity data and confirmed it's balanced between phishing and legitimate sites. Next up, you'll train a Random Forest classifier to learn the patterns that distinguish malicious URLs from safe ones.
Train a Random Forest Classifier
Your dataset is loaded and you've confirmed the classes are roughly balanced. Now it's time to build the model that actually detects phishing websites.
In this step, you'll train a Random Forest classifier on 80% of the data, then test it on the remaining 20% to see how well it distinguishes phishing sites from legitimate ones.
In this step, get ready to:
- Prepare the data by remapping labels and splitting into training and test sets.
- Train a Random Forest model on the training data.
- Evaluate the model's accuracy, precision, recall, and F1 score.
Prepare the data for training
Before feeding data into a model, you need to prepare it. The dataset currently uses -1 for phishing and 1 for legitimate, but scikit-learn works more cleanly with 0 and 1.
You also need to hold back a portion of the data for testing. This is called a train/test split. The model learns patterns from the training set, then you check its accuracy on the test set it has never seen.
- Create a new cell in your notebook and add the following code to remap labels and split the data:
# Cell 5: Prepare data for training
y_binary = y.replace({-1: 0, 1: 1})
X_train, X_test, y_train, y_test = train_test_split(
X, y_binary, test_size=0.2, random_state=42
)
print(f"Training set size: {len(X_train)}")
print(f"Test set size: {len(X_test)}")
What does this code do?
- y.replace({-1: 0, 1: 1}) remaps the target labels so 0 means phishing and 1 means legitimate. This makes the output easier to interpret later.
- train_test_split randomly divides your data into 80% for training and 20% for testing.
- random_state=42 ensures the split is reproducible. Anyone running this code gets the same split.
- Run the cell by pressing Shift+Enter.
- You should see output showing a training set size of 8,844 and a test set size of 2,211.
Seeing a NameError?
If you see NameError: name 'X' is not defined, it means the cells above haven't been run yet. Go back to Cell 1 and run each cell in order from top to bottom.
help me fix this NameError in my Jupyter Notebook
Train the model
A Random Forest works by building many decision trees on random subsets of the data, then combining their votes to make a final prediction. It's effective for classification tasks because it reduces overfitting compared to a single decision tree.
- Create a new cell and add this code to train the model and generate predictions:
# Cell 6: Train the Random Forest
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train.values.ravel())
y_pred = model.predict(X_test)
What does this code do?
- RandomForestClassifier(n_estimators=100) creates a forest of 100 decision trees. More trees generally improve accuracy up to a point.
- model.fit() trains the model by having each tree learn patterns from the training data.
- model.predict(X_test) asks the trained model to classify each website in the test set as phishing (0) or legitimate (1).
- Run the cell. It completes without printed output. The model is now trained and predictions are stored in y_pred.
Cell taking a long time?
Training on 8,844 rows should take only a few seconds. If it's taking much longer, check that you haven't accidentally used the full dataset without splitting. Confirm X_train has 8,844 rows, not 11,055.
help me debug why my model training is slow
Evaluate performance
A model is only useful if you can measure how well it performs. Accuracy tells you the overall percentage of correct predictions. The classification report goes deeper with precision, recall, and F1 score for each class.
- Create a new cell and add this code to print the model's performance metrics:
# Cell 7: Evaluate the model
accuracy = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {accuracy:.4f}")
print(f"\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=["Phishing", "Legitimate"]))
What do these metrics mean?
- Accuracy is the percentage of all predictions that were correct.
- Precision answers: of all websites the model flagged as phishing, how many actually were?
- Recall answers: of all actual phishing websites, how many did the model catch?
- F1 score is the balanced average of precision and recall. It's useful when you care about both catching phishing sites and not flagging legitimate ones.
- Run the cell. You should see an accuracy of approximately 0.95 or higher, along with a table showing precision, recall, and F1 for both Phishing and Legitimate classes.
Accuracy much lower than expected?
If your accuracy is below 90%, double-check that you used y_binary (the remapped labels) in the train_test_split call, not the original y. Also confirm random_state=42 is set in both the split and the classifier.
help me understand why my model accuracy is low
✔️ Awesome, I've got everything!
Great. Double check you've saved your notebook before moving on.
ⓧ I'd like to double check the full code
Here's the complete notebook up to this point. Compare your cells against this:
# Cell 1: Imports
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (
classification_report,
accuracy_score,
confusion_matrix,
ConfusionMatrixDisplay,
)
from ucimlrepo import fetch_ucirepo
# Cell 2: Load the dataset
dataset = fetch_ucirepo(id=327)
X = dataset.data.features
y = dataset.data.targets
print(f"Dataset shape: {X.shape}")
print(f"Features: {list(X.columns)}")
print(f"\nFirst 5 rows:")
print(X.head())
# Cell 3: Explore the data
print(f"\nDataset info:")
print(f"Total samples: {len(X)}")
print(f"Number of features: {X.shape[1]}")
print(f"\nTarget distribution:")
print(y.value_counts())
# Cell 4: Visualize class distribution
target_counts = y.value_counts()
plt.figure(figsize=(6, 4))
plt.bar(
["Legitimate (1)", "Phishing (-1)"],
[target_counts.get(1, 0), target_counts.get(-1, 0)],
color=["green", "red"],
)
plt.title("Dataset Class Distribution")
plt.ylabel("Number of Websites")
plt.tight_layout()
plt.show()
# Cell 5: Prepare data for training
y_binary = y.replace({-1: 0, 1: 1})
X_train, X_test, y_train, y_test = train_test_split(
X, y_binary, test_size=0.2, random_state=42
)
print(f"Training set size: {len(X_train)}")
print(f"Test set size: {len(X_test)}")
# Cell 6: Train the Random Forest
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train.values.ravel())
y_pred = model.predict(X_test)
# Cell 7: Evaluate the model
accuracy = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {accuracy:.4f}")
print(f"\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=["Phishing", "Legitimate"]))
Your model is trained and performing well. Next up, you'll visualize exactly where the model gets confused and discover which URL features are the strongest indicators of phishing.
Visualize Results and Feature Importance
Your model is trained and you know it achieves strong accuracy. But a single accuracy number does not tell the full story.
In this step, you will visualize where the model gets things right and wrong using a confusion matrix. Then you will uncover which URL features the model relies on most to distinguish phishing from legitimate sites.
In this step, get ready to:
- Create a confusion matrix heatmap to see where the model makes mistakes.
- Plot a feature importance chart showing which URL characteristics matter most.
Create a confusion matrix
A confusion matrix shows exactly how many predictions fell into four categories: true positives, true negatives, false positives, and false negatives. This lets you see whether your model is better at catching phishing sites or confirming legitimate ones.
- Create a new cell in your notebook and add the following code to generate the confusion matrix heatmap:
# Cell 8: Confusion matrix
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=["Phishing", "Legitimate"])
fig, ax = plt.subplots(figsize=(6, 5))
disp.plot(ax=ax, cmap="Blues")
plt.title("Phishing Detection - Confusion Matrix")
plt.tight_layout()
plt.show()
What does this code do?
confusion_matrix(y_test, y_pred) compares the actual test labels against the model's predictions and produces a 2x2 grid of counts.
ConfusionMatrixDisplay renders that grid as a colour-coded heatmap. The display_labels parameter maps the 0/1 binary labels back to human-readable names so the chart is easy to interpret.
- Run the cell by pressing Shift+Enter.
- You should see a blue heatmap grid. The diagonal squares (top-left and bottom-right) show correct predictions. The off-diagonal squares show mistakes: false positives (legitimate sites flagged as phishing) and false negatives (phishing sites that slipped through).
Why do false negatives matter more here?
In phishing detection, a false negative means a dangerous site was classified as safe. That is worse than a false positive (a safe site getting flagged), because the user might enter their credentials on a phishing page. Look at the off-diagonal numbers to see how your model handles this trade-off.
Not seeing the heatmap?
Make sure you ran the previous cells first. The variables y_test and y_pred must already exist from Cell 5 and Cell 6.
If you restarted your kernel, re-run all cells from the top by clicking Kernel > Restart & Run All.
Help me debug my confusion matrix code
Plot feature importance
Random Forest models track how much each feature contributes to splitting decisions across all trees. Extracting these scores tells you which URL characteristics are the strongest signals for detecting phishing.
- Create a new cell and add the following code to extract and plot feature importance:
# Cell 9: Feature importance
importances = model.feature_importances_
feature_names = X.columns
feature_importance_df = pd.DataFrame({
"feature": feature_names,
"importance": importances
}).sort_values("importance", ascending=True)
top_10 = feature_importance_df.tail(10)
plt.figure(figsize=(8, 6))
plt.barh(top_10["feature"], top_10["importance"], color="steelblue")
plt.xlabel("Importance Score")
plt.title("Top 10 Features for Phishing Detection")
plt.tight_layout()
plt.show()
print("\nTop 5 most important features:")
for _, row in feature_importance_df.tail(5).iloc[::-1].iterrows():
print(f" {row['feature']}: {row['importance']:.4f}")
What does this code do?
- model.feature_importances_ pulls the importance score for each of the 30 features. Higher means the model relies on it more.
- The code builds a DataFrame, sorts by importance, and takes the top 10 for the chart.
- plt.barh creates a horizontal bar chart so feature names are easy to read.
- The final loop prints the top 5 features with their numeric scores for quick reference.
- Run the cell by pressing Shift+Enter.
- You should see a horizontal bar chart showing the top 10 features ranked by importance. Below the chart, the top 5 feature names and their scores print in the output.
What do the top features mean?
Features like sslfinal_state (whether the site has a valid SSL certificate), url_length, and domain_registration_length tend to rank highly. Phishing sites often have short-lived domains, suspicious URLs, and missing SSL certificates.
Your exact ranking may vary slightly, but these structural URL properties are consistently strong indicators of phishing attempts.
Chart looks empty or features are missing?
Check that the variable model exists by running type(model) in a new cell. If you see a NameError, re-run Cell 6 to train the model first.
Help me fix my feature importance chart
✔️ Awesome, I've got everything!
Great. Make sure you have saved your notebook before moving on.
ⓧ I'd like to double check the full code
Here is the complete notebook up to this point. Compare it against your cells to spot any differences.
# Cell 1: Imports
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (
classification_report,
accuracy_score,
confusion_matrix,
ConfusionMatrixDisplay,
)
from ucimlrepo import fetch_ucirepo
# Cell 2: Load the dataset
dataset = fetch_ucirepo(id=327)
X = dataset.data.features
y = dataset.data.targets
print(f"Dataset shape: {X.shape}")
print(f"Features: {list(X.columns)}")
print(f"\nFirst 5 rows:")
print(X.head())
# Cell 3: Explore the data
print(f"\nDataset info:")
print(f"Total samples: {len(X)}")
print(f"Number of features: {X.shape[1]}")
print(f"\nTarget distribution:")
print(y.value_counts())
# Cell 4: Visualize class distribution
target_counts = y.value_counts()
plt.figure(figsize=(6, 4))
plt.bar(
["Legitimate (1)", "Phishing (-1)"],
[target_counts.get(1, 0), target_counts.get(-1, 0)],
color=["green", "red"],
)
plt.title("Dataset Class Distribution")
plt.ylabel("Number of Websites")
plt.tight_layout()
plt.show()
# Cell 5: Prepare data for training
y_binary = y.replace({-1: 0, 1: 1})
X_train, X_test, y_train, y_test = train_test_split(
X, y_binary, test_size=0.2, random_state=42
)
print(f"Training set size: {len(X_train)}")
print(f"Test set size: {len(X_test)}")
# Cell 6: Train the Random Forest
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train.values.ravel())
y_pred = model.predict(X_test)
# Cell 7: Evaluate the model
accuracy = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {accuracy:.4f}")
print(f"\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=["Phishing", "Legitimate"]))
# Cell 8: Confusion matrix
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=["Phishing", "Legitimate"])
fig, ax = plt.subplots(figsize=(6, 5))
disp.plot(ax=ax, cmap="Blues")
plt.title("Phishing Detection - Confusion Matrix")
plt.tight_layout()
plt.show()
# Cell 9: Feature importance
importances = model.feature_importances_
feature_names = X.columns
feature_importance_df = pd.DataFrame({
"feature": feature_names,
"importance": importances
}).sort_values("importance", ascending=True)
top_10 = feature_importance_df.tail(10)
plt.figure(figsize=(8, 6))
plt.barh(top_10["feature"], top_10["importance"], color="steelblue")
plt.xlabel("Importance Score")
plt.title("Top 10 Features for Phishing Detection")
plt.tight_layout()
plt.show()
print("\nTop 5 most important features:")
for _, row in feature_importance_df.tail(5).iloc[::-1].iterrows():
print(f" {row['feature']}: {row['importance']:.4f}")
You now have a complete picture of your model's performance and the features driving its decisions. Next up, you will build a reusable prediction function that scores any website's phishing risk on demand.
Secret mission
Build a Prediction Function
Your model can classify phishing sites, but only on test data it already has. What if you wanted to check a brand new website? Build a reusable prediction function that accepts any website's features and returns a human-readable phishing risk score with confidence level.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.
Resources you used:
- Jupyter Notebook server (running in your terminal).
- Project folder on your Desktop (contains phishing-env and phishing_detector.ipynb).
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with your phishing detector.
- Your Jupyter Notebook server stays running in the terminal.
- Your virtual environment stays active.
- All files remain on your Desktop ready for next time.
Pause - I'll come back to this later
Shut down running processes to free up memory. Your files stay on disk so you can pick up where you left off.
- Switch to the terminal where Jupyter Notebook is running.
- Press Ctrl+C to stop the Jupyter server. Confirm with y if prompted.
- Deactivate your virtual environment by running:
deactivate
Your project files are still on your Desktop. To resume later, navigate back to the project folder, activate the virtual environment, and run jupyter notebook again.
Delete - I don't want to use this again
Remove all project resources and start fresh. This permanently deletes your notebook, trained model, and virtual environment.
- Switch to the terminal where Jupyter Notebook is running.
- Press Ctrl+C to stop the Jupyter server. Confirm with y if prompted.
- Deactivate your virtual environment by running:
deactivate
- Delete the project folder from your Desktop. On macOS or Linux, run:
cd ~/Desktop
ls
Find the folder that contains phishing-env and phishing_detector.ipynb. Then delete it by running:
rm -rf <your-project-folder-name>
- On Windows, open File Explorer, navigate to your Desktop, right-click the project folder, and select Delete.
Nice Work!
Nice Work!
You just built a machine learning-powered phishing website detector from scratch. Starting with a real cybersecurity dataset, you trained a model that classifies malicious URLs with high accuracy.
Here's what you accomplished:
- Trained a Random Forest classifier on 11,055 real phishing and legitimate website samples, achieving ~95%+ accuracy.
- Evaluated model performance using precision, recall, F1 scores, and a confusion matrix visualization.
- Identified the top feature importances for phishing detection, revealing which URL characteristics (SSL state, URL length, domain registration) matter most.
- Built a reusable prediction function that scores any website's phishing risk with a confidence percentage (Secret Mission).
Ready to quiz yourself?