Analyze Job Posts with AWS AI Services
Build a Python pipeline that extracts skills from job post screenshots.
Introduction
30 Second Summary
You have probably scrolled through dozens of job postings, trying to spot which skills keep showing up and what employers actually want. Tracking those patterns manually across 10 or 20 listings is tedious and easy to get wrong.
In this project, you will build a command-line Python tool that turns job post screenshots into a structured intelligence report. You will upload screenshots to Amazon S3, extract the text with Amazon Textract, and identify the most in-demand skills and recurring patterns with Amazon Comprehend.
What You'll Build
Drop a batch of job listing screenshots into your tool, run one command, and get back a frequency-ranked breakdown of the skills, key phrases, companies, and job titles that keep appearing across roles.
By the end of this project, you'll have:
- A working pipeline that uploads job post screenshots to Amazon S3 and uses Amazon Textract to pull clean text out of each image.
- A structured analysis powered by Amazon Comprehend that identifies key phrases like "distributed systems" or "3 years of experience" alongside named entities like company names, locations, and technologies.
- An aggregated frequency report printed to your terminal showing top skills, most common phrases, and entity breakdowns across all your job posts, ranked by how often they appear.
- Secret Mission: Export your analysis to a CSV file so you can track trends over time as you add more job posts.
Are there any prerequisites?
This project assumes you already have an AWS account with active credits, Python 3, and the AWS CLI installed and configured on your machine. Step 1 walks you through installing the one remaining package you need.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters. This is your chance to commit to the goal before any hands-on work begins.
Set Up Your Project and S3 Bucket
You know what you're building: a Python pipeline that reads job post screenshots and surfaces the most in-demand skills. Before you write any code, you need two things in place.
boto3 is the Python SDK that lets your scripts call AWS services like Amazon S3, Amazon Textract, and Amazon Comprehend. You also need an S3 bucket to store the screenshots that Textract reads from.
In this step, get ready to:
- Install boto3 and verify it works.
- Create an S3 bucket and upload job post screenshots.
- Verify your uploads are accessible from S3.
Create your project folder and install boto3
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir job-post-analyzer
cd job-post-analyzer
- Install boto3 by running:
pip3 install boto3
What is boto3?
boto3 is the official AWS SDK for Python. It provides Python functions that map directly to AWS service APIs, so you can create S3 buckets, call Textract, run Comprehend analysis, and more from a regular Python script.
- Verify the installation by running:
python3 -c "import boto3; print(boto3.__version__)"
You should see a version number like 1.43.80 printed to your terminal.
Not seeing a version number?
- If your terminal cannot find pip3, try running pip install boto3 instead.
- If you see a ModuleNotFoundError, your pip3 and python3 may point to different Python installations. Try running:
python3 -m pip install boto3
Help me fix my boto3 installation.
Create your S3 bucket and upload screenshots
Textract reads images directly from S3. You need to collect a few job post screenshots, save them locally, and then upload them to a bucket.
- Open a job posting site like LinkedIn Jobs or Indeed in your browser.
- Find 3-5 job listings in a field you are interested in.
- Take a full-page screenshot of each listing and save it as a PNG or JPEG file.
Getting good screenshots
Scroll down so the full job description is visible, including the requirements and qualifications sections. The more text Textract can read, the richer your analysis will be.
- Create a screenshots folder inside your project by running:
mkdir screenshots
- Move or copy your saved screenshot files into the screenshots folder.
- Rename one of your screenshots to sample-job-post.png. You will use this specific file to test Textract in the next step.
- Confirm your files are in place by running:
ls screenshots/
You should see your screenshot files listed, including sample-job-post.png.
- Create your S3 bucket by running this command:
aws s3 mb s3://job-post-analyzer-[[YOURNAME="your name"]] --region us-east-1
You should see make_bucket followed by your bucket name, confirming the bucket was created.
Why does my bucket name need to include my name?
S3 bucket names are globally unique across all AWS accounts. Adding your name to the end ensures no one else has already taken the same bucket name.
- Upload your screenshots to the bucket by running:
aws s3 cp ./screenshots/ s3://job-post-analyzer-[[YOURNAME="your name"]]/screenshots/ --recursive
What does --recursive do?
The --recursive flag tells the AWS CLI to upload every file inside the screenshots/ folder. Without it, the command would only work on a single file.
You should see upload progress lines printed for each file.
Seeing an access denied or bucket error?
- Make sure your AWS CLI is configured with valid credentials. Run aws sts get-caller-identity to check that your CLI is authenticated.
- If the bucket name was already taken, choose a different suffix and recreate the bucket.
Help me fix my S3 bucket creation or upload error.
Verify your uploads in S3
Before you run the next command, how many files do you expect to see listed in your bucket?
- List the contents of your S3 screenshots folder by running:
aws s3 ls s3://job-post-analyzer-[[YOURNAME="your name"]]/screenshots/
You should see each of your screenshot files listed with their sizes and upload timestamps. The count should match the number of files you placed in your local screenshots/ folder.
Not seeing your files?
- Double-check the bucket name in your command matches the one you created. A single typo means the CLI looks at a different bucket.
- Make sure you included the --recursive flag when you ran the upload command. Without it, only one file is uploaded.
- Re-run the upload command if needed. Uploading again is safe because aws s3 cp overwrites existing files with the same name.
Help me figure out why my files are not showing up in S3.
Your storage is set up and your screenshots are ready. Next up, you will feed your first screenshot to Amazon Textract and see it pull raw text straight out of the image.
Extract Text from a Single Screenshot with Textract
Your screenshots are sitting in S3, ready for processing. A Python script can't read pixels the way your eyes can, so the first step is pulling the actual text out of those images.
Amazon Textract is an AWS service that performs OCR (Optical Character Recognition) on images, returning each line of detected text as structured data. In this step, you'll send one screenshot to Textract and see what comes back.
In this step, get ready to:
- Extract text from a single job post screenshot using Amazon Textract.
- Examine the raw OCR output and spot its limitations.
Write the extraction script
- Create a new file called extract_single.py in the folder that contains your screenshots/ directory.
- Open extract_single.py in a text editor and add the following code:
import boto3
# Your S3 bucket name from Step 1
BUCKET = "job-post-analyzer-yourname"
# One of your uploaded screenshot filenames
DOCUMENT = "screenshots/sample-job-post.png"
def extract_text(bucket, document):
# Create a Textract client
textract = boto3.client("textract")
# Send the image to Textract for text detection
response = textract.detect_document_text(
Document={"S3Object": {"Bucket": bucket, "Name": document}}
)
# Collect each LINE block's text
lines = []
for block in response["Blocks"]:
if block["BlockType"] == "LINE":
lines.append(block["Text"])
return "\n".join(lines)
What does this code do?
BUCKET and DOCUMENT define which S3 object to send to Textract. You'll update these to match your actual bucket and screenshot filename in a moment.
extract_text() creates a Textract client and calls detect_document_text(), pointing it at your image stored in S3.
Textract returns a list of Blocks. The function filters for LINE blocks, which contain complete lines of detected text, and joins them with newlines.
- Update yourname in the BUCKET value to match the bucket name you created in Step 1.
- Update sample-job-post.png in the DOCUMENT value to match one of your uploaded screenshot filenames from Step 1.
- Save extract_single.py.
- Add the main() function below extract_text() in extract_single.py:
def main():
print(f"Extracting text from s3://{BUCKET}/{DOCUMENT}\n")
# Extract and display the text
text = extract_text(BUCKET, DOCUMENT)
print("--- Extracted Text ---")
print(text)
print(f"\n--- Total lines extracted: {len(text.splitlines())} ---")
if __name__ == "__main__":
main()
What does this code do?
main() calls extract_text() and prints the full result to your terminal. It also shows the total number of lines Textract found.
The if __name__ == "__main__" guard ensures main() only runs when you execute the script directly.
- Save extract_single.py.
✔️ Awesome, I've got everything!
Great. Double check you've saved extract_single.py and that your BUCKET and DOCUMENT values match your actual S3 bucket and screenshot filename.
ⓧ I'd like to double check the full code
Here's the complete extract_single.py file. Compare it against yours:
import boto3
BUCKET = "job-post-analyzer-yourname"
DOCUMENT = "screenshots/sample-job-post.png"
def extract_text(bucket, document):
textract = boto3.client("textract")
# Send the image to Textract for text detection
response = textract.detect_document_text(
Document={"S3Object": {"Bucket": bucket, "Name": document}}
)
# Collect each LINE block's text
lines = []
for block in response["Blocks"]:
if block["BlockType"] == "LINE":
lines.append(block["Text"])
return "\n".join(lines)
def main():
print(f"Extracting text from s3://{BUCKET}/{DOCUMENT}\n")
text = extract_text(BUCKET, DOCUMENT)
print("--- Extracted Text ---")
print(text)
print(f"\n--- Total lines extracted: {len(text.splitlines())} ---")
if __name__ == "__main__":
main()
Run the script and examine the output
Before you run this, think about what the extracted text will look like. Will Textract return the text neatly organized into categories like skills, company info, and requirements? Or something more raw?
- Run your extraction script:
python3 extract_single.py
Your terminal fills with lines of text pulled straight from the screenshot image. At the bottom, you see the total number of lines Textract extracted.
Didn't see any extracted text?
- Check that your BUCKET value matches the exact name of your S3 bucket from Step 1, including the part after job-post-analyzer-.
- Check that your DOCUMENT value matches an actual file in your bucket. The key must include the screenshots/ prefix and the correct file extension.
- If you see a credentials error, make sure your AWS CLI is still configured correctly. You verified this in Step 1.
Why isn't my Textract extraction script returning any text?
Now look at the output closely. Job titles, required skills, company names, years of experience, and generic filler are all mixed together in one block of words. There's no way to tell which words are skills versus which are company names versus which are dates.
Why is raw text not enough?
Textract reads text from images, but it doesn't understand what the words mean. It can't tell a skill like "Python" apart from a company name or a location.
To find the most in-demand skills across many job posts, you need a service that can categorize text automatically. That's what Amazon Comprehend does in the next step.
You've proven that Textract can pull text out of your screenshots. Next, you'll feed this text into Amazon Comprehend to separate skills from company names and uncover the structure hiding in the raw words.
Analyze the Extracted Text with Comprehend
In the last step, you extracted raw text from a job post screenshot using Textract. You saw the output: a wall of words with job titles, requirements, and company info all jumbled together.
There is no way to tell what is a "skill" versus a "company name" versus filler text. Amazon Comprehend solves this by applying NLP (Natural Language Processing) to automatically identify key phrases like "distributed systems" and named entities like company names, locations, and job titles.
In this step, get ready to:
- Feed extracted text into Comprehend's key phrase extraction to surface terms like "3 years of experience" or "cloud infrastructure".
- Run Comprehend's entity recognition to identify companies, job titles, quantities, and locations automatically.
- Compare both outputs side by side and see structured intelligence emerge from raw text.
Create analyze_single.py with Textract extraction
You will build a new script that reuses the extraction logic from Step 2, then layers Comprehend analysis on top of it.
- Create a new file called analyze_single.py in your project folder (the same folder where extract_single.py lives).
- Start the file with the same boto3 import, constants, and extract_text function from Step 2 by copying the following code into it:
import boto3
# S3 location of the job post screenshot
BUCKET = "job-post-analyzer-yourname"
DOCUMENT = "screenshots/sample-job-post.png"
def extract_text(bucket, document):
# Create a Textract client to perform OCR
textract = boto3.client("textract")
# Send the S3 object to Textract for text detection
response = textract.detect_document_text(
Document={"S3Object": {"Bucket": bucket, "Name": document}}
)
# Collect only the LINE blocks (full lines of text)
lines = []
for block in response["Blocks"]:
if block["BlockType"] == "LINE":
lines.append(block["Text"])
return "\n".join(lines)
Why reuse the extraction function?
The extract_text function is identical to the one in extract_single.py. You are copying it here so that analyze_single.py is a self-contained script that handles both extraction and analysis in one run.
- Save analyze_single.py.
- Confirm the file exists by running:
ls
You should see both extract_single.py and analyze_single.py listed in the output.
Add the Comprehend analysis function
Now you will add the function that takes the raw extracted text and sends it to Comprehend for two types of analysis: key phrase extraction and entity recognition.
- In analyze_single.py, add the following analyze_text function below the extract_text function:
def analyze_text(text):
# Create a Comprehend client for NLP analysis
comprehend = boto3.client("comprehend")
# Extract key phrases like "3 years of experience"
phrases_response = comprehend.detect_key_phrases(
Text=text, LanguageCode="en"
)
# Identify named entities like companies and locations
entities_response = comprehend.detect_entities(
Text=text, LanguageCode="en"
)
return phrases_response["KeyPhrases"], entities_response["Entities"]
What does this function do?
This function makes two separate Comprehend API calls on the same text.
- detect_key_phrases() finds meaningful multi-word phrases like "distributed systems" or "3 years of experience". Each phrase comes with a confidence score.
- detect_entities() classifies words into categories like ORGANIZATION, TITLE, QUANTITY, LOCATION, and DATE. This is what separates "Amazon" (a company) from "Seattle" (a location).
- Now add the main() function and entry point at the bottom of analyze_single.py, below the analyze_text function:
def main():
print(f"Extracting text from s3://{BUCKET}/{DOCUMENT}\n")
text = extract_text(BUCKET, DOCUMENT)
print("Analyzing with Comprehend...\n")
phrases, entities = analyze_text(text)
# Print the top 15 key phrases sorted by confidence
print("--- Key Phrases ---")
for phrase in sorted(phrases, key=lambda p: p["Score"], reverse=True)[:15]:
print(f" {phrase['Text']} (confidence: {phrase['Score']:.2f})")
if __name__ == "__main__":
main()
What does this code do?
- extract_text() sends your screenshot to Textract and pulls back the raw text, just like in Step 2.
- analyze_text() passes that raw text into Comprehend and gets back both key phrases and entities.
- The loop sorts phrases by their confidence score (highest first) and prints only the top 15. You will add entity printing next.
- Save analyze_single.py.
- Run the script to see your first Comprehend results by running:
python3 analyze_single.py
You should see a list of key phrases printed under --- Key Phrases ---, each with a confidence score between 0 and 1.
Not seeing any key phrases?
- Check that your BUCKET and DOCUMENT values match a real screenshot in your S3 bucket.
- Make sure the region your AWS CLI is configured for matches the region where you created your bucket.
- If you see an access denied error, confirm your IAM user has permissions for both Textract and Comprehend.
Help me debug my analyze_single.py script
Add entity recognition to the output
Key phrases tell you what concepts appear in the posting, but they do not tell you the role each word plays. The entity data your analyze_text function already returns can answer that: is "Amazon" an organization, a product, or a location?
- In analyze_single.py, find the last line of the key phrases for loop inside main():
print(f" {phrase['Text']} (confidence: {phrase['Score']:.2f})")
- Add the following entity-printing code directly below that line, still inside main() and above the if __name__ block:
print("\n--- Entities by Type ---")
# Group entities by their type (ORGANIZATION, TITLE, etc.)
entities_by_type = {}
for entity in entities:
entity_type = entity["Type"]
if entity_type not in entities_by_type:
entities_by_type[entity_type] = []
entities_by_type[entity_type].append(entity)
# Print each entity type with its items and confidence
for entity_type, items in sorted(entities_by_type.items()):
print(f"\n {entity_type}:")
for item in items:
print(f" - {item['Text']} (confidence: {item['Score']:.2f})")
What does this code do?
- The code groups every entity Comprehend found by its Type field. Comprehend assigns types like ORGANIZATION, TITLE, QUANTITY, LOCATION, DATE, PERSON, and OTHER.
- Each entity is printed under its type heading with a confidence score, so you can see at a glance which companies, job titles, and quantities appeared in the posting.
- Save analyze_single.py.
Before you run the script again, what do you expect to see this time compared to the last run?
- Run the updated script by running:
python3 analyze_single.py
You should now see two separate sections in the output: the key phrases list from before, followed by an --- Entities by Type --- section with entities grouped under headings like ORGANIZATION, TITLE, QUANTITY, and DATE.
Only seeing key phrases, no entities?
- Make sure the entity-printing code is indented under def main(): (one level of indentation). If it sits outside the function, it will not run.
- Check that the new code is placed below the key phrases loop and above the if __name__ == "__main__": line.
Help me fix my entity printing in analyze_single.py
✔️ Awesome, I've got everything!
Great. Double check you have saved analyze_single.py and that your output shows both key phrases and entities.
ⓧ I'd like to double check the full code
Scroll up through your analyze_single.py file and confirm these sections appear in this order:
- import boto3 at the top, followed by BUCKET and DOCUMENT constants.
- The extract_text(bucket, document) function that calls Textract.
- The analyze_text(text) function that calls both detect_key_phrases() and detect_entities().
- The main() function that prints key phrases (top 15, sorted by score) and entities (grouped by type).
- The if __name__ == "__main__": entry point at the very bottom.
Compare key phrases and entities side by side
Look at the two sections of output in your terminal. Key phrases pull out multi-word concepts like skill requirements and experience levels. Entities classify individual words into categories like company names and locations.
What about the Comprehend text limit?
Each detect_key_phrases() and detect_entities() call accepts up to 100 KB of UTF-8 text. A single job post screenshot typically extracts 1-5 KB of text, so you are well within that limit.
The structured output from one job post is useful, but it is not actionable on its own. You cannot spot patterns from a single data point. To find which skills appear most often, which companies keep posting, and which phrases repeat across roles, you need to batch process all your screenshots and aggregate the results.
You now have structured intelligence from a single job post. Next up, you will build the full pipeline that processes every screenshot in your S3 bucket at once and ranks the most common skills, phrases, and entities across all of them.
Build the Full Pipeline and Aggregate Patterns
In Step 3, you proved that Comprehend can pull structured insights from a single job post. But one post only tells you what one employer wants.
The real value comes from processing all your screenshots at once and spotting which skills, phrases, and entities show up most frequently across every posting. In this step, you will build a full pipeline that scans your entire S3 bucket, processes every screenshot through Textract and Comprehend, and prints a ranked frequency report.
In this step, get ready to:
- Create a new pipeline script with functions to list, extract, and analyze all screenshots in your bucket.
- Run the pipeline and see a ranked report of the most common skills and entities across all your job posts.
- Upload more screenshots and watch the patterns strengthen with more data.
Create the pipeline script with your helper functions
Instead of modifying analyze_single.py, you will create a brand new script called job_analyzer.py. This keeps your single-post script intact for quick tests while the new script handles the full batch.
- Create a new file called job_analyzer.py in your project folder.
- Add the imports, constants, and a function that lists all screenshots in your S3 bucket by copying this code:
import boto3
from collections import Counter
# S3 bucket and prefix where your job post screenshots are stored
BUCKET = "job-post-analyzer-yourname"
PREFIX = "screenshots/"
def list_screenshots(bucket, prefix):
# Connect to S3 and list all objects under the prefix
s3 = boto3.client("s3")
response = s3.list_objects_v2(Bucket=bucket, Prefix=prefix)
if "Contents" not in response:
print("No files found in the bucket prefix.")
return []
# Filter to only image files
image_extensions = (".png", ".jpg", ".jpeg")
keys = [
obj["Key"]
for obj in response["Contents"]
if obj["Key"].lower().endswith(image_extensions)
]
return keys
What does this code do?
Counter is a built-in Python class that counts how many times each item appears. You will use it later to tally key phrases and entities across all your job posts.
list_screenshots() calls s3.list_objects_v2() to fetch every object stored under the screenshots/ prefix, then filters down to only image files. This is how the script discovers which screenshots to process.
- Replace yourname in the BUCKET variable with the same name you used when creating your bucket in Step 1.
- Next, add the extract_text and analyze_text functions below the code you just pasted. These are the same two functions from analyze_single.py:
def extract_text(bucket, document):
# Use Textract to pull text from a single screenshot
textract = boto3.client("textract")
response = textract.detect_document_text(
Document={"S3Object": {"Bucket": bucket, "Name": document}}
)
# Collect every LINE block into a list
lines = []
for block in response["Blocks"]:
if block["BlockType"] == "LINE":
lines.append(block["Text"])
return "\n".join(lines)
def analyze_text(text):
# Use Comprehend to extract key phrases and entities
comprehend = boto3.client("comprehend")
phrases_response = comprehend.detect_key_phrases(
Text=text, LanguageCode="en"
)
entities_response = comprehend.detect_entities(
Text=text, LanguageCode="en"
)
return phrases_response["KeyPhrases"], entities_response["Entities"]
Why are these functions duplicated?
These are identical to the functions in analyze_single.py. Putting them here makes job_analyzer.py self-contained. You can run it without depending on your earlier scripts.
- Save job_analyzer.py.
- Verify the S3 listing works by running this command:
python3 -c "from job_analyzer import list_screenshots, BUCKET, PREFIX; print(list_screenshots(BUCKET, PREFIX))"
You should see a Python list printed with the S3 keys for each screenshot you uploaded, like ['screenshots/sample-job-post.png', ...].
Seeing an empty list or an error?
- If you see [], double check that the BUCKET variable matches the exact name of your S3 bucket. Also confirm your screenshots are stored under the screenshots/ prefix.
- If you see a NoSuchBucket error, make sure the bucket name in your code exactly matches what you created in Step 1. Bucket names are case-sensitive.
- If you see a ModuleNotFoundError, make sure you are running the command from the same folder where job_analyzer.py is saved.
Help me debug why my list_screenshots function returns an empty list or throws an error.
Add the processing loop and aggregated report
Now that the helper functions are in place, you need a main() function that loops through every screenshot, extracts and analyzes each one, and tallies the results.
- In job_analyzer.py, add the main() function below analyze_text by copying this code:
def main():
print(f"Scanning s3://{BUCKET}/{PREFIX} for screenshots...\n")
keys = list_screenshots(BUCKET, PREFIX)
if not keys:
return
print(f"Found {len(keys)} screenshots to process.\n")
# Counters to aggregate results across all job posts
all_phrases = Counter()
all_entities_by_type = {}
for i, key in enumerate(keys, 1):
print(f"[{i}/{len(keys)}] Processing {key}...")
text = extract_text(BUCKET, key)
if not text.strip():
print(f" Skipped (no text extracted)")
continue
phrases, entities = analyze_text(text)
# Tally each phrase (lowercased so duplicates merge)
for phrase in phrases:
all_phrases[phrase["Text"].lower()] += 1
# Group entity counts by their type
for entity in entities:
entity_type = entity["Type"]
if entity_type not in all_entities_by_type:
all_entities_by_type[entity_type] = Counter()
all_entities_by_type[entity_type][entity["Text"]] += 1
What does this code do?
- The loop calls extract_text() and analyze_text() on every screenshot in your bucket, printing a progress counter as it goes.
- all_phrases is a Counter that tallies how many job posts each key phrase appears in. Lowercasing the text ensures "Python" and "python" count as the same phrase.
- all_entities_by_type is a dictionary of Counters, one per entity type (ORGANIZATION, TITLE, QUANTITY, etc.). Each Counter tracks how often that specific entity name appears across all posts.
- Now add the report printing section directly below the code you just pasted, still inside the main() function:
# Print the aggregated report header
print("\n" + "=" * 60)
print("JOB POST ANALYSIS REPORT")
print(f"Screenshots analyzed: {len(keys)}")
print("=" * 60)
# Rank key phrases by frequency
print("\n--- Top 15 Key Phrases ---")
for rank, (phrase, count) in enumerate(all_phrases.most_common(15), 1):
print(f" {rank}. {phrase} (appeared in {count} posts)")
# Show top entities grouped by type
print("\n--- Top Entities by Type ---")
for entity_type in sorted(all_entities_by_type.keys()):
counter = all_entities_by_type[entity_type]
print(f"\n {entity_type}:")
for name, count in counter.most_common(10):
print(f" - {name} ({count}x)")
print("\n" + "=" * 60)
if __name__ == "__main__":
main()
What does this code do?
- most_common(15) returns the 15 most frequently occurring key phrases, ranked from highest to lowest count. This surfaces the skills and requirements that show up across the most job posts.
- The entity section loops through each type (ORGANIZATION, TITLE, QUANTITY, etc.) and shows the top 10 names within each category. This tells you which companies, job titles, and quantities come up most often.
- Save job_analyzer.py.
Before you run this, what do you think the report will look like? How many screenshots will it process, and do you expect any phrases to appear in more than one post?
- Run your pipeline by executing this command:
python3 job_analyzer.py
You should see a progress counter as each screenshot is processed, followed by a full report. The report shows the top 15 key phrases with frequency counts and the top entities grouped by type (ORGANIZATION, TITLE, QUANTITY, and others).
Script errors or no output?
- If you see an IndentationError, make sure the report printing code is indented at the same level as the rest of the main() function body (one level of indentation inside def main():).
- If you see No files found in the bucket prefix., check that your BUCKET and PREFIX variables match where your screenshots are stored in S3.
- If the script processes screenshots but the report shows very few phrases, that is expected with only 3-5 posts. You will add more in the next substep.
Help me debug why my job_analyzer.py pipeline is failing or producing unexpected output.
✔️ Awesome, I've got everything!
Great. Double check you have saved job_analyzer.py and that the report printed to your terminal.
ⓧ I'd like to double check the full code
Your job_analyzer.py should contain these sections in order. Scroll up and confirm each one is present:
- Imports (boto3 and Counter), BUCKET and PREFIX constants, and the list_screenshots() function.
- The extract_text() and analyze_text() functions.
- The main() function with the processing loop, Counter aggregation, and report printing.
- The if __name__ == "__main__": block at the very bottom calling main().
Upload more screenshots and watch patterns emerge
With only 3-5 screenshots, you might see scattered results with few phrases appearing more than once. The more data you feed in, the clearer the signal becomes about what employers actually want.
- Collect a few more job post screenshots from job boards. Aim for 10-15 total in your screenshots/ folder.
- Upload the new screenshots to your S3 bucket by running this command:
aws s3 cp ./screenshots/ s3://[[BUCKET_NAME="your-bucket-name"]]/screenshots/ --recursive
- Confirm the uploads by listing the bucket contents:
aws s3 ls s3://[[BUCKET_NAME="your-bucket-name"]]/screenshots/
You should see all of your screenshot files listed, including the new ones you just uploaded.
Now, before you rerun the pipeline, think about what you expect to change. Do you think new key phrases will appear? Will the frequency counts go up for phrases you already saw?
- Rerun the pipeline with your expanded dataset:
python3 job_analyzer.py
You should see the progress counter process all of your screenshots, followed by a ranked report showing the top 15 key phrases and top entities by type with frequency counts next to each item. With more data, the patterns become much clearer.
What should I see?
With 10-15 screenshots, you will likely see some key phrases appearing in 3, 4, or more posts. Common patterns include phrases related to years of experience, specific programming languages, or domain expertise.
The entity types give you a different angle. ORGANIZATION entities reveal which companies or products keep showing up. TITLE entities surface common job titles. QUANTITY entities capture numbers like "5 years" or "3+ years" that hint at experience expectations.
Report looks the same as before?
- Verify your new screenshots actually uploaded by running aws s3 ls on your bucket's screenshots/ prefix again. The file count should match the number of images in your local folder.
- Make sure your new screenshots are in a supported format (.png, .jpg, or .jpeg). Other formats will be filtered out by list_screenshots().
Help me figure out why my new screenshots are not showing up in the job analyzer report.
Secret mission
Export Your Analysis to CSV
Your terminal report vanishes when you close the window. In this mission, you'll add CSV export to your pipeline so you can save your analysis, compare different job searches, and track how skill demand changes over time.
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's only ongoing resource is your S3 bucket, which sits within the 5 GB Always Free tier at no cost.
Resources you used:
- S3 bucket (job-post-analyzer-yourname) containing your uploaded job post screenshots.
- Local project files: extract_single.py, analyze_single.py, job_analyzer.py, and job_analysis_report.csv.
- boto3 Python package installed via pip3.
Keep everything running
No action needed. Choose this if you plan to keep adding job post screenshots and rerunning the analyzer.
- Your S3 bucket stays within the 5 GB Always Free tier, so there is no ongoing storage cost.
- Amazon Textract and Amazon Comprehend only incur costs when you run your scripts. They do not charge anything while idle.
- Your local Python scripts and CSV report remain on your machine ready to use.
Pause - I'll come back to this later
There are no running services to pause. Amazon S3 storage is the only ongoing resource, and your screenshots sit well within the free tier.
- Your S3 bucket and screenshots will stay in place at no cost until you return.
- Your local project files will remain on your machine whenever you are ready to pick up again.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below to clean up everything.
- Delete your S3 bucket and all its contents by running this command:
aws s3 rb s3://[[BUCKET_NAME="job-post-analyzer-yourname"]] --force
What does this command do?
The --force flag empties the bucket first by deleting all objects inside it, then removes the bucket itself.
- Delete your local project files. On macOS or Linux, navigate to the folder containing your scripts and run:
rm extract_single.py analyze_single.py job_analyzer.py job_analysis_report.csv
- On Windows, navigate to the folder containing your scripts and run:
del extract_single.py, analyze_single.py, job_analyzer.py, job_analysis_report.csv
- If you also want to remove the local screenshots folder you uploaded from, delete that folder as well.
- Uninstall the boto3 package by running:
pip3 uninstall boto3
What about the AWS CLI?
The AWS CLI and Python were prerequisites you had before this project. Only remove them if you are sure you will not need them for other projects.
Nice Work!
Nice Work!
You just built a command-line intelligence tool that turns a pile of job post screenshots into a ranked skill frequency report. That is a real data pipeline, powered by AWS AI services, solving a real problem.
You learned how to:
- Built a Python pipeline that uploads job post screenshots to Amazon S3 and extracts text from images using Amazon Textract's OCR.
- Used Amazon Comprehend's NLP to identify key phrases and named entities from unstructured job posting text.
- Aggregated results across multiple job posts using Python's Counter to surface the most in-demand skills, recurring phrases, and common entities, ranked by frequency.
- Secret Mission: Exported your analysis results to a CSV file for tracking skill trends over time.
Ready to quiz yourself?