Skip to content

Commit 3aa3bdc

Browse files
core: Melhorias gerais
1 parent 6528cf5 commit 3aa3bdc

11 files changed

Lines changed: 102 additions & 141 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## [0.1.0](https://github.com/henriquesebastiao/vagas-dev/releases/0.1.0) - 2026-03-04
2+
3+
### Core
4+
5+
- Refatorando código e melhorias na busca de vagas
6+
7+
18
## [0.1.0-beta.5](https://github.com/henriquesebastiao/vagas-dev/releases/0.1.0-beta.5) - 2026-03-04
29

310
### Funcionalidade

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Um sistema de busca de vagas de emprego para desenvolvedores e notificações vi
88

99
Esse sistema é composto por uma API REST que busca por novas vagas de trabalho periódicamente em fontes como [Gupy](https://portal.gupy.io/) e [LinkedIn](https://www.linkedin.com) em background e notifica sobre as novas vagas encontradas via apps de mensagens como Telegram e Discord.
1010

11-
Os links de acesso ao sistema estão listado abaixo:
11+
Os links de acesso ao sistema estão listados abaixo:
1212

1313
- [Documentação](https://henriquesebastiao.github.io/vagas-dev/)
1414
- [Interface Web](https://vagas.henriquesebastiao.com/)
@@ -20,7 +20,7 @@ Os links de acesso ao sistema estão listado abaixo:
2020
2121
## 🔔 Notificações
2222

23-
Para receber alertas das vagas encontradas pelo sistema faça parte do grupo de alertas no seu app mensageiro de preferência entre os listados abaixo:
23+
Para receber alerta das vagas encontradas pelo sistema faça parte do grupo de alerta no seu app mensageiro de preferência entre os listados abaixo:
2424

2525
> O link de cada um é o link de convite para o grupo ou servidor.
2626
@@ -30,7 +30,7 @@ Para receber alertas das vagas encontradas pelo sistema faça parte do grupo de
3030
## Funcionalidades
3131

3232
- **Busca periódica automática** — scheduler configurável que roda em background e coleta novas vagas em intervalos definidos, sem intervenção manual
33-
- **Notificações** — integração com Telegram e Discord para enviar alertas de novas vagas que correspondam a palavras-chave específicas
33+
- **Notificações** — integração com Telegram e Discord para enviar alerta de novas vagas que correspondam a palavras-chave específicas
3434
- **Sem duplicação de vagas** — vagas já registradas são ignoradas automaticamente, garantindo que o banco nunca acumule duplicatas, independente de quantas vezes o scheduler seja executado
3535
- **API REST centralizada** — todas as vagas de todas as fontes acessíveis por um único endpoint, com filtros por palavra-chave, localização, tipo de trabalho e fonte
3636
- **Filtros disponíveis** - busque vagas podendo filtrar pelas seguintes características:
@@ -42,13 +42,13 @@ Para receber alertas das vagas encontradas pelo sistema faça parte do grupo de
4242
- **Paginação** — controle o número de resultados retornados e a partir de qual posição começar (limit/offset)
4343
- **Trigger manual de sync** — é possível disparar uma sincronização sob demanda via endpoint, sem precisar esperar o próximo ciclo do scheduler
4444
- **Arquitetura extensível** — adicionar uma nova fonte de vagas exige apenas criar um novo scraper herdando da classe base; toda a lógica de persistência e deduplicação já está pronta
45-
- **Interface Web** - interface web desenvolvida com [Next.js](https://nextjs.org/) para visualização e busca das vagas encontradas pelo sistema.
45+
- **Interface Web** - interface web desenvolvida com [Next.js](https://nextjs.org/) para visualização e busca das vagas encontradas pelo sistema.
4646

4747
## Fontes Suportadas
4848

49-
| Fonte | Método | Status |
50-
|-------|--------|--------------|
51-
| Gupy | API | ✅ Disponível |
49+
| Fonte | Método | Status |
50+
|----------|---------|--------------|
51+
| Gupy | API | ✅ Disponível |
5252
| LinkedIn | Scraper | ✅ Disponível |
5353

5454
> Novas fontes podem ser adicionadas criando um scraper que implementa `BaseJobScraper`.

backend/app/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.1.0-beta.5'
1+
__version__ = '0.1.0'

backend/app/keywords.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
11
KEYWORDS = [
22
'python',
3-
'fastapi',
43
'django',
5-
'flask',
6-
'sqlalchemy',
7-
'docker',
8-
'kubernetes',
94
'java',
10-
'spring',
115
'golang',
126
'javascript',
13-
'react',
14-
'angular',
15-
'vue',
167
'frontend',
178
'backend',
189
]

backend/app/scrapers/gupy.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@
66

77
from app.scrapers import transport
88
from app.scrapers.base import BaseJobScraper
9-
from app.utils import (
10-
add_time,
11-
after_request,
12-
before_request,
13-
get_level_seniority,
14-
)
9+
from app.utils import get_level_seniority
1510

1611
logger = logging.getLogger(__name__)
1712

@@ -48,10 +43,6 @@ async def fetch_jobs(self) -> list[dict]:
4843
base_url=self.BASE_URL,
4944
transport=transport,
5045
timeout=10,
51-
event_hooks={
52-
'request': [before_request, add_time],
53-
'response': [after_request],
54-
},
5546
) as client:
5647
for keyword in self.keywords:
5748
offset = 0

backend/app/scrapers/linkedin.py

Lines changed: 83 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import hashlib
23
import logging
34
from http import HTTPStatus
@@ -7,11 +8,6 @@
78

89
from app.scrapers import transport
910
from app.scrapers.base import BaseJobScraper
10-
from app.utils import (
11-
add_time,
12-
after_request,
13-
before_request,
14-
)
1511

1612
logger = logging.getLogger(__name__)
1713

@@ -23,8 +19,8 @@
2319

2420
linkedin_workplace_type_id = {
2521
'remote': 2,
26-
'hybrid': 3,
27-
'on-site': 1,
22+
# 'hybrid': 3,
23+
# 'on-site': 1,
2824
}
2925

3026

@@ -46,85 +42,93 @@ async def fetch_jobs(self) -> list[dict]:
4642
base_url=self.BASE_URL,
4743
transport=transport,
4844
timeout=30,
49-
event_hooks={
50-
'request': [before_request, add_time],
51-
'response': [after_request],
52-
},
5345
) as client:
5446
for keyword in self.keywords:
5547
for level, level_id in linkedin_level_id.items():
5648
for (
5749
workplace_type,
5850
workplace_type_id,
5951
) in linkedin_workplace_type_id.items():
60-
params = {
61-
'keywords': f'"{keyword}"',
62-
'f_E': level_id,
63-
'f_WT': workplace_type_id,
64-
'location': 'Brasil',
65-
'geoId': '106057199',
66-
}
67-
68-
try:
69-
response = await client.get(
70-
'/jobs/search', params=params
71-
)
72-
except httpx.ReadTimeout:
73-
logger.warning(
74-
'Timeout ao buscar vagas para keyword '
75-
f'"{keyword}". Pulando para o próximo keyword.'
76-
)
77-
continue
78-
79-
if response.status_code == HTTPStatus.OK:
80-
bs = BeautifulSoup(response.text, 'html.parser')
81-
82-
# Busca pelos links e títulos das vagas.
83-
jobs_list = bs.find_all(
84-
'a', {'class': 'base-card__full-link'}
85-
)
86-
87-
if len(jobs_list) == 0:
88-
continue
89-
90-
jobs = []
91-
92-
for job in jobs_list:
93-
job_data = {
94-
'title': job.get_text(strip=True),
95-
'url': job['href'],
96-
}
97-
jobs.append(job_data)
98-
99-
for job in jobs:
100-
external_id = hashlib.md5(
101-
job['title'].encode()
102-
).hexdigest()[:10]
103-
content = {
104-
'external_id': external_id,
105-
'keyword': keyword,
106-
'title': job['title'],
107-
'description': None,
108-
'company': None,
109-
'location': 'Brasil',
110-
'url': job['url'],
111-
'workplace_type': workplace_type,
112-
'published_at': None,
113-
'end_applications': None,
114-
'for_pcd': False,
115-
'level': level,
116-
}
117-
118-
all_jobs.append(self._parse(content))
119-
else:
120-
logger.warning(
121-
'Erro ao buscar vagas '
122-
f'para keyword "{keyword}". '
123-
f'Status code: {response.status_code}. '
124-
f'Resposta: {response.text[:200]}... '
125-
'Pulando para o próximo keyword.'
126-
)
127-
continue
52+
while True:
53+
params = {
54+
'keywords': f'"{keyword}"',
55+
'f_E': level_id,
56+
'f_WT': workplace_type_id,
57+
'location': 'Brasil',
58+
'geoId': '106057199',
59+
}
60+
61+
try:
62+
response = await client.get(
63+
'/jobs/search', params=params
64+
)
65+
except httpx.ReadTimeout:
66+
logger.warning(
67+
'Timeout ao buscar vagas para keyword '
68+
f'"{keyword}". Pulando para o próximo keyword.'
69+
)
70+
break
71+
72+
if response.status_code == HTTPStatus.OK:
73+
bs = BeautifulSoup(
74+
response.text, 'html.parser'
75+
)
76+
77+
# Busca pelos links e títulos das vagas.
78+
jobs_list = bs.find_all(
79+
'a', {'class': 'base-card__full-link'}
80+
)
81+
82+
if len(jobs_list) == 0:
83+
break
84+
85+
jobs = []
86+
87+
for job in jobs_list:
88+
job_data = {
89+
'title': job.get_text(strip=True),
90+
'url': job['href'],
91+
}
92+
jobs.append(job_data)
93+
94+
for job in jobs:
95+
external_id = hashlib.md5(
96+
job['title'].encode()
97+
).hexdigest()[:10]
98+
content = {
99+
'external_id': external_id,
100+
'keyword': keyword,
101+
'title': job['title'],
102+
'description': None,
103+
'company': None,
104+
'location': 'Brasil',
105+
'url': job['url'],
106+
'workplace_type': workplace_type,
107+
'published_at': None,
108+
'end_applications': None,
109+
'for_pcd': False,
110+
'level': level,
111+
}
112+
113+
all_jobs.append(self._parse(content))
114+
break
115+
elif response.status_code == 999: # noqa
116+
logger.warning(
117+
'Status 999 recebido, tentando novamente...'
118+
)
119+
await asyncio.sleep(
120+
2
121+
) # Aguarda antes de tentar novamente
122+
continue # Repete o while com os mesmos valores
123+
else:
124+
logger.warning(
125+
'Erro ao buscar vagas '
126+
f'para keyword "{keyword}". '
127+
f'Status code: {response.status_code}. '
128+
f'Resposta: {response.text[:200]}... '
129+
'Pulando para o próximo keyword.'
130+
)
131+
break
128132
return all_jobs
129133

130134
@staticmethod

backend/app/utils.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import logging
2-
import time
3-
import uuid
42
from typing import Annotated
53

6-
import httpx
74
from fastapi import Depends
85
from sqlalchemy.ext.asyncio import AsyncSession
96

@@ -42,27 +39,3 @@ def get_level_seniority(title: str) -> str | None:
4239
return JobLevel.senior
4340
else:
4441
return None
45-
46-
47-
async def before_request(request: httpx.Request):
48-
request_id = str(uuid.uuid4())
49-
request.headers['X-Request-ID'] = request_id
50-
request.extensions['request_id'] = request_id
51-
52-
53-
async def add_time(request: httpx.Request):
54-
request.extensions['start_time'] = time.monotonic()
55-
56-
57-
async def after_request(response: httpx.Response):
58-
request = response.request
59-
start = response.request.extensions.get('start_time', None)
60-
if start:
61-
elapsed = time.monotonic() - start
62-
else:
63-
elapsed = None
64-
65-
_logger.info(
66-
f'{request.method} {request.url} {response.status_code}'
67-
f'{elapsed} {request.extensions.get("request_id")}'
68-
)

backend/app/wrappers/telegram.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
PYTHON_KEYWORDS,
1717
)
1818
from app.models import Job
19-
from app.utils import add_time, after_request, before_request
2019

2120
logger = logging.getLogger(__name__)
2221

@@ -78,10 +77,6 @@ async def send_notification_jobs(
7877
base_url=f'https://api.telegram.org/bot{self._token}',
7978
transport=limiter_transport,
8079
timeout=10,
81-
event_hooks={
82-
'request': [before_request, add_time],
83-
'response': [after_request],
84-
},
8580
) as client:
8681
settings = get_settings()
8782

frontend/components/Header.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default function Header({ sources }) {
3737
width: 34,
3838
height: 34,
3939
borderRadius: 10,
40-
background: "linear-gradient(135deg, #6366f1, #8b5cf6)",
40+
background: "linear-gradient(135deg, #6366f1, #0CC0DF)",
4141
display: "flex",
4242
alignItems: "center",
4343
justifyContent: "center",

frontend/components/JobDetail.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export default function JobDetail({ job }) {
118118
target="_blank"
119119
rel="noopener noreferrer"
120120
style={{
121-
background: "linear-gradient(135deg, #6366f1, #8b5cf6)",
121+
background: "linear-gradient(135deg, #6366f1, #0CC0DF)",
122122
color: "#fff",
123123
padding: "10px 20px",
124124
borderRadius: 10,

0 commit comments

Comments
 (0)