|
3 | 3 | import warnings |
4 | 4 | from collections.abc import Iterable |
5 | 5 | from concurrent.futures import ThreadPoolExecutor |
| 6 | +from datetime import date |
6 | 7 | from pathlib import Path |
| 8 | +from typing import get_args |
7 | 9 |
|
8 | 10 | import pandas as pd |
9 | 11 | from pydantic import ValidationError |
|
27 | 29 | from jobspy.naukri import Naukri |
28 | 30 | from jobspy.util import ( |
29 | 31 | configure_logging, |
30 | | - desired_order, |
31 | | - extract_salary, |
32 | 32 | ) |
33 | 33 | from jobspy.ziprecruiter import ZipRecruiter |
34 | 34 |
|
|
44 | 44 | Site.HELLOWORK: HelloWork, |
45 | 45 | } |
46 | 46 |
|
| 47 | +column_types = { |
| 48 | + name: { |
| 49 | + str: "str", |
| 50 | + float: "float64", |
| 51 | + int: "int64", |
| 52 | + bool: "bool", |
| 53 | + date: "object", |
| 54 | + }.get((get_args(field.annotation) or (field.annotation,))[0], "str") |
| 55 | + for name, field in JobPost.model_fields.items() |
| 56 | +} |
| 57 | + |
47 | 58 |
|
48 | 59 | def scrape_jobs( |
49 | 60 | site_name: str | Site | Iterable[str | Site] | None = None, |
@@ -110,55 +121,19 @@ def scrape_site(site: Site) -> tuple[str, list[JobPost]]: |
110 | 121 | executor.map(scrape_site, dict.fromkeys(scraper_input.site_name)) |
111 | 122 | ) |
112 | 123 |
|
113 | | - rows = [] |
114 | | - for site, jobs in site_to_jobs_dict.items(): |
115 | | - for job in jobs: |
116 | | - job_data = job.model_dump() |
117 | | - job_data["site"] = site |
118 | | - job_data["company"] = job_data["company_name"] |
119 | | - job_data["job_type"] = ( |
120 | | - ", ".join(job_type.value for job_type in job_data["job_type"]) |
121 | | - if job_data["job_type"] |
122 | | - else None |
123 | | - ) |
124 | | - for name in ("emails", "skills"): |
125 | | - job_data[name] = ", ".join(job_data[name]) if job_data[name] else None |
126 | | - job_data["location"] = ( |
127 | | - job.location.display_location() if job.location else None |
128 | | - ) |
129 | | - |
130 | | - job_data |= dict.fromkeys( |
131 | | - ("interval", "min_amount", "max_amount", "currency", "salary_source") |
132 | | - ) |
133 | | - if job.compensation: |
134 | | - job_data |= job.compensation.model_dump(mode="json") |
135 | | - job_data["salary_source"] = "direct_data" |
136 | | - elif scraper_input.country_indeed == Country.USA: |
137 | | - ( |
138 | | - job_data["interval"], |
139 | | - job_data["min_amount"], |
140 | | - job_data["max_amount"], |
141 | | - job_data["currency"], |
142 | | - ) = extract_salary(job_data["description"]) |
143 | | - job_data["salary_source"] = "description" |
144 | | - if scraper_input.enforce_annual_salary and job_data["interval"] not in ( |
145 | | - None, |
146 | | - "yearly", |
147 | | - ): |
148 | | - factor = {"hourly": 2080, "daily": 260, "weekly": 52, "monthly": 12}[ |
149 | | - job_data["interval"] |
150 | | - ] |
151 | | - for name in ("min_amount", "max_amount"): |
152 | | - if job_data[name] is not None: |
153 | | - job_data[name] = round(job_data[name] * factor, 2) |
154 | | - job_data["interval"] = "yearly" |
155 | | - if not (job_data["min_amount"] or job_data["max_amount"]): |
156 | | - job_data["salary_source"] = None |
157 | | - rows.append(job_data) |
| 124 | + rows = [ |
| 125 | + (job.annualised() if scraper_input.enforce_annual_salary else job).model_copy( |
| 126 | + update={"site": site} |
| 127 | + ) |
| 128 | + for site, jobs in site_to_jobs_dict.items() |
| 129 | + for job in jobs |
| 130 | + ] |
158 | 131 |
|
| 132 | + jobs_df = pd.DataFrame( |
| 133 | + [job.model_dump() for job in rows], columns=list(JobPost.model_fields) |
| 134 | + ) |
159 | 135 | if not rows: |
160 | | - return pd.DataFrame() |
161 | | - jobs_df = pd.DataFrame(rows, columns=desired_order) |
| 136 | + return jobs_df.astype(column_types) |
162 | 137 | return jobs_df.sort_values( |
163 | 138 | by=["site", "date_posted"], ascending=[True, False] |
164 | 139 | ).reset_index(drop=True) |
0 commit comments