OppaAI commited on
Commit
bfad4de
·
verified ·
1 Parent(s): 51a4078

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +41 -70
app.py CHANGED
@@ -1,90 +1,59 @@
1
  import gradio as gr
2
- from bs4 import BeautifulSoup
3
- from urllib.parse import urlencode
4
- import cloudscraper
5
  from fastmcp import FastMCP
6
  import logging
 
7
 
8
  # Set up logging
9
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
10
  logger = logging.getLogger(__name__)
11
 
12
  # Initialize FastMCP server
13
- mcp = FastMCP("Canada Job Bank Scraper Agent")
14
 
15
  @mcp.tool(name="search_jobs")
16
  def search_jobs_tool(query: str, location: str, limit: int = 10, salary: str = None, job_type: str = None) -> dict:
17
  """
18
- Scrape job listings from the Canada Job Bank website using cloudscraper.
19
 
20
  Args:
21
- query (str): Job title or keyword to search for.
22
- location (str): Location to filter jobs (e.g., "Toronto, ON").
23
- limit (int): Maximum number of job results to return (default: 10).
24
- salary (str, optional): Ignored (for API compatibility).
25
- job_type (str, optional): Ignored (for API compatibility).
26
 
27
  Returns:
28
- dict: A dictionary with a list of job listings or an error message.
29
  """
30
- base_url = "https://www.jobbank.gc.ca/jobsearch/jobsearch"
31
  params = {
32
- "searchstring": query.strip(),
33
- "locationstring": location.strip(),
34
- "sort": "M", # Sort by most recent
35
- }
36
- url = base_url + "?" + urlencode(params, safe=",")
37
-
38
- headers = {
39
- "User-Agent": (
40
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
41
- "AppleWebKit/537.36 (KHTML, like Gecko) "
42
- "Chrome/126.0.0.0 Safari/537.36"
43
- ),
44
- "Accept-Language": "en-US,en;q=0.9",
45
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
46
- "Referer": "https://www.jobbank.gc.ca/",
47
- "Connection": "keep-alive",
48
  }
 
 
49
 
50
  try:
51
- logger.info(f"Attempting to scrape: {url}")
52
- scraper = cloudscraper.create_scraper()
53
- response = scraper.get(url, headers=headers, timeout=10)
54
  response.raise_for_status()
55
- soup = BeautifulSoup(response.text, "html.parser")
56
- cards = soup.find_all("article", class_="job-result") # Verify class name
57
-
58
- if not cards:
59
- logger.warning("No job cards found. The website may use JavaScript or the HTML structure may have changed.")
60
- logger.debug(f"HTML sample: {soup.prettify()[:2000]}") # Increased sample size for debugging
61
- return {"error": "No job listings found. The website may use JavaScript or the HTML structure may have changed."}
62
 
 
63
  jobs = []
64
- for card in cards[:limit]:
65
- title_elem = card.find("span", class_="job-title")
66
- company_elem = card.find("li", class_="employer")
67
- location_elem = card.find("li", class_="job-location")
68
- link_elem = card.find("a", href=True)
69
-
70
- link = link_elem.get("href") if link_elem else None
71
- if link and not link.startswith("http"):
72
- link = "https://www.jobbank.gc.ca" + link
73
-
74
- job = {
75
- "title": title_elem.get_text(strip=True) if title_elem else "No Title",
76
- "company": company_elem.get_text(strip=True).replace("Employer", "").strip() if company_elem else "Unknown Company",
77
- "location": location_elem.get_text(strip=True).replace("Location", "").strip() if location_elem else "Unknown Location",
78
- "url": link or "#"
79
- }
80
- jobs.append(job)
81
-
82
- logger.info(f"Found {len(jobs)} job listings.")
83
  return {"jobs": jobs}
84
 
85
- except cloudscraper.exceptions.CloudflareChallengeError as cf_err:
86
- logger.error(f"Cloudflare challenge error: {cf_err}")
87
- return {"error": f"Cloudflare challenge error: {cf_err}"}
88
  except requests.exceptions.HTTPError as http_err:
89
  logger.error(f"HTTP error: {http_err}")
90
  return {"error": f"HTTP error occurred: {http_err}"}
