Spaces:
Configuration error
Configuration error
changed to run on HF directly
Browse files- main.py +68 -36
- requirements.txt +2 -1
- run.py +18 -58
main.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import requests
|
| 3 |
-
import pandas as pd
|
| 4 |
import streamlit as st
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
-
# ... your existing code displaying data tables ...
|
| 7 |
|
| 8 |
st.sidebar.header("βοΈ Live Scraper Controller")
|
| 9 |
st.sidebar.write("Trigger an on-demand cloud scraping run on GitHub Actions.")
|
|
@@ -22,36 +24,66 @@ if date_input == 'all':
|
|
| 22 |
#date_mapping = {"all": "None", "daily": "r86400", "weekly": "r604800", "monthly": "r2592000"}
|
| 23 |
|
| 24 |
# 3. The Activation Button
|
| 25 |
-
if st.sidebar.button("π
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
if response.status_code == 204:
|
| 55 |
-
st.sidebar.success("π GitHub Workflow started! Your fresh data will show up here in a few minutes.")
|
| 56 |
-
else:
|
| 57 |
-
st.sidebar.error(f"Failed to connect. Error {response.status_code}: {response.text}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
+
import logging
|
| 3 |
+
import sys
|
| 4 |
+
import threading
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
from run import job_scraper
|
| 7 |
+
from apscheduler.schedulers.background import BackgroundScheduler
|
| 8 |
|
|
|
|
| 9 |
|
| 10 |
st.sidebar.header("βοΈ Live Scraper Controller")
|
| 11 |
st.sidebar.write("Trigger an on-demand cloud scraping run on GitHub Actions.")
|
|
|
|
| 24 |
#date_mapping = {"all": "None", "daily": "r86400", "weekly": "r604800", "monthly": "r2592000"}
|
| 25 |
|
| 26 |
# 3. The Activation Button
|
| 27 |
+
if st.sidebar.button("π Run Scraper"):
|
| 28 |
+
|
| 29 |
+
# π Create a separate background thread so the UI doesn't freeze!
|
| 30 |
+
scraper_thread = threading.Thread(
|
| 31 |
+
target=job_scraper,
|
| 32 |
+
kwargs={
|
| 33 |
+
"job_title": job_input,
|
| 34 |
+
"target_location": loc_input,
|
| 35 |
+
"date_range": date_input,
|
| 36 |
+
"run_type": "manual"
|
| 37 |
+
}
|
| 38 |
+
)
|
| 39 |
+
# Start the background task
|
| 40 |
+
scraper_thread.start()
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# π 1. Configure the global root logger ONLY ONCE on application startup
|
| 44 |
+
if "logger_initialized" not in st.session_state: # Streamlit specific safety guard
|
| 45 |
+
root_logger = logging.getLogger()
|
| 46 |
+
root_logger.setLevel(logging.DEBUG)
|
| 47 |
+
|
| 48 |
+
# Mute sub-loggers
|
| 49 |
+
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
| 50 |
+
logging.getLogger("requests").setLevel(logging.WARNING)
|
| 51 |
+
|
| 52 |
+
# File Handler
|
| 53 |
+
import os
|
| 54 |
+
os.makedirs("logs", exist_ok=True)
|
| 55 |
+
timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
|
| 56 |
+
file_handler = logging.FileHandler(f"logs/app-{timestamp}.log")
|
| 57 |
+
file_handler.setLevel(logging.INFO)
|
| 58 |
+
file_formatter = logging.Formatter('%(asctime)s:%(name)s - [%(levelname)s]: %(message)s')
|
| 59 |
+
file_handler.setFormatter(file_formatter)
|
| 60 |
+
|
| 61 |
+
# Console Handler
|
| 62 |
+
console_handler = logging.StreamHandler(sys.stdout)
|
| 63 |
+
console_handler.setLevel(logging.DEBUG)
|
| 64 |
+
console_formatter = logging.Formatter('[%(levelname)s]: %(message)s')
|
| 65 |
+
console_handler.setFormatter(console_formatter)
|
| 66 |
+
|
| 67 |
+
# Attach both
|
| 68 |
+
root_logger.addHandler(file_handler)
|
| 69 |
+
root_logger.addHandler(console_handler)
|
| 70 |
|
| 71 |
+
st.session_state["logger_initialized"] = True
|
| 72 |
+
|
| 73 |
+
# π 2. Now initialize file-specific loggers anywhere using __name__
|
| 74 |
+
main_logger = logging.getLogger(__name__)
|
| 75 |
+
main_logger.info("Application UI successfully booted up!")
|
| 76 |
+
|
| 77 |
+
def daily_scraper():
|
| 78 |
+
# Injected values are passed directly into the function call here!
|
| 79 |
+
start_scraping_routine(
|
| 80 |
+
job_title="Data Analyst",
|
| 81 |
+
target_location="Kuala Lumpur",
|
| 82 |
+
date_range="daily",
|
| 83 |
+
run_type="scheduled"
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
scheduler = BackgroundScheduler()
|
| 87 |
+
# Runs every single night
|
| 88 |
+
scheduler.add_job(automated_job, 'cron', hour=16, minute=0)
|
| 89 |
+
scheduler.start()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -10,4 +10,5 @@ python-dotenv==1.0.1
|
|
| 10 |
streamlit
|
| 11 |
pandas
|
| 12 |
|
| 13 |
-
huggingface_hub
|
|
|
|
|
|
| 10 |
streamlit
|
| 11 |
pandas
|
| 12 |
|
| 13 |
+
huggingface_hub
|
| 14 |
+
apscheduler
|
run.py
CHANGED
|
@@ -9,57 +9,14 @@ from dotenv import load_dotenv
|
|
| 9 |
from ingestion.jobstreet.jobstreet_scraper import js_scraper, get_total_pages
|
| 10 |
from ingestion.linkedin.linkedin_scraper import ld_scraper
|
| 11 |
|
| 12 |
-
|
| 13 |
-
ROOT = Path(__file__).parent
|
| 14 |
-
load_dotenv(ROOT / ".env")
|
| 15 |
|
| 16 |
-
|
| 17 |
-
job_title = os.environ.get("JOB_TYPE", "Data Analyst")
|
| 18 |
-
target_location = os.environ.get("LOCATION", "Kuala Lumpur")
|
| 19 |
-
date_range = os.environ.get("DATE_RANGE", "None")
|
| 20 |
-
run_type = os.environ.get("RUN_TYPE")
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
date_range = None
|
| 25 |
-
|
| 26 |
-
# Logging
|
| 27 |
-
os.makedirs("logs", exist_ok=True)
|
| 28 |
-
timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
|
| 29 |
-
|
| 30 |
-
# 1. Initialize the master root logger
|
| 31 |
-
root_logger = logging.getLogger()
|
| 32 |
-
root_logger.setLevel(logging.DEBUG) # Master threshold level
|
| 33 |
-
|
| 34 |
-
# Mute the specific sub-loggers
|
| 35 |
-
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
| 36 |
-
logging.getLogger("requests").setLevel(logging.WARNING)
|
| 37 |
-
|
| 38 |
-
# 2. Create Handler #1: For writing to a file
|
| 39 |
-
timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
|
| 40 |
-
file_handler = logging.FileHandler(f"logs/app-{timestamp}.log")
|
| 41 |
-
file_handler.setLevel(logging.INFO)
|
| 42 |
-
file_formatter = logging.Formatter('%(asctime)s:%(name)s - [%(levelname)s]: %(message)s')
|
| 43 |
-
file_handler.setFormatter(file_formatter)
|
| 44 |
-
|
| 45 |
-
# 3. Create Handler #2: For streaming to terminal screen
|
| 46 |
-
console_handler = logging.StreamHandler(sys.stdout)
|
| 47 |
-
console_handler.setLevel(logging.DEBUG)
|
| 48 |
-
console_formatter = logging.Formatter('[%(levelname)s]: %(message)s')
|
| 49 |
-
console_handler.setFormatter(console_formatter)
|
| 50 |
-
|
| 51 |
-
# 4. Tie both handlers to the root logger
|
| 52 |
-
root_logger.addHandler(file_handler)
|
| 53 |
-
root_logger.addHandler(console_handler)
|
| 54 |
-
|
| 55 |
-
# initialize logger for current file (main.py)
|
| 56 |
-
main_logger = logging.getLogger(__name__)
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
if __name__ == "__main__":
|
| 61 |
|
| 62 |
-
|
| 63 |
# π Define all the jobs you want to track automatically every night!
|
| 64 |
DAILY_JOBS = [
|
| 65 |
"Data Analyst"
|
|
@@ -71,30 +28,33 @@ if __name__ == "__main__":
|
|
| 71 |
|
| 72 |
for job in DAILY_JOBS:
|
| 73 |
|
| 74 |
-
|
| 75 |
|
| 76 |
-
|
| 77 |
js_scraper(job_type = job, location = s_target_location, date_range = js_date_range)
|
| 78 |
|
| 79 |
-
|
| 80 |
ld_scraper(job_type = job, location = s_target_location, date_range = ld_date_range)
|
| 81 |
|
| 82 |
-
|
| 83 |
|
| 84 |
elif run_type == 'manual':
|
| 85 |
-
|
| 86 |
if date_range == 'daily':
|
| 87 |
ld_date_range = "r86400"
|
| 88 |
js_date_range = 1
|
| 89 |
|
| 90 |
-
|
| 91 |
-
|
| 92 |
js_scraper(job_type = job_title, location = target_location, date_range = js_date_range)
|
| 93 |
|
| 94 |
-
|
| 95 |
ld_scraper(job_type = job_title, location = target_location, date_range = ld_date_range)
|
| 96 |
|
| 97 |
-
|
| 98 |
|
| 99 |
else:
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
from ingestion.jobstreet.jobstreet_scraper import js_scraper, get_total_pages
|
| 10 |
from ingestion.linkedin.linkedin_scraper import ld_scraper
|
| 11 |
|
| 12 |
+
app_logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
| 13 |
|
| 14 |
+
def job_scraper(job_title="Data Analyst", target_location="Kuala Lumpur", date_range="daily", run_type="scheduled"):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
+
if date_range == "None" or date_range is None:
|
| 17 |
+
date_range = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
if run_type == 'scheduled':
|
| 20 |
# π Define all the jobs you want to track automatically every night!
|
| 21 |
DAILY_JOBS = [
|
| 22 |
"Data Analyst"
|
|
|
|
| 28 |
|
| 29 |
for job in DAILY_JOBS:
|
| 30 |
|
| 31 |
+
app_logger.info(f"β° Starting Scheduled web scraper run for {job} job listings.")
|
| 32 |
|
| 33 |
+
app_logger.info("π Scraping jobs from Jobstreet")
|
| 34 |
js_scraper(job_type = job, location = s_target_location, date_range = js_date_range)
|
| 35 |
|
| 36 |
+
app_logger.info("π Scraping jobs from Linkedin")
|
| 37 |
ld_scraper(job_type = job, location = s_target_location, date_range = ld_date_range)
|
| 38 |
|
| 39 |
+
app_logger.info("π All scraping tasks completed successfully.")
|
| 40 |
|
| 41 |
elif run_type == 'manual':
|
| 42 |
+
app_logger.info(date_range)
|
| 43 |
if date_range == 'daily':
|
| 44 |
ld_date_range = "r86400"
|
| 45 |
js_date_range = 1
|
| 46 |
|
| 47 |
+
app_logger.info(f"Starting web scraper run for {job_title} job listings.")
|
| 48 |
+
app_logger.info("π Scraping jobs from Jobstreet")
|
| 49 |
js_scraper(job_type = job_title, location = target_location, date_range = js_date_range)
|
| 50 |
|
| 51 |
+
app_logger.info("π Scraping jobs from Linkedin")
|
| 52 |
ld_scraper(job_type = job_title, location = target_location, date_range = ld_date_range)
|
| 53 |
|
| 54 |
+
app_logger.info("π All scraping tasks completed successfully.")
|
| 55 |
|
| 56 |
else:
|
| 57 |
+
app_logger.warning('π Undetermined run type task is unable to start!')
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
|