99 lines
2.8 KiB
TypeScript
99 lines
2.8 KiB
TypeScript
import { ElementHandle, Page } from "puppeteer-core";
|
|
import { randomDelay } from "../utils/browser";
|
|
import { Job } from "../types/job";
|
|
import { createDateFromRelativeTime } from "../utils/parse-time";
|
|
|
|
export class BrowserService {
|
|
private page: Page;
|
|
|
|
constructor(page: Page) {
|
|
this.page = page;
|
|
}
|
|
|
|
async getJobLinks(): Promise<string[]> {
|
|
const jobLinks = await this.page.evaluate(() => {
|
|
const links = document.querySelectorAll(
|
|
"a[href*='/viewjob/']"
|
|
) as NodeListOf<HTMLAnchorElement>;
|
|
return Array.from(links).map((link) => link.href);
|
|
});
|
|
|
|
return jobLinks;
|
|
}
|
|
|
|
async getJobData(jobLink: string): Promise<Job> {
|
|
let jobContainer: ElementHandle<Element> | null = null;
|
|
|
|
await this.page.goto(jobLink, {
|
|
waitUntil: "domcontentloaded",
|
|
timeout: 60000,
|
|
});
|
|
|
|
// await this.page.waitForNetworkIdle({ idleTime: 3000, timeout: 0 });
|
|
|
|
try {
|
|
jobContainer = await this.page.waitForSelector(
|
|
"::-p-xpath(/html/body/div[1]/div[3]/div/div/div/div[1])",
|
|
{
|
|
timeout: 10000,
|
|
}
|
|
);
|
|
} catch (error) {
|
|
console.log("No job listings found on the page");
|
|
return {} as Job;
|
|
}
|
|
|
|
if (jobContainer === null) {
|
|
console.log("Job container not found");
|
|
return {} as Job;
|
|
}
|
|
|
|
const jobTitle = await jobContainer.waitForSelector("h2");
|
|
const jobTitleText = await jobTitle?.evaluate((el) =>
|
|
el.textContent?.trim()
|
|
);
|
|
const company = await jobContainer.waitForSelector(
|
|
"span.text-xl.font-semibold.text-gray-700.flex-none"
|
|
);
|
|
const companyText = await company?.evaluate((el) => el.textContent?.trim());
|
|
const location = await jobContainer.waitForSelector(
|
|
"::-p-xpath(/html/body/div[1]/div[3]/div/div/div/div[1]/div/div[2]/div[3]/span)"
|
|
);
|
|
const locationText = await location?.evaluate((el) =>
|
|
el.textContent?.trim()
|
|
);
|
|
const jobPostTime = await jobContainer.waitForSelector(
|
|
"::-p-xpath(/html/body/div[1]/div[3]/div/div/div/div[1]/div/div[2]/div[1]/div/span)"
|
|
);
|
|
const jobPostTimeText = await jobPostTime?.evaluate((el) =>
|
|
el.textContent?.trim()
|
|
);
|
|
|
|
const time = jobPostTimeText?.split(" ")[1] ?? "";
|
|
|
|
const parsedDateTime = createDateFromRelativeTime(time);
|
|
|
|
const description = await this.page.waitForSelector("article");
|
|
|
|
const descriptionText = await description?.evaluate((el) =>
|
|
el.textContent?.trim()
|
|
);
|
|
|
|
const job: Job = {
|
|
company: companyText || "",
|
|
title: jobTitleText || "",
|
|
location: locationText || "",
|
|
description: descriptionText || "",
|
|
criteria: {},
|
|
jobLink,
|
|
companyLink: "",
|
|
provider: 7,
|
|
jobPostTime: parsedDateTime.toISOString(),
|
|
status: 0,
|
|
};
|
|
|
|
console.log({ job });
|
|
|
|
return job;
|
|
}
|
|
}
|