Daily automated pipeline that tracks Data Scientist job postings and keeps a clean, structured dataset for long-term analysis.
(Updated automatically every day)
Last run: Aug 12, 2026 at 10:00 AM EDT
| Metric | Value |
|---|---|
| Total jobs tracked | 13328 |
| Jobs collected today | 31 |
| New unique roles today | 31 |
This project creates a reliable daily record of Data Scientist job openings to support long-term trend analysis.
An automated pipeline collects public job postings from Google Jobs each morning, cleans and structures the data, deduplicates it across days, and stores the results in long-term object storage.
The current configuration collects postings from New York City and nearby areas, but the location can be changed at any time through the settings file.
Part 1 focuses on building a clean, continuously growing dataset.
Part 2 will use that dataset to reveal patterns in hiring demand, geography, seniority, and how the job market evolves over time.
The pipeline is set up to run on its own for months with no ongoing expenses.
Using only free tier services such as SerpApi, GitHub Actions, Cloudflare R2, and Telegram, it keeps collecting and storing data quietly in the background until it is time to begin the analysis phase.
- Fetches Google Jobs listings through SerpApi with configurable query and location.
- Uses a quota-aware request cap to avoid over-spending API budget.
- Fully automated through GitHub Actions.
- Transforms raw SerpApi JSON into a consistent, analysis-ready schema.
- Generates a stable
job_keyto track identical postings across days. - Maintains a SQLite database of all seen jobs to avoid duplicates and measure job “lifetimes.”
- Saves raw JSON and processed Parquet snapshots locally.
- Syncs state and outputs to Cloudflare R2 for durable, long-term storage.
- Structure mirrors local folders for easy downstream analysis.
- Structured logging at each pipeline stage.
- Automatic Telegram notifications with daily summaries.
- Error alerts sent directly to the Telegram bot for visibility.
- Clear separation of scraping, normalization, state handling, and storage.
- YAML-based config allows easy changes to search parameters, locations, and request budgets.
- Designed so future jobs (e.g., multiple search terms or cities) can be added without rewriting the pipeline.
Fetches raw Google Jobs data from SerpApi, handles pagination, stop conditions, and rate-control logic.
Writes raw JSON snapshots for full reproducibility.
Code: source/scraper.py
Transforms raw SerpApi responses into a consistent schema defined in
config/normalize_schema.json.
Generates stable job_key identifiers for cross-day tracking.
Code: source/normalize.py
Tracks which jobs have ever appeared using a SQLite table with
job_key, first_seen, and last_seen.
Enables identifying new, returning, and persistent postings.
Code: source/seen_store.py
Maintains SerpApi request usage between runs, including last monthly reset
and unused carryover capacity.
Code: source/state_store.py
Saves daily outputs (raw JSON + processed Parquet) locally and syncs them to
Cloudflare R2 via S3-compatible operations.
Code: source/storage.py
Daily GitHub Actions workflow runs the pipeline at 13:00 UTC, with
Telegram notifications reporting success or failure and run statistics.
Code: source/telegram_bot.py, source/runner.py
.
├── config/
│ ├── settings.yaml # Search parameters, budget rules
│ └── normalize_schema.json # Schema for normalized job fields
│
├── data/ # Auto-created locally (or synced from R2)
│ ├── raw/ # Daily raw JSON snapshots
│ ├── processed/ # Daily Parquet outputs
│ └── state/ # SQLite: run_state.sqlite + seen_jobs.sqlite
│
├── source/
│ ├── account.py # Fetch SerpApi quota + usage
│ ├── config_loader.py # YAML + env variable loader
│ ├── logger.py # Centralized logging
│ ├── normalize.py # Schema extraction + job_key generation
│ ├── policies.py # Request cap logic (daily + rollover)
│ ├── scraper.py # SerpApi fetcher with pagination
│ ├── seen_store.py # SQLite store for deduplication
│ ├── state_store.py # Track resets + carryover state
│ ├── storage.py # Save JSON/Parquet
│ ├── summary.py # Summary builder + Telegram formatter
│ ├── telegram_bot.py # Telegram notifications
│ ├── update_readme_stats.py # Update README "Daily Stats" section after each run
│ └── runner.py # Pipeline orchestrator
│
├── .github/
│ └── workflows/
│ └── daily.yml # GitHub Actions workflow
│
├── .env.example # Template for required env variables
├── requirements.txt
└── README.md
git clone https://github.com/imwaymaran/data-scientist-job-tracker.git
cd data-scientist-job-tracker
conda create -n jobtracker python=3.10
conda activate jobtracker
python -m pip install --upgrade pip
pip install -r requirements.txtCreate a .env file in the project root:
cp .env.example .envThen open .env and fill your credentials:
- SerpApi key
- Telegram bot token + chat ID (optional)
- Cloudflare R2 credentials (for GitHub Actions long-term storage)
- Access Key ID / Secret Access Key (S3-style credentials for R2)
All configuration lives in the config/ directory:
-
settings.yaml
Defines search parameters (query, location, filters), SerpApi settings, and daily request-budget rules. -
normalize_schema.json
Specifies the normalized field schema used when converting raw SerpApi data into clean, structured rows.
Secrets and credentials are provided through environment variables (see .env.example):
SERPAPI_KEY— SerpApi API keyTELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_ID— Telegram notifications (optional)CLOUDFLARE_ACCOUNT_ID,CLOUDFLARE_R2_BUCKET— R2 storage configurationCLOUDFLARE_R2_ACCESS_KEY_ID,CLOUDFLARE_R2_SECRET_ACCESS_KEY— S3-compatible credentials for syncing state and outputs
This project does not automatically read .env files.
If you want to run the pipeline locally, you must load environment variables into your shell.
Option A — Export directly in your shell:
export SERPAPI_KEY="your_key"
export TELEGRAM_BOT_TOKEN="your_token"
export TELEGRAM_CHAT_ID="your_chat_id"Option B — If you prefer using a .env file, install python-dotenv:
pip install python-dotenvAnd add this at the top of source/config_loader.py and source/telegram_bot.py:
from dotenv import load_dotenv
load_dotenv()python -m source.runner- Raw JSON:
data/raw/ - Normalized Parquet:
data/processed/ - State databases:
data/state/
A scheduled GitHub Actions workflow runs the pipeline in the cloud and keeps its state synchronized with Cloudflare R2:
- Syncs state databases from R2 →
data/state/ - Runs
python -m source.runneron a GitHub-hosted runner - Uploads updated state and daily outputs (
data/raw/,data/processed/) back to R2 - Sends a Telegram summary (and failure alerts, if enabled)
By default, the workflow is scheduled for 13:00 UTC every day, which is 8:00 or 9:00 AM in New York depending on daylight saving time.
The workflow can also be triggered manually through GitHub Actions.
Workflow file: .github/workflows/daily.yml
Below is a typical daily summary sent to Telegram after a successful run:
Job Tracker — Daily Run
Date: 2025-11-15
Cap: 12
Requests used: 12
Remaining after run: 203
Reason: limit_reached
Jobs scraped: 97
Normalized: 96
Uniques stored: 96
Total seen overall: 412
Carryover to tomorrow: 0
- Daily scraping pipeline
- Normalization & deduplication
- Persistent state tracking
- Cloudflare R2 integration
- GitHub Actions automation
- Telegram notifications
Next:
- Maintain pipeline and grow dataset
- Publish dataset on Kaggle
- Begin analysis (Part 2)
This project is licensed under the MIT License.
Feel free to use, modify, and distribute the code in accordance with the license terms.