From d039f2c00f7c23bdf0d712df75a64dc1e18b978e Mon Sep 17 00:00:00 2001 From: Oscar Date: Tue, 26 May 2026 15:31:45 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A8=20refactor(scraper):=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D0=BE=D0=B4=D1=8B=20askGPT=20=D0=BD=D0=B0=20askOpenCode,?= =?UTF-8?q?=20getResume=20=D0=BD=D0=B0=20listResumes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hh/scraper.ts | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/hh/scraper.ts b/src/hh/scraper.ts index 841e665..c9b65b4 100644 --- a/src/hh/scraper.ts +++ b/src/hh/scraper.ts @@ -2,7 +2,7 @@ import type { Message } from 'node-telegram-bot-api' import bot from '@bot' import prisma from '@prisma' import { type Browser, chromium, type Page } from 'playwright' -import { askGPT } from '../openai' +import { askOpenCode } from '../openai' // const SESSION_FILE = path.resolve('./session.json') @@ -33,7 +33,7 @@ async function randomScroll(page: Page) { } async function getBrowser(): Promise { - return chromium.launch({ headless: true }) + return chromium.launch({ headless: false }) } async function loadSession(page: Page, telegramId: bigint | number): Promise { @@ -139,8 +139,6 @@ export async function login( await bot.sendMessage(chatId, `cookies: ${cookies.length}`) await browser.close() - - await getResume(chatId) } export async function checkIsAuth(telegramId: bigint | number) { @@ -165,30 +163,44 @@ export async function checkIsAuth(telegramId: bigint | number) { // return await page.$('[data-qa="mainmenu_createResume"]') } -export async function getResume(chatId: number) { +export interface ResumeListItem { + title: string + href: string +} + +export async function listResumes(chatId: number): Promise { const browser = await getBrowser() const context = await browser.newContext() const page = await context.newPage() await loadSession(page, chatId) - const url = `https://hh.ru/applicant/resumes` - await page.goto(url, { waitUntil: 'networkidle' }) + await page.goto('https://hh.ru/applicant/resumes', { waitUntil: 'networkidle' }) - const text = await page - .$('[data-qa^="resume-card-link-"]') - .then(html => html?.getAttribute('href')) + const resumes = await page.$$eval( + '[data-qa^="resume-card-link-"]', + links => links.map(a => ({ + href: (a as HTMLAnchorElement).getAttribute('href') ?? '', + title: a.textContent?.trim() ?? '(без названия)', + })), + ) - const hhUrl = new URL(`https://hh.ru${text}`) + await browser.close() + return resumes +} - const id = hhUrl.pathname.split('/').pop()! +export async function saveResume(chatId: number, resumeHref: string): Promise { + const browser = await getBrowser() + const context = await browser.newContext() + const page = await context.newPage() + await loadSession(page, chatId) + const id = new URL(`https://hh.ru${resumeHref}`).pathname.split('/').pop()! const resumeUrl = `https://hh.ru/resume_converter/resume.txt?hash=${id}&type=txt&hhtmFrom=&hhtmSource=resume` await page.goto(resumeUrl, { waitUntil: 'networkidle' }) - let resume + let resume: string | undefined try { resume = await page.locator('.resume').innerText() - await prisma.resume.upsert({ where: { id }, create: { data: resume, id, telegramId: chatId }, @@ -263,14 +275,16 @@ export async function applyToJobs({ }) if (!resume?.data) { - await getResume(chatId) + results.errors.push(`${vacancy.title}: резюме не выбрано — выберите резюме через меню`) + continue } const user = await prisma.user.findUnique({ where: { telegramId: chatId }, }) - const letter = await askGPT(resume!.data, description, user!.prompt) + // const letter = await askGPT(resume!.data, description, user!.prompt) + const letter = await askOpenCode(resume!.data, description, user!.prompt) await bot.sendMessage(chatId, `✅ Сопроводительное письмо отправлено: ${letter}`)