Build a Simple Job Recommendation Engine with Python and Tkinter

Published: (January 30, 2026 at 10:19 PM EST)
5 min read
Source: Dev.to

Source: Dev.to

Job Recommendation Engine Tutorial

Ever wanted to create a desktop app that recommends jobs based on your skills? In this tutorial, we’ll build a Job Recommendation Engine using Python, Tkinter, and ttkbootstrap.

We’ll cover:

  • Setting up the project
  • Defining the job data structure
  • Fetching and filtering jobs
  • Building a simple recommendation engine
  • Displaying results with a Tkinter UI
  • Adding pagination and search

Let’s get started! 🚀


1. Setup & Install Dependencies

First, make sure you have Python 3 installed. Then install the required packages:

pip install ttkbootstrap pillow requests

Packages explained

PackagePurpose
ttkbootstrapModern Tkinter UI styling
PillowHandle and display images (company logos)
requestsFetch logos from URLs

2. Define the Job Data Structure

We’ll use a dataclass to represent each job. This makes it easy to store and manage job info.

from dataclasses import dataclass
from typing import List

@dataclass
class Job:
    title: str
    company: str
    url: str
    description: str
    skills: List[str]
    location: str
    logo_url: str = ""
    score: float = 0.0

✅ This gives each job a title, company, location, skills, logo, and a score for recommendations.


3. Fetch Jobs

For this tutorial we’ll use a static list of jobs. Later you could replace this with API calls.

from typing import List

def fetch_jobs(query: str) -> List[Job]:
    jobs_data = [
        Job(
            "Python Developer",
            "TechCorp",
            "https://example.com/job1",
            "Develop backend applications using Python.",
            ["Python", "Django"],
            "Remote",
            "https://via.placeholder.com/100x100.png?text=TechCorp",
        ),
        Job(
            "Frontend Engineer",
            "Webify",
            "https://example.com/job2",
            "Build responsive web apps with React.",
            ["JavaScript", "React"],
            "NY, USA",
            "https://via.placeholder.com/100x100.png?text=Webify",
        ),
        Job(
            "Data Scientist",
            "DataWorks",
            "https://example.com/job3",
            "Analyze large datasets and build ML models.",
            ["Python", "ML", "Pandas"],
            "SF, USA",
            "https://via.placeholder.com/100x100.png?text=DataWorks",
        ),
    ]

    # Filter jobs by query keyword
    query_tokens = set(query.lower().split())
    matched_jobs = []
    for job in jobs_data:
        text_to_match = " ".join(
            [
                job.title.lower(),
                job.description.lower(),
                " ".join(job.skills).lower(),
            ]
        )
        if any(token in text_to_match for token in query_tokens):
            matched_jobs.append(job)
    return matched_jobs

💡 Tip: This simple search matches keywords in the job title, description, or skills.


4. Build a Simple Recommendation Engine

We’ll assign a score to each job based on keyword matches to rank results.

def recommend_jobs(query: str, candidates: List[Job], top_n: int = 5):
    query_tokens = set(query.lower().split())
    recommendations = []

    for job in candidates:
        text_to_match = " ".join(
            [
                job.title.lower(),
                job.description.lower(),
                " ".join(job.skills).lower(),
            ]
        )
        score = sum(1 for token in query_tokens if token in text_to_match)
        recommendations.append((job, score))

    recommendations.sort(key=lambda x: x[1], reverse=True)
    return recommendations[:top_n]

✅ Higher scores mean better matches. This is a simple content‑based recommendation.


5. Load Company Logos

We’ll use Pillow to fetch and display images in the app.

import io
import requests
from PIL import Image, ImageTk

logo_cache = {}

def load_image(url: str, size: tuple = (80, 80)):
    """Download an image, resize it, and cache the PhotoImage."""
    if not url:
        return None
    if url in logo_cache:
        return logo_cache[url]
    try:
        resp = requests.get(url, timeout=10)
        resp.raise_for_status()
        img = Image.open(io.BytesIO(resp.content))
        img = img.resize(size, Image.ANTIALIAS)
        photo = ImageTk.PhotoImage(img)
        logo_cache[url] = photo
        return photo
    except Exception:
        return None

💡 Tip: Caching avoids downloading the same image multiple times.


6. Build the Tkinter UI

Now let’s build the user interface with ttkbootstrap.

import tkinter as tk
import ttkbootstrap as tb
from ttkbootstrap.widgets.scrolled import ScrolledText

# ----------------------------------------------------------------------
# Main window
# ----------------------------------------------------------------------
app = tb.Window(
    title="Job Recommendation Engine",
    themename="flatly",
    size=(980, 720),
)