@@ -100,12 +69,12 @@ def search_jobs_tool(query: str, location: str, limit: int = 10, salary: str = N
100
 
101
  def search_jobs_ui(query, location, limit=10, salary=None, job_type=None):
102
  """
103
- Interface function for displaying job search results in markdown format.
104
  """
105
- if not query or not location:
106
- return "❌ Please provide both a job title/keyword and location."
107
 
108
- limit = int(max(1, min(limit, 50))) # Enforce reasonable limit
109
  result = search_jobs_tool(query, location, limit, salary, job_type)
110
 
111
  if "error" in result:
@@ -113,14 +82,16 @@ def search_jobs_ui(query, location, limit=10, salary=None, job_type=None):
113
 
114
  jobs = result.get("jobs", [])
115
  if not jobs:
116
- return "No jobs found for your search. Try different keywords or a broader location."
117
 
118
- output = "# Job Search Results\n\n"
119
  for i, job in enumerate(jobs, 1):
120
  output += (
121
  f"### {i}. {job['title']}\n"
122
  f"**Company**: {job['company']}\n"
123
  f"**Location**: {job['location']}\n"
 
 
124
  f"[Apply Here]({job['url']})\n\n"
125
  )
126
 
@@ -131,16 +102,16 @@ app = gr.Interface(
131
  fn=search_jobs_ui,
132
  inputs=[
133
  gr.Textbox(label="Job Title / Keyword", placeholder="e.g., Software Engineer"),
134
- gr.Textbox(label="Location", placeholder="e.g., Toronto, ON"),
135
  gr.Slider(minimum=1, maximum=50, value=10, step=1, label="Number of Results"),
136
  gr.Textbox(label="Salary (optional, ignored)", placeholder="Not used"),
137
  gr.Textbox(label="Job Type (optional, ignored)", placeholder="Not used")
138
  ],
139
  outputs=gr.Markdown(),
140
- title="Canada Job Bank Job Search",
141
- description="Search jobs by scraping Canada Job Bank using FastMCP and cloudscraper.",
142
  theme="huggingface"
143
  )
144
 
145
  if __name__ == "__main__":
146
- app.launch(mcp_server=True)
 
1
  import gradio as gr
 
 
 
2
  from fastmcp import FastMCP
3
  import logging
4
+ import requests
5
 
6
  # Set up logging
7
  logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
8
  logger = logging.getLogger(__name__)
9
 
10
  # Initialize FastMCP server
11
+ mcp = FastMCP("Jobicy Canada Remote Jobs Agent")
12
 
13
  @mcp.tool(name="search_jobs")
14
  def search_jobs_tool(query: str, location: str, limit: int = 10, salary: str = None, job_type: str = None) -> dict:
15
  """
16
+ Use Jobicy's Remote Jobs API to search remote jobs in Canada.
17
 
18
  Args:
19
+ query (str): Keyword or job title to search for.
20
+ location (str): Location filter (ignored since it's remote, but kept for API compatibility).
21
+ limit (int): Max number of jobs to return (max 50).
22
+ salary (str): Ignored (for compatibility).
23
+ job_type (str): Ignored (for compatibility).
24
 
25
  Returns:
26
+ dict: Contains 'jobs' list or 'error' message.
27
  """
28
+ base_url = "https://jobicy.com/api/v2/remote-jobs"
29
  params = {
30
+ "count": min(max(1, limit), 50), # ensure between 1 and 50
31
+ "geo": "canada",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
+ if query.strip():
34
+ params["tag"] = query.strip()
35
 
36
  try:
37
+ logger.info(f"Requesting Jobicy API with params: {params}")
38
+ response = requests.get(base_url, params=params, timeout=10)
 
39
  response.raise_for_status()
40
+ data = response.json()
 
 
 
 
 
 
41
 
42
+ jobs_raw = data.get("jobs", [])
43
  jobs = []
44
+ for job in jobs_raw:
45
+ jobs.append({
46
+ "title": job.get("jobTitle", "No Title"),
47
+ "company": job.get("companyName", "Unknown Company"),
48
+ "location": job.get("jobGeo", "Remote"),
49
+ "url": job.get("url", "#"),
50
+ "pubDate": job.get("pubDate", "Unknown Date"),
51
+ "salary": f"{job.get('salaryMin', '')} - {job.get('salaryMax', '')} {job.get('salaryCurrency', '')}".strip()
52
+ })
53
+
54
+ logger.info(f"Found {len(jobs)} jobs from Jobicy.")
 
 
 
 
 
 
 
 
55
  return {"jobs": jobs}
56
 
 
 
 
57
  except requests.exceptions.HTTPError as http_err:
58
  logger.error(f"HTTP error: {http_err}")
59
  return {"error": f"HTTP error occurred: {http_err}"}
 
69
 
70
  def search_jobs_ui(query, location, limit=10, salary=None, job_type=None):
71
  """
72
+ Gradio UI function for displaying job search results.
73
  """
74
+ if not query:
75
+ return "❌ Please provide a job title or keyword to search for."
76
 
77
+ limit = int(max(1, min(limit, 50)))
78
  result = search_jobs_tool(query, location, limit, salary, job_type)
79
 
80
  if "error" in result:
 
82
 
83
  jobs = result.get("jobs", [])
84
  if not jobs:
85
+ return "No jobs found for your search. Try different keywords."
86
 
87
+ output = "# Jobicy Canada Remote Job Search Results\n\n"
88
  for i, job in enumerate(jobs, 1):
89
  output += (
90
  f"### {i}. {job['title']}\n"
91
  f"**Company**: {job['company']}\n"
92
  f"**Location**: {job['location']}\n"
93
+ f"**Salary**: {job['salary']}\n"
94
+ f"**Posted On**: {job['pubDate']}\n"
95
  f"[Apply Here]({job['url']})\n\n"
96
  )
97
 
 
102
  fn=search_jobs_ui,
103
  inputs=[
104
  gr.Textbox(label="Job Title / Keyword", placeholder="e.g., Software Engineer"),
105
+ gr.Textbox(label="Location (ignored)", placeholder="Any, e.g., Toronto, ON"),
106
  gr.Slider(minimum=1, maximum=50, value=10, step=1, label="Number of Results"),
107
  gr.Textbox(label="Salary (optional, ignored)", placeholder="Not used"),
108
  gr.Textbox(label="Job Type (optional, ignored)", placeholder="Not used")
109
  ],
110
  outputs=gr.Markdown(),
111
+ title="Jobicy Canada Remote Job Search",
112
+ description="Search remote jobs in Canada using Jobicy API with FastMCP and Gradio.",
113
  theme="huggingface"
114
  )
115
 
116
  if __name__ == "__main__":
117
+ app.launch(mcp_server=True)