# ----------------------------------------------------------------------
# Top Section – Search Bar
# ----------------------------------------------------------------------
top = tb.Frame(app, padding=15)
top.pack(fill=tk.X)

tb.Label(
    top,
    text="Job Recommendation Engine",
    font=("Segoe UI", 16, "bold"),
).pack(anchor=tk.W)

query_entry = tb.Entry(top, font=("Segoe UI", 12))
query_entry.pack(fill=tk.X, pady=8)

tb.Button(
    top,
    text="Search",
    bootstyle="primary",
    command=lambda: print("Search clicked!"),
).pack(anchor=tk.E)

# ----------------------------------------------------------------------
# Results Area
# ----------------------------------------------------------------------
result_frame = tb.Frame(app, padding=(15, 5))
result_frame.pack(fill=tk.BOTH, expand=True)

result_box = ScrolledText(result_frame, autohide=True)
result_box.pack(fill=tk.BOTH, expand=True)

text = result_box.text
text.configure(state="disabled", wrap="word")
# This is where job results will be displayed.

# ----------------------------------------------------------------------
# Pagination Controls
# ----------------------------------------------------------------------
nav = tb.Frame(app, padding=10)
nav.pack(fill=tk.X)

prev_btn = tb.Button(nav, text="← Prev", bootstyle="secondary")
prev_btn.pack(side=tk.LEFT)

page_label = tb.Label(nav, text="Page 1", font=("Segoe UI", 10))
page_label.pack(side=tk.LEFT, padx=10)

next_btn = tb.Button(nav, text="Next →", bootstyle="secondary")
next_btn.pack(side=tk.LEFT)

# ----------------------------------------------------------------------
# (Optional) Hook up pagination and search logic here
# ----------------------------------------------------------------------

💡 You can now hook the Search, Prev, and Next buttons to the functions defined earlier to navigate between result pages.


7. Putting It All Together

Below is a minimal example that ties everything together. Feel free to expand it with more features (e.g., detailed job view, saving favorites, etc.).

def display_results(recommendations):
    """Populate the ScrolledText widget with job cards."""
    text.configure(state="normal")
    text.delete("1.0", tk.END)

    for job, score in recommendations:
        # Load logo (cached)
        logo = load_image(job.logo_url)

        # Build a simple text block for each job
        block = f"""\
{job.title} ({job.location})
Company: {job.company}
Score: {score}
Skills: {', '.join(job.skills)}
Link: {job.url}
"""
        text.insert(tk.END, block + "\n" + "-" * 40 + "\n\n")
    text.configure(state="disabled")

def on_search():
    query = query_entry.get()
    candidates = fetch_jobs(query)          # In a real app, fetch all jobs first
    recommendations = recommend_jobs(query, candidates, top_n=10)
    display_results(recommendations)

# Bind the search button
search_btn = tb.Button(
    top,
    text="Search",
    bootstyle="primary",
    command=on_search,
)
search_btn.pack(anchor=tk.E, pady=5)

# Start the Tkinter main loop
if __name__ == "__main__":
    app.mainloop()

That’s it! You now have a functional desktop job‑recommendation app built with Python, Tkinter, and ttkbootstrap. 🎉

Feel free to experiment with more sophisticated recommendation algorithms, integrate real job APIs, or polish the UI further. Happy coding!

Connecting the Search Input with Fetching, Recommending, and Displaying Jobs

Use threading to keep the UI responsive while performing the search.

import threading

def perform_search():
    """Called when the user presses the Search button."""
    query = query_entry.get().strip()
    if not query:
        return
    # Run the heavy work in a background thread
    threading.Thread(target=search_thread, args=(query,), daemon=True).start()

def search_thread(query):
    """Background worker that fetches jobs, ranks them, and prints the results."""
    candidates = fetch_jobs(query)
    recommendations = recommend_jobs(query, candidates)
    for job, score in recommendations:
        print(f"{job.title} @ {job.company} | Score: {score}")

Full‑screen Controls

  • Enter fullscreen mode
  • Exit fullscreen mode

Try It Out

  1. Run the script

    python job_recommender.py
  2. Type a keyword (e.g., Python or React) in the search box.

  3. Double‑click a job title to open its link (if you’ve implemented that feature).

  4. Navigate through results with the Prev / Next buttons.


Full Source Code

The complete project is available on GitHub:

https://github.com/rogers-cyber/JOBREC


Conclusion

Congrats! 🎉 You now have a working Job Recommendation Engine featuring:

  • Python data classes
  • Simple content‑based recommendations
  • A Tkinter UI with search, results, and pagination

Next Steps

  • Replace the static job list with real API calls.
  • Add fuzzy matching for smarter search.
  • Enhance the UI with images, icons, and filters.

Job Recommendation Engine Screenshot

Back to Blog

Related posts

Read more »