---
title: "Construa um Motor de Busca Semântica de Empregos com Bright Data, LanceDB e Cohere"
slug: semantic-job-search-engine-with-bright-data-lancedb-and-cohere
date: 2026-08-12T17:01:32+00:00
modified: 2026-09-06T16:11:41+00:00
permalink: https://brightdata.com.br/blog/ai/semantic-job-search-engine-with-bright-data-lancedb-and-cohere
type: blog
---

[ Blog ](https://brightdata.com.br/blog "Blog") / [AI](https://brightdata.com.br/blog/ai)







 [AI](https://brightdata.com.br/blog/ai)

# Construa um Motor de Busca Semântica de Empregos com Bright Data, LanceDB e Cohere

Construa um motor de busca semântica de empregos. O Web Scraper da Bright Data retorna vagas estruturadas do LinkedIn; o Cohere fornece embeddings para correspondência baseada em significado.

 31 min de leitura





 [ ![Satyam Tripathi](https://media.brightdata.com.br/2024/09/Satyam-Tripathi-50x50.png) ](https://brightdata.com/blog/authors/satyam-tripathi)

 [Satyam Tripathi

Technical Writer

 ](https://brightdata.com/blog/authors/satyam-tripathi)





 ![Build a Semantic Job Search Engine with Bright Data, LanceDB, and Cohere](https://media.brightdata.com.br/2026/08/Semantic-Job-Search-Engine-with-Bright-Data-LanceDB-and-Cohere.svg)





Quadros de empregos buscam apenas por palavras exatas, então a vaga certa fica oculta quando sua frase não corresponde ao anúncio. A busca semântica combina por significado. Construímos do início ao fim e medimos qual modo de busca vence, em vez de assumir que o mais complexo vence.

## TL;DR

Este guia cria um motor de busca semântica de empregos com 200 vagas reais do LinkedIn usando Bright Data (scraping), Cohere (embeddings + rerank) e LanceDB (armazenamento vetorial local).

- A busca por palavras-chave combina palavras exatas. A busca vetorial combina significado. Uma consulta como “engenheiro que trabalha com LLMs” encontra uma vaga de “Desenvolvedor GenAI” que a busca por palavras-chave não detecta.
- A API Web Scraper da Bright Data retorna vagas estruturadas do LinkedIn em JSON por US$0,0015 por registro, sem parsing de HTML ou manutenção de scraper.
- O LanceDB roda localmente e combina busca vetorial com filtros SQL (salário, senioridade) em 1 consulta, além de busca de texto completo e reranking do Cohere.
- Em 10 consultas de teste, a busca vetorial obteve 70% de precisão@3 contra 43% da busca por palavras-chave. Híbrido + rerank não adicionou ganho mensurável nessa escala, então abaixo de ~10k linhas, o vetor sozinho é um padrão razoável.
- O projeto completo tem 9 arquivos pequenos, incluindo um harness de avaliação, e o [código completo está no GitHub](https://github.com/triposat/semantic-job-search). A execução completa custa ~US$0,34.

## O problema com a busca por palavras-chave

A busca por palavras-chave em um quadro de empregos faz exatamente o que você pede. Retorna anúncios cujo título ou descrição contém os tokens literais da sua consulta. Pesquise *“engenheiro que trabalha com LLMs e engenharia de prompt”* e você perderá vagas como *“Desenvolvedor GenAI”*, mesmo quando são um encaixe perfeito. A busca lexical combina palavras exatas, não significado.

A [busca vetorial](/blog/ai/vector-databases) combina por significado. Cada descrição de vaga é convertida em um embedding (um vetor de alta dimensão que captura seu conteúdo semântico), assim como sua consulta. Uma vaga cujo vetor está próximo ao da sua consulta é uma boa correspondência em significado, mesmo quando não compartilha nenhuma das mesmas palavras.

Transformar isso em um motor de busca funcional requer 3 peças:

1. **Bright Data** faz o scraping de 200 vagas reais do LinkedIn em JSON estruturado e limpo.
2. **Cohere** transforma as descrições em embeddings e faz o reranking dos resultados finais.
3. **LanceDB** armazena os embeddings localmente e serve consultas híbridas (vetor + texto completo) com filtros no estilo SQL.

## A stack em resumo

O que cada camada faz e por que a usamos:

CamadaFerramentaPor que estaDados web**Bright Data** Web Scraper APIO Scraper pré-construído do LinkedIn retorna JSON estruturado com salário, senioridade e localização, sem parsing de HTML ou manutenção de scraper.Embeddings**Cohere** `embed-english-v3.0`Codificação assimétrica (diferentes tipos de entrada para documentos vs consultas). O Cohere também oferece `embed-v4.0`, que é multimodal. Usamos a v3 aqui pelo seu perfil de preço/latência somente em inglês (planeje re-embedar antes do fim da vida útil da v3).Reranker**Cohere** `rerank-v3.5`Fixamos a v3.5 pelo seu perfil de preço/latência. O Cohere também oferece `rerank-v4.0` (`-pro` para qualidade, `-fast` para latência).Armazenamento vetorial**LanceDB**Local, embutido, sem servidores. Suporta busca híbrida (vetor + BM25) e pré-filtros SQL.UI (opcional)**Streamlit**Interface web com código mínimo para um app Python de dados.Esta stack roda a partir de um único venv Python no seu laptop. Bright Data e Cohere são os únicos serviços gerenciados envolvidos.

## Configuração

O projeto completo e executável está no [GitHub](https://github.com/triposat/semantic-job-search). Clone-o e instale as dependências (Python 3.10 ou mais recente):

```none
git clone https://github.com/triposat/semantic-job-search.git
cd semantic-job-search
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
```

Copie o arquivo env de exemplo e adicione suas duas chaves de API, um [token da Bright Data](/cp/setting/users) e uma chave do Cohere em dashboard.cohere.com (uma chave de teste funciona para todo o guia):

```none
cp .env.example .env
# then edit .env with your keys:
#   BRIGHTDATA_API_TOKEN=...
#   COHERE_API_KEY=...
```

Com ambas as chaves configuradas, execute `python scrape.py` para obter os dados e `python index.py` para construir o índice.

## Arquitetura

O sistema tem dois fluxos, não um. **Ingestão** constrói o índice (execute uma vez ou em um agendamento). **Consulta** roda em cada busca. Ambos usam Cohere e LanceDB, mas para trabalhos diferentes.

![Diagrama de arquitetura de dois fluxos. INGESTÃO (executar uma vez ou em agendamento): uma seta 'keyword' entra na Bright Data ('Descobrir vagas por palavra-chave (async)'), que envia 'vagas (JSON)' para o Cohere ('embed (document)'), que envia 'vetores' para o LanceDB ('vetor + FTS + índices escalares, versionado'). CONSULTA (por busca, modo híbrido padrão): uma seta 'query' entra no Cohere ('embed (query)'), que envia um 'vetor de consulta' para o LanceDB ('busca vetor + FTS + pré-filtro SQL'), que envia 'candidatos' para o Cohere ('rerank'), que retorna 'resultados ordenados'. Cohere e LanceDB são destacados para mostrar que são reutilizados em ambos os fluxos, e o rerank roda apenas no modo híbrido.](https://media.brightdata.com/2026/08/Bk1rV5yffl.png)*Os dois fluxos lado a lado. A ingestão embeda documentos e os armazena. A consulta embeda o texto de busca, executa busca vetorial + texto completo com pré-filtro SQL e depois faz o reranking. Cohere e LanceDB aparecem em ambos os fluxos, mas fazem trabalhos diferentes em cada um, por isso o rerank nunca toca o caminho de ingestão.*

3 scripts executam o pipeline: `scrape.py`, `index.py`, `search.py`. Mais 6 helpers: `lib.py` (backend de busca compartilhado), `compare.py` (comparação de modos), `eval.py` (precisão@3), `stats.py` (resumo do dataset), `versions.py` (navegador de snapshots) e `app.py` (UI Streamlit).

## Fazer scraping do LinkedIn com Bright Data

O LinkedIn é uma fonte importante de dados de vagas, mas é difícil de fazer scraping de forma confiável: limites de taxa, markup dinâmico e HTML que muda sem aviso. A [Web Scraper API](/products/web-scraper) retorna JSON estruturado e limpo de endpoints pré-construídos, então você não precisa manter parsers.

### Escolha o endpoint correto

A Bright Data expõe vários scrapers do LinkedIn:

- **Perfis de pessoas** → perfis individuais de membros
- **Informações de empresas** → páginas de empresas
- **Vagas de emprego → Coletar por URL** → URLs específicas de vagas que você já tem
- **Vagas de emprego → [Descobrir por palavra-chave](/products/web-scraper/linkedin/jobs)** ← é o que queremos
- **Vagas de emprego → Descobrir por URL** → vagas de uma URL de resultados de busca
- **Posts do LinkedIn** e **Busca de pessoas** → outros tipos de entidade

`Descobrir por palavra-chave` é a opção certa porque queremos descoberta em massa de vagas a partir de uma consulta de busca. Uma única chamada de API retorna até 1.000 vagas estruturadas por palavra-chave, incluindo título, empresa, localização, nível de senioridade, tipo de emprego, faixa salarial quando listada e a descrição completa da vaga.

Cada scraper tem seu próprio `dataset_id`. Para encontrar um, abra a [Biblioteca de Scrapers](/cp/scrapers/browse) da Bright Data, pesquise pelo site (aqui, `linkedin.com`) e abra-o. Escolha o endpoint **Vagas de emprego → Descobrir por palavra-chave**, e seu `dataset_id` (`gd_lpfll7v5hcqtkxl6l`) e uma requisição pronta para uso aparecem no painel de exemplos de código. Um token válido é tudo que o `scrape.py` precisa para chamá-lo.

![Dashboard da Bright Data mostrando o menu da Biblioteca de Web Scrapers, com o endpoint de vagas do LinkedIn → 'Descobrir por palavra-chave' selecionado na barra lateral esquerda. O painel central mostra a aba Configuração com entradas de exemplo (paris/gerente de produto, Nova York/desenvolvedor python). O painel direito mostra a visualização de exemplos de código com uma requisição curl autenticada contendo `dataset_id=gd_lpfll7v5hcqtkxl6l`.](https://media.brightdata.com/2026/08/B1JSEqJGze.png)*A página do scraper ‘Descobrir por palavra-chave’. O painel de exemplos de código à direita é onde você encontrará o `dataset_id`.*

### Síncrono vs assíncrono

A Bright Data oferece 2 modos de entrega:

- **Síncrono** (`POST /datasets/v3/scrape`) retorna os dados inline, melhor para lotes pequenos.
- **Assíncrono** (`POST /datasets/v3/trigger`) retorna um ID de snapshot. Você verifica a conclusão e baixa o resultado, melhor para volumes maiores.

Em nossas execuções, o tempo de resposta médio foi de ~6 segundos **por entrada**. Para 2 palavras-chave com `limit_per_input=100` (200 vagas no total), uma chamada síncrona precisa manter a conexão aberta para todo o lote, o que arrisca timeout. Assíncrono é o padrão seguro.

### Controle custos com limites por entrada

O parâmetro de consulta `limit_per_input=N` limita quantos resultados cada busca de entrada retorna, que é exatamente o controle que você quer para gastos previsíveis:

```none
2 keywords × 100 jobs × $0.0015 = $0.30 per run
```

Aumente para execuções maiores, até 1.000 vagas por palavra-chave.

### O código

O scraper [aciona um snapshot](https://docs.brightdata.com/scraping-automation/web-data-apis/web-scraper-api/trigger-a-collection), verifica até estar pronto e baixa o JSON. O núcleo está abaixo (uma versão de produção adicionaria retry/backoff e tratamento de erros mais robusto):

```none
# scrape.py
import json, time, sys
from pathlib import Path
import requests
from lib import require_env

BD_TOKEN = require_env("BRIGHTDATA_API_TOKEN")
DATASET_ID = "gd_lpfll7v5hcqtkxl6l"  # LinkedIn jobs - discover by keyword
LIMIT_PER_INPUT = 100

SEARCHES = [
    {"location": "San Francisco", "keyword": "machine learning engineer",
     "country": "US", "time_range": "Past month", "job_type": "Full-time",
     "experience_level": "", "remote": "", "company": "", "location_radius": ""},
    {"location": "New York", "keyword": "python developer",
     "country": "US", "time_range": "Past month", "job_type": "Full-time",
     "experience_level": "", "remote": "", "company": "", "location_radius": ""},
]

API = "https://api.brightdata.com/datasets/v3"
HEADERS = {"Authorization": f"Bearer {BD_TOKEN}", "Content-Type": "application/json"}

def trigger_snapshot() -> str:
    r = requests.post(f"{API}/trigger", headers=HEADERS, json={"input": SEARCHES},
        params={"dataset_id": DATASET_ID, "type": "discover_new",
                "discover_by": "keyword", "include_errors": "true",
                "limit_per_input": str(LIMIT_PER_INPUT)})
    r.raise_for_status()
    return r.json()["snapshot_id"]

def wait_until_ready(snapshot_id: str) -> None:
    while True:
        status = requests.get(f"{API}/progress/{snapshot_id}", headers=HEADERS).json()["status"]
        if status == "ready": return
        if status == "failed": raise RuntimeError("snapshot failed")
        time.sleep(10)

def download(snapshot_id: str) -> list[dict]:
    return requests.get(f"{API}/snapshot/{snapshot_id}",
                        headers=HEADERS, params={"format": "json"}).json()
```

Executando:

```none
$ python scrape.py
→ scraping 2 keyword searches, max 100 jobs each
  estimated max cost: $0.30 (at $0.0015/record × 200 max records)
  triggered snapshot: sd_mojicp6g39xwbwqn2
  status: ready
✓ saved 204 jobs → data/raw_jobs.json
  actual cost: $0.31
```

### O que você recebe de volta

Cada vaga no JSON tem mais de 25 campos. Aqui estão os que importam:

```none
{
  "job_posting_id": "<id>",
  "job_title": "Associate Machine Learning Engineer",
  "company_name": "ExampleCo",
  "job_location": "San Francisco, CA",
  "job_seniority_level": "Entry level",
  "job_employment_type": "Full-time",
  "job_industries": "Software Development",
  "job_summary": "About ExampleCo. ExampleCo is the career network for the AI economy...",
  "base_salary": {
    "min_amount": 115000,
    "max_amount": 144000,
    "currency": "$",
    "payment_period": "yr"
  },
  "job_posted_date": "2026-04-25T03:41:21.072Z",
  "url": "https://www.linkedin.com/jobs/view/<id>"
}
```

O campo estruturado `base_salary` é o que torna possíveis as consultas com filtro de salário na próxima etapa.

## Indexar com Cohere e LanceDB

Temos 204 registros brutos de vagas, 4 deles são linhas de erro que filtramos no carregamento. Agora tornamos os 200 restantes pesquisáveis semanticamente.

### Por que Cohere

Escolhemos o Cohere em vez das alternativas (modelos de embedding da OpenAI, Voyage AI ou sentence-transformers locais):

1. **Codificação assimétrica.** O Cohere permite marcar a entrada como `search_document` ao indexar ou `search_query` ao buscar. O modelo codifica cada lado de forma diferente, o que funciona melhor do que tratar ambos da mesma forma.
2. **Embedding declarativo.** O registro do LanceDB suporta Cohere nativamente (assim como OpenAI e sentence-transformers), então o embedding acontece na inserção e na consulta sem chamadas manuais de `embed()`.
3. **A API de Rerank.** É um modelo separado que recebe uma consulta mais uma lista de candidatos e reordena os candidatos por relevância real. É o segundo estágio que pode afinar o ranking de um pipeline híbrido, e adicionamos esse estágio com uma única chamada `.rerank()`.

### O registro de embeddings do LanceDB

Os embeddings no LanceDB passam pelo seu registro de embeddings. Você declara seu esquema uma vez e os embeddings acontecem automaticamente em cada inserção e consulta, cada um com o `input_type` correto.

```none
# index.py
import lancedb
from lancedb.embeddings import get_registry
from lancedb.pydantic import LanceModel, Vector

cohere = get_registry().get("cohere").create(
    name="embed-english-v3.0",
    api_key=COHERE_API_KEY,
)

class Job(LanceModel):
    text: str = cohere.SourceField()              # ← o que embedar
    vector: Vector(cohere.ndims()) = cohere.VectorField()  # ← embedding armazenado
    job_id: str
    title: str
    company: str
    location: str
    country_code: str
    seniority: str
    employment_type: str
    job_function: str
    industry: str
    posted_date: str
    apply_url: str
    search_keyword: str
    salary_min_annual: float
    salary_max_annual: float
    salary_currency: str
    salary_display: str
    description_snippet: str
```

Tudo após `vector` é uma coluna armazenada simples, usada para filtragem e exibição.

### O truque da normalização de salários

A maioria das vagas tem salários cotados por ano, mas alguns são por hora. Para fazer `salary_min_annual >= 200000` funcionar de forma consistente, normalizamos na ingestão:

```none
HOURS_PER_YEAR = 2080

def _normalize_salary(base):
    if not base:
        return 0.0, 0.0, "", ""
    lo = float(base.get("min_amount") or 0)
    hi = float(base.get("max_amount") or 0)
    if (base.get("payment_period") or "").lower() == "hr":
        lo *= HOURS_PER_YEAR
        hi *= HOURS_PER_YEAR
    currency = base.get("currency") or ""
    display = f"{currency}{int(lo):,}–{currency}{int(hi):,}/yr" if (lo and hi) else ""
    return lo, hi, currency, display
```

Armazenamos tanto os valores numéricos brutos (para filtros) quanto uma string de exibição legível por humanos (para a UI).

### Atualizações incrementais com upserts

Na primeira vez que o `index.py` roda, ele cria a tabela. Cada execução subsequente é um **upsert** com chave em `job_id`:

```none
result = (
    table.merge_insert("job_id")
         .when_matched_update_all()       # atualiza vagas existentes
         .when_not_matched_insert_all()   # adiciona novas vagas descobertas
         .execute(rows)
)
print(f"inserted={result.num_inserted_rows}, updated={result.num_updated_rows}")
```

Novas vagas de um scraping fresco da Bright Data são inseridas, e vagas re-publicadas (mesmo `job_id`) têm seus salários, descrições e timestamps atualizados. Para remover completamente postagens obsoletas, encadeie `.when_not_matched_by_source_delete()`.

O upsert inteiro é uma única transação atômica. Como o Lance armazena dados de forma colunar com copy-on-write, a re-ingestão é uma escrita incremental em vez de uma reconstrução completa da tabela.

### Índices escalares para filtros SQL rápidos

Quando `search.py, where "salary_min_annual >= 200000"` roda, o LanceDB aplica o filtro *antes* do scan vetorial (`prefilter=True`). Com 200 linhas isso é instantâneo de qualquer forma. Com 200.000 linhas o filtro percorreria toda a coluna a menos que informemos ao LanceDB como indexá-la:

```none
table.create_scalar_index("salary_min_annual", index_type="BTREE",  replace=True)
table.create_scalar_index("seniority",         index_type="BITMAP", replace=True)
table.create_scalar_index("search_keyword",    index_type="BITMAP", replace=True)
table.create_scalar_index("employment_type",   index_type="BITMAP", replace=True)
```

2 tipos de índice cobrem o que precisamos:

- **BTREE** para colunas ordenáveis de maior cardinalidade. `salary_min_annual` se beneficia porque queremos consultas de intervalo (`>=`, `BETWEEN`).
- **BITMAP** para enums de baixa cardinalidade. `seniority` tem ~6 valores distintos, `employment_type` é quase todo `Full-time`, e `search_keyword` é uma das nossas 2 entradas de scraping. Cada valor distinto recebe seu próprio bitmap. Um filtro `=` se torna um único AND bit a bit.

Ambos rodam com `replace=True`, então re-executar o `index.py` os reconstrói de forma idempotente. Após a chamada, `table.list_indices()` reporta todos os 5 (os 4 escalares + o índice FTS):

```none
text_idx               type=FTS      columns=['text']
salary_min_annual_idx  type=BTree    columns=['salary_min_annual']
seniority_idx          type=Bitmap   columns=['seniority']
search_keyword_idx     type=Bitmap   columns=['search_keyword']
employment_type_idx    type=Bitmap   columns=['employment_type']
```

### Inspecione os dados indexados

Após executar `python index.py`, nosso script auxiliar `stats.py` resume o que está no banco de dados:

```none
$ python stats.py

📊 LanceDB · table 'jobs'  ·  200 rows

by source keyword
  machine learning engineer  ████████████████████ 100
  python developer           ████████████████████ 100

by seniority
  Mid-Senior level  ████████████████████ 99
  Entry level       ████████████ 62
  Not Applicable    ████ 20
  Internship        ██ 14
  Associate          4
  Director           1

salary coverage: 43/200 jobs (22%)
  min  $   65,000
  med  $  150,000
  max  $1,000,000

  highest-paying jobs:
    • Quantitative Developer (Python)                  Fintal Partners       $400,000–$1,000,000/yr
    • Machine Learning Engineer                        Mercor                $130,000–$500,000/yr
    • Data Scientist                                   Triumph               $200,000–$400,000/yr
    • Senior Python Developer (Middle Office Tech)     Quantitative Systems  $200,000–$400,000/yr
    • ML Engineer (Infra & Distributed training)       techire ai            $250,000–$400,000/yr

top hiring companies (top 10)
  Turing          ████████████████████ 7
  Handshake       █████████████████ 6
  OpenAI          █████████████████ 6
  Meta            █████████████████ 6
  Jack & Jill     ██████████████ 5
  DataAnnotation  ██████████████ 5
  Catalyst Labs   ███████████ 4
  Notion          ███████████ 4
  LangChain       ███████████ 4
  Uber            ████████ 3
```

## Executar busca híbrida com reranking

O LanceDB suporta 3 modos de busca, e nosso `lib.py` expõe os 3 por trás de uma única função:

```none
# lib.py
from lancedb.rerankers import CohereReranker

reranker = CohereReranker(model_name="rerank-v3.5")  # fixado; o modelo mais recente do Cohere é rerank-v4.0

def search(query: str, mode: str = "hybrid", limit: int = 10, where: str | None = None):
    table = _table()
    if mode == "vector":
        q = table.search(query, query_type="vector")
    elif mode == "keyword":
        q = table.search(query, query_type="fts")
    elif mode == "hybrid":
        q = table.search(query, query_type="hybrid").rerank(reranker=reranker)
    if where:
        q = q.where(where, prefilter=True)
    return q.limit(limit).to_pandas()
```

Três partes do `search()` merecem explicação:

- **`query_type="hybrid"`** combina similaridade vetorial e pontuações BM25 do índice de texto completo que construímos no momento da indexação (FTS nativo do LanceDB). A união de candidatos é então reordenada.
- **`.rerank(reranker)`** envia a lista de candidatos para a API de Rerank do Cohere e retorna sua ordenação. Passamos `model_name="rerank-v3.5"` explicitamente porque o padrão do LanceDB é mais antigo.
- **`prefilter=True`** aplica a cláusula SQL `WHERE` *antes* do scan vetorial, não depois. Isso é mais rápido (espaço de busca menor) e mais preciso (você não perde resultados por truncamento).

### Uma consulta real

Aqui estão os 2 primeiros resultados para uma consulta que não compartilha muitas palavras literais com nenhum título de vaga no dataset:

```none
$ python search.py "deep learning model training with GPUs"

  ▸ Training: ML Framework Engineer  ·  score 0.275
    OpenAI — San Francisco, CA
    Entry level · Full-time · 2026-04-22
    "About The Team Training Runtime designs the core distributed
     machine-learning training runtime that powers everything from early
     research experiments to frontier-scale model runs..."

  ▸ Machine Learning Engineer  ·  score 0.138
    Skild AI — San Mateo, CA
    Entry level · Full-time · 2026-04-15
    "Company Overview At Skild AI, we are building the world's first
     general purpose robotic intelligence that is robust and adapts to
     unseen scenarios without failing. We believe massive scale through
     data-driven machine learning..."
```

O título de nenhuma vaga contém “GPUs”, mas ambas as descrições são sobre treinamento de ML distribuído, que é o que a consulta está perguntando. A busca por palavras-chave pura provavelmente perderia ambas.

> Cada modo retorna um tipo diferente de pontuação. O modo vetorial retorna distância de cosseno (menor = mais próximo), híbrido + rerank retorna a pontuação de relevância do Cohere (0 a 1, maior = melhor) e o modo de palavras-chave retorna BM25 bruto (ilimitado, maior = mais sobreposição de palavras-chave). Os números não são comparáveis entre modos, apenas dentro de um único modo.

### Combine semântica com restrições rígidas

Similaridade semântica e filtros SQL se combinam em uma única consulta no LanceDB:

```none
$ python search.py "fintech python role with equity" \
    --where "salary_min_annual >= 250000"

  ▸ Quantitative Developer (Python)  ·  score 0.374
    Fintal Partners — New York, United States
    Mid-Senior level · Full-time · $400,000–$1,000,000/yr · 2026-04-22

  ▸ Senior Software Engineer (Python)  ·  score 0.272
    Fintal Partners — New York, NY
    Mid-Senior level · Full-time · $250,000–$400,000/yr · 2026-04-23
```

A parte vetorial combina a parte descritiva (“fintech python with equity”). O filtro SQL impõe a restrição numérica (`>= $250k`). Ambos os resultados são vagas da Fintal Partners na faixa salarial correta.

O mesmo padrão híbrido + filtro roda na UI do Streamlit, em um scraping posterior (as listagens ao vivo diferem da execução CLI acima):

![Interface de busca do Streamlit com a consulta 'fintech python role with equity' e o slider de Salário Mínimo na barra lateral arrastado para $250.000. O cabeçalho de resultados lê '3 results · mode: hybrid · filter: salary_min_annual >= 250000′. O cartão do primeiro resultado mostra Senior Software Engineer (Python) na Fintal Partners em Nova York, NY, com badges de Mid-Senior level, Full-time e um badge verde de salário mostrando $300.000,$500.000/yr, pontuação de relevância 0.272 e um trecho sobre uma empresa de negociação quantitativa. Um segundo resultado, Data Scientist na OpenArt AI em San Francisco, pontuação 0.165, começa abaixo.”/></figure>
<p class=](https://media.brightdata.com/2026/08/SyJrN5JzMx.png)*O app Streamlit executando uma busca híbrida. O badge de pontuação em cada cartão é a pontuação de relevância do Cohere, e o trecho abaixo dos badges mostra por que cada resultado entrou no top 3.*## Onde palavras-chave, vetor e híbrido discordam

O `compare.py` executa a mesma consulta nos 3 modos e imprime um relatório lado a lado:

```none
$ python compare.py "engineer working on LLMs and prompt engineering" --top 3

══════════════════════════════════════════════════════════════════════════
  query: engineer working on LLMs and prompt engineering
══════════════════════════════════════════════════════════════════════════

  ── keyword (BM25) ───────────────────────────────────────────────────────
  1. AI/ML Engineer                                          — Careerswift
  2. AI/ML Engineer                                          — Careerswift
  3. Applied AI Engineer                                     — Serval

  ── vector (Cohere) ──────────────────────────────────────────────────────
  1. Senior Software Engineer (Prompt Engineer Python/GenAI)        — Genpact
  2. 15+ Years exp/ Need f2f/ AI/ML Engineer or Python AI Engi...   — Jobs via Dice
  3. ML Engineer (Infra & Distributed training)                     — techire ai

  ── hybrid + rerank ──────────────────────────────────────────────────────
  1. Applied AI Engineer                                     — Serval
  2. Senior Software Engineer (Prompt Engineer Python/GenAI) — Genpact
  3. AI/ML Engineer                                          — Careerswift

  overlap: keyword∩vector=0/3 · hybrid∩vector=1/3 · hybrid∩keyword=2/3
```

Na linha de sobreposição, **palavras-chave e vetor encontraram 0 das mesmas vagas no top 3.** Eles pesquisam em espaços conceituais diferentes.

- Palavras-chave (BM25) encontra anúncios onde os tokens literais “LLMs” e “prompt” aparecem com mais frequência. Retorna títulos genéricos de IA/ML.
- Vetor (Cohere) encontra o anúncio *Senior Software Engineer (Prompt Engineer Python/GenAI)* em #1, mesmo que a consulta do usuário diga “prompt engineering” (gerúndio) e o título diga “Prompt Engineer” (substantivo). Também retorna uma listagem focada em LLM do Jobs via Dice que é uma forte correspondência semântica, mas lexicamente distante da consulta.
- Híbrido + rerank pega a união, desduplicata e passa pelo Cohere Rerank. A vaga *Applied AI Engineer* da Serval ($200k a $325k) sobe para #1. Sua descrição é densa em trabalho com engenharia de prompt e agentes LLM, mas nem seu título nem seus termos BM25 mais ponderados teriam classificado a vaga tão alto.

Para esta consulta específica, vetor e híbrido foram melhores que palavras-chave. A sobreposição de tokens brutos classificou os resultados da Genpact e da Serval abaixo de onde sua relevância semântica os colocaria. Mas uma única consulta é uma anedota, não evidência. Se esse padrão se mantém em geral é uma questão que apenas uma avaliação real pode responder.

## Medir qualidade com precisão@3

Para medir isso adequadamente, o `eval.py` pontua **10 consultas escritas manualmente** em todos os 3 modos e calcula a **precisão@3**, a fração dos 3 primeiros resultados que correspondem a um predicado de verdade fundamental transparente.

A verdade fundamental para cada consulta é um predicado Python, não um número mágico, então um leitor pode decidir se classificaria os resultados da mesma forma.

Para *“engenheiro de machine learning na OpenAI”*, um resultado conta como relevante apenas se seu campo `company` contém “OpenAI”. Para *“desenvolvedor quantitativo em empresa de trading”*, a regra é mais ampla. Um resultado conta se o título contém “Quant” ou “Trading”, ou a empresa é uma firma de trading conhecida (Fintal Partners, DRW, Hudson River Trading, Tower Research, Mondrian Alpha). Esses predicados são ajustados ao dataset de amostra, então suas pontuações mudarão com vagas novas. Ajuste-os aos seus próprios dados. A diferença entre os modos persiste mesmo quando as porcentagens exatas não.

Executando:

```none
$ python eval.py

precision@3 per query (hits/3)
────────────────────────────────────────────────────────────────────────
  query                                          keyword    vector     hybrid
────────────────────────────────────────────────────────────────────────
  machine learning engineer at OpenAI            1.00 (3/3)  1.00 (3/3)  1.00 (3/3)
  founding engineer at AI startup with equity    0.33 (1/3)  0.67 (2/3)  0.67 (2/3)
  prompt engineer working with LLMs              0.00 (0/3)  0.67 (2/3)  0.33 (1/3)
  quantitative developer at trading firm         0.67 (2/3)  1.00 (3/3)  1.00 (3/3)
  computer vision and robotics engineer          1.00 (3/3)  0.67 (2/3)  1.00 (3/3)
  data scientist role                            0.67 (2/3)  1.00 (3/3)  1.00 (3/3)
  distributed training infrastructure for ML     0.33 (1/3)  0.67 (2/3)  0.67 (2/3)
  backend engineer at AI company                 0.33 (1/3)  0.33 (1/3)  0.33 (1/3)
  python developer at fintech                    0.00 (0/3)  0.67 (2/3)  0.33 (1/3)
  high-paying machine learning role with equity  0.00 (0/3)  0.33 (1/3)  0.33 (1/3)
────────────────────────────────────────────────────────────────────────
  AVERAGE (10 queries)                           0.433       0.700       0.667
```

Os mesmos números em um gráfico:

![Gráfico de barras de precisão@3 em 10 consultas de teste: palavras-chave 43%, vetor 70%, híbrido + rerank 67%.](https://media.brightdata.com/2026/08/SkR4N9yMzg.png)*Precisão@3 em média nas 10 consultas de avaliação. Vetor pontua bem acima de palavras-chave, e híbrido está a poucos pontos do vetor.*

### O que os números dizem

Da tabela:

- **A busca vetorial pontuou bem acima da busca por palavras-chave** com **70% vs 43% de precisão@3 média.** Todas as 3 consultas onde palavras-chave pontuou 0 (“engenheiro de prompt”, “desenvolvedor python em fintech”, “ML de alto salário com equity”) tiveram pelo menos 1 resultado relevante no vetor.
- **Híbrido + rerank não superou o vetor nessa escala.** A diferença de 67% vs 70% está dentro do ruído: o reranker adiciona uma chamada ao Cohere por consulta, e a parte FTS alimenta ele com quase-acertos lexicais que ele então precisa filtrar de volta.
- **Nenhum modo é estritamente dominado.** “Visão computacional e robótica” é a única consulta onde palavras-chave (1,00) pontua acima do vetor (0,67), porque as empresas relevantes contêm todos os termos literais de robótica em suas descrições.

### Quando habilitar híbrido + rerank

Depende de alguns fatores:

- **Tamanho do pool de candidatos.** Com algumas centenas de linhas, o vetor sozinho geralmente é suficiente. A recuperação em 2 estágios do híbrido precisa de um pool maior (10k+) antes que a etapa de rerank valha seu custo.
- **Tipo de consulta.** Consultas com intenção semântica *e* palavras-chave distintivas (um nome de marca, uma tecnologia específica) se beneficiam do híbrido. Consultas puramente semânticas geralmente não.
- **Qualidade do reranker.** O rerank-v3.5 do Cohere teve bom desempenho em nossa avaliação. Se você trocar por um reranker diferente, re-execute o `eval.py` antes de confiar nele, pois um reranker mais fraco pode reordenar bons resultados vetoriais para baixo em um pool pequeno de candidatos.

Execute o `eval.py` em seus próprios dados para decidir. Adicionar uma consulta é uma string mais um predicado de verdade fundamental.

**Nota:** a avaliação híbrida roda bem com uma chave gratuita do Cohere. O limite de taxa do trial faz ela recuar e terminar em ~90s em vez de ~15s.

## Adicionar uma UI web com Streamlit

O Streamlit transforma o mesmo backend de busca em um app web clicável. O núcleo de busca e renderização está abaixo:

```none
# app.py
import streamlit as st
from lib import search

mode = st.sidebar.radio("Mode", ["hybrid", "vector", "keyword"])
seniority = st.sidebar.selectbox("Seniority", ["any", "Entry level", "Associate", "Mid-Senior level", "Director", "Internship", "Not Applicable"])
min_salary = st.sidebar.slider("Min salary ($/yr)", 0, 500_000, 0, step=10_000)

query = st.text_input("Search jobs", placeholder="e.g. remote ML engineer...")

if query:
    where_clauses = []
    if seniority != "any":
        where_clauses.append(f"seniority = '{seniority}'")
    if min_salary > 0:
        where_clauses.append(f"salary_min_annual >= {min_salary}")
    where = " AND ".join(where_clauses) or None

    df = search(query, mode=mode, where=where, limit=10)
    for _, row in df.iterrows():
        with st.container(border=True):
            st.markdown(f"### [{row['title']}]({row['apply_url']})")
            st.markdown(f"**{row['company']}** — {row['location']}")
            st.caption(row["description_snippet"] + "…")
```

Execute:

```none
streamlit run app.py
```

Você obtém uma página de busca completa em `localhost:8501` com uma caixa de busca, alternador de modo, filtros na barra lateral para senioridade, palavra-chave de origem e salário, além de cartões de resultado com badges, pontuações e prévias de trechos.

![Interface de busca do Streamlit para a consulta 'founding ML engineer at AI startup with computer vision' mostrando uma lista de resultados híbridos. A barra lateral contém filtros para modo de busca, senioridade, palavra-chave de busca de origem, salário mínimo e número de resultados. O cartão do primeiro resultado é 'Founding ML Engineer | Frontier Medical AI | $150k,$200k | SF' da CoffeeSpace na Área da Baía de San Francisco, com badges Mid-Senior level + Full-time, uma pontuação de relevância Cohere de 0.720 e um trecho de descrição. Um segundo resultado, 'AI/ML Engineer - AI Design Software Leader' com pontuação 0.711, começa abaixo.](https://media.brightdata.com/2026/08/Sk1S4c1Gzg.png)*O app Streamlit executando uma busca híbrida. O badge de pontuação em cada cartão é a pontuação de relevância do Cohere, e o trecho abaixo dos badges mostra por que cada resultado entrou no top 3.*

## Viagem no tempo gratuita com LanceDB

Isso cobre a busca e a UI. O LanceDB tem mais um recurso que vale mostrar. Cada escrita no LanceDB cria uma nova versão automaticamente, sem custo ou infraestrutura extra. É assim que o formato colunar Lance subjacente funciona. Para tornar uma versão fácil de encontrar mais tarde, o `index.py` a marca após cada ingestão:

```none
table.tags.create(f"ingest-{datetime.now():%Y-%m-%d-%H%M}", table.version)
```

Nosso script auxiliar `versions.py` permite navegar e abrir snapshots históricos. Após executar `python index.py` uma vez você verá 1 tag. Após uma segunda ingestão (digamos, re-scraping uma semana depois) você verá 2:

```none
$ python versions.py

📊 table 'jobs'  ·  current version: 13  ·  200 rows

🏷  tags (2):
  • ingest-2026-05-20-0905           → version 7
  • ingest-2026-05-20-0906           → version 13  ← current

  travel back with: `python versions.py --tag <name>`

$ python versions.py --tag ingest-2026-05-20-0905

📌 snapshot 'ingest-2026-05-20-0905'  ·  version 7  ·  200 rows
  • Associate Machine Learning Engineer  — Handshake
  • Machine Learning Engineer            — RZR
  • Machine Learning Engineer            — ChatGPT Jobs
```

A viagem no tempo é uma chamada `table.checkout(tag_or_version)`. Para um produto de busca de empregos, responde perguntas como *“quais vagas foram publicadas no trimestre passado?”* ou *“a distribuição de salários está mudando ao longo do tempo?”* sem um banco de dados de séries temporais separado. Essa é uma das razões pelas quais escolhemos o LanceDB aqui.

## Custo e escala

Para a demo (200 vagas, ~5 consultas de exemplo):

ItemCustoScraping Bright Data (204 registros @ US$0,0015/registro)**US$0,31**Embeddings Cohere (~228k tokens no total @ US$0,10/1M)~US$0,02Rerank Cohere (~US$0,002/consulta, Rerank v3.5 a US$2 / 1k buscas)~US$0,01 para 5 consultasLanceDB**gratuito**A demo completa custa **~US$0,34** no total. Esses preços são de uma execução de 2026, então verifique as tarifas atuais dos provedores.

### Escale

A demo local lida com 200 vagas. Algumas alavancas cobrem o caminho daqui até um dataset em escala de produção:

- **Mais vagas.** Altere `LIMIT_PER_INPUT` (máximo 1.000 por palavra-chave) ou adicione mais buscas por palavras-chave. 10.000 vagas custam ~US$15 em créditos da Bright Data.
- **Mais palavras-chave / localizações.** Adicione entradas à lista `SEARCHES` em `scrape.py`.
- **Atualização agendada.** O upsert `merge_insert` que construímos significa que re-executar o pipeline atualiza o que mudou. A Bright Data suporta coleta e entrega agendadas pelo dashboard. Combine isso com o upsert e você tem um dataset auto-atualizável.
- **Índice vetorial.** Acima de ~10k linhas, troque a busca por força bruta por um índice HNSW ou IVF\_PQ via `table.create_index(vector_column_name="vector")`. Ele constrói na CPU por padrão. Para uma construção em GPU, passe `accelerator="cuda"` (ou `"mps"` no Apple Silicon) com PyTorch&gt;2.0. A indexação automática em GPU é atualmente um recurso do LanceDB Enterprise.
- **Armazenamento vetorial de produção.** O LanceDB OSS escala para milhões de vetores em um único nó. Além de centenas de milhões de vetores ou terabytes de dados, o LanceDB Cloud e Enterprise adicionam indexação distribuída e execução de consultas (seus docs têm como alvo ~10 a 50B linhas / ~10 a 30 TB).

Antes de qualquer um desses movimentos de escala, porém, a própria demo tem arestas afiadas.

## 8 bugs e armadilhas que encontramos

Caso economize as horas que custaram para nós:

1. **`list_tables()` não retorna uma lista.** No LanceDB 0.30 ele retorna um objeto `ListTablesResponse` que *parece* iterável no REPL, mas `if TABLE in db.list_tables()` falha silenciosamente. Use `try: db.open_table(TABLE)` e capture a exceção, ou `.tables` na resposta.
2. **`table.checkout(tag)` retorna `None`** e muta o handle da tabela no lugar. Parece um bug, mas não é. Faça `t = db.open_table(...); t.checkout(tag); use(t)`, não `t = db.open_table(...).checkout(tag)`.
3. **O `CohereReranker()` padrão usa um modelo antigo** (`rerank-english-v3.0` nas versões que testamos). Passe um modelo explicitamente, seja `rerank-v3.5` (o que fixamos aqui) ou `rerank-v4.0-pro` para maior qualidade. O padrão não avisa.
4. **Use `/trigger` + polling, não `/scrape`, para lotes reais.** Síncrono (`/scrape`) é construído para pulls pequenos. Manter a conexão aberta para `limit_per_input=100` × 2 palavras-chave (~200 vagas) pode atingir timeout, então use `/trigger` + polling para qualquer coisa acima de ~50 registros.
5. **Alguns registros scraped são linhas de erro.** De 204 vagas, 4 tinham um campo `error` definido em vez de `job_title` (por exemplo, `"Crawl aborted on job cancel"`). Eles parecem superficialmente registros normais, então filtre-os no `index.py` ou o `merge_insert` falhará em um `job_id` vazio.
6. **Salários vêm em 2 períodos (`yr` e `hr`)**, mas o campo do esquema é o mesmo. Sem normalizar para anual (multiplicar por hora por 2080), um filtro como `salary_min_annual >= 200000` silenciosamente perde contratos por hora bem remunerados e inclui cargos assalariados com valores implausivamente baixos.
7. **Strings de ajuda do `argparse` com `%` bruto quebram no Python 3.14.** Escrever `--where "salary > 200000 AND location LIKE '%SF%'"` no seu texto de ajuda levanta `ValueError: badly formed help string` porque o argparse tenta formatar. Escape como `%%` ou reformule o exemplo.
8. **O Streamlit renderiza texto entre sinais de `$` como LaTeX matemático.** Um salário como `$150k,$200k` mostrado com `st.markdown` ou `st.caption` vira matemática distorcida. Escape cada `$` nas suas strings de exibição (o `app.py` do repositório faz isso com um `replace` de uma linha), ou os badges de salário renderizam como caracteres sem sentido.

## O que você pode construir a seguir

O padrão, *Bright Data ⟶ embeddings ⟶ banco de dados vetorial ⟶ busca híbrida*, generaliza para quase qualquer domínio:

DomínioProduto Bright DataO que você consultaria**Acesso web agêntico**[O MCP da Bright Data](/ai/mcp-server) (nível gratuito atualmente 5.000 requisições/mês)*“dar a um agente de IA ferramentas de busca + scraping ao vivo, depois fundamentar suas respostas em um cache respaldado pelo LanceDB de resultados passados”***Corpora de site completo**[Crawl API](/products/crawl-api)*“indexar um site de documentação inteiro ou base de conhecimento para recuperação híbrida”***E-commerce**Web Scraper API (produtos Amazon)*“tênis de corrida confortáveis por menos de $100 com 4+ estrelas”***Imóveis**Web Scraper API (Zillow / Redfin)*“casa familiar tranquila perto de boas escolas, 3+ quartos”***Inteligência de notícias**[SERP API](/products/serp-api) + [Web Unlocker](/products/web-unlocker)*“artigos sobre segurança de IA desta semana, classificados por relevância para alinhamento”***Prospecção de vendas**Informações de empresas do LinkedIn*“startups Series A em IA de saúde baseadas na Europa”***Restaurantes**Dataset do Yelp*“lugar italiano aconchegante com área externa”*Algumas extensões naturais deste projeto exato:

- **Busca multimodal.** Mude para o Cohere `embed-v4.0` (nativamente multimodal) e embede logos de empresas junto com descrições de vagas.
- **Filtros extraídos por LLM.** Deixe o usuário digitar *“vagas remotas de ML pagando $200k+”* e tenha um LLM extraindo `remote=true, salary_min_annual >= 200000` automaticamente.
- **Buscas salvas com alertas por e-mail.** Re-execute uma consulta contra o scraping mais recente e notifique sobre novas correspondências.
- **Correspondência de currículo.** Embede um currículo e busque vagas por similaridade com o candidato. O [assistente de IA para busca de empregos no LinkedIn](/blog/ai/linkedin-job-hunting-agent) da Bright Data é um exemplo mais completo.
- **Um scraper auto-mantido.** Dê a um agente acesso ao MCP da Bright Data e ele pode inspecionar a página, escrever o scraper e tentar uma correção quando o layout mudar, em vez de você corrigir o `scrape.py` manualmente. O [Scraper Studio](/products/web-scraper/studio) da Bright Data empacota isso como um produto gerenciado, transformando um prompt em linguagem simples em um scraper auto-reparável.

## Próximos passos

A busca por palavras-chave perdeu as vagas certas, e a busca vetorial as encontrou mesmo quando os títulos nunca corresponderam à consulta. Na avaliação, o vetor obteve 70% de precisão@3 contra 43% das palavras-chave, com o híbrido não adicionando ganho nessa escala.

O [projeto completo no GitHub](https://github.com/triposat/semantic-job-search) tem 9 arquivos pequenos. Para usá-lo nos seus próprios dados, execute `python eval.py` primeiro, porque o melhor modo depende dos dados, não de qual é mais complexo. Então decida uma cadência de atualização, onde os upserts do `merge_insert` atualizam apenas o que mudou e o `versions.py` faz snapshot de cada ingestão. E antes de qualquer coisa entrar em produção, planeje uma rotina de rotação de chaves, porque as chaves BD e Cohere vão no `.env`.

O mesmo padrão funciona para qualquer coisa que a [Bright Data](/) possa fazer scraping, não apenas vagas. A partir daí, você tem um motor de busca semântica que pode reutilizar para qualquer dataset que você scrape.

## FAQ

### Posso usar isso para outros sites além do LinkedIn?

Sim. A [Biblioteca de Web Scrapers](https://docs.brightdata.com/datasets/scrapers/scrapers-library/overview) da Bright Data cobre centenas de sites (Amazon, Zillow, Yelp e mais), cada um com seu próprio `dataset_id`. Troque o `DATASET_ID` em `scrape.py` e o mapeamento `to_row()` em `index.py` para o novo formato JSON. A lógica de busca e indexação é agnóstica a dados e continua funcionando.

### Preciso de uma conta paga do Cohere para isso?

Não, uma chave de teste executa toda a demo. O endpoint de Rerank do trial do Cohere é atualmente limitado a 10 chamadas/min, então o `eval.py` recebe um 429 e recua automaticamente (~90s em vez de ~15s). Scraping, indexação e busca ad-hoc ficam bem abaixo dos limites. Faça upgrade apenas se você iterar na avaliação com frequência.

### Por que LanceDB, não Pinecone, Weaviate ou pgvector?

LanceDB é uma biblioteca embutida sem servidor, sem banco de dados separado e sem conta de serviço gerenciado. Suporta busca híbrida e reranking do Cohere nativamente, e cada escrita é um snapshot de versão. Para um pipeline de máquina única sem ops, é a menor sobrecarga. Os outros são capazes, mas adicionam mais infraestrutura.

### Com que frequência devo re-executar o scraper?

Uma vez por dia é adequado para um quadro de empregos ativo. A Bright Data pode executar coleta agendada pelo dashboard, e o upsert `merge_insert` desduplicata no lado do LanceDB, então re-execuções são baratas. Anúncios com mais de ~30 dias geralmente estão fechados, então snapshots antigos se tornam históricos, e o `versions.py` os mantém consultáveis.



Contate-nosTeste grátis![google social icon](/wp-content/themes/brightdata/assets/images/ic_google.svg)









 Índice













 [ ](https://news.ycombinator.com/submitlink?t=Construa+um+Motor+de+Busca+Sem%C3%A2ntica+de+Empregos+com+Bright+Data%2C+LanceDB+e+Cohere&u=https://brightdata.com.br/blog/ai/semantic-job-search-engine-with-bright-data-lancedb-and-cohere) [ ](https://www.linkedin.com/shareArticle?mini=true&title=Construa+um+Motor+de+Busca+Sem%C3%A2ntica+de+Empregos+com+Bright+Data%2C+LanceDB+e+Cohere&url=https://brightdata.com.br/blog/ai/semantic-job-search-engine-with-bright-data-lancedb-and-cohere) [ ](http://www.reddit.com/submit?title=Construa+um+Motor+de+Busca+Sem%C3%A2ntica+de+Empregos+com+Bright+Data%2C+LanceDB+e+Cohere&url=https://brightdata.com.br/blog/ai/semantic-job-search-engine-with-bright-data-lancedb-and-cohere)







##  Você também pode estar interessado em

 [ ![OpenHuman with Bright Data](https://media.brightdata.com.br/2026/09/OpenHuman-with-Bright-Data.png) ](https://brightdata.com.br/blog/ai/openhuman-with-bright-data "Acesso à Web Pronto para Produção no OpenHuman Através do Bright Data CLI")

 [AI



 ![Antonello Zanini](https://media.brightdata.com.br/2022/12/Antonello-Zanini-2-50x50.jpg)

Antonello Zanini

Technical Writer





### Acesso à Web Pronto para Produção no OpenHuman Através do Bright Data CLI

Integre o Bright Data CLI ao OpenHuman para habilitar acesso à web pronto para produção e coleta de dados para agentes de IA.



 09-Sep-2026

 14 min de leitura

 ](https://brightdata.com.br/blog/ai/openhuman-with-bright-data)

 [ ![Multimodal Web Scraping with MiniMax](https://media.brightdata.com.br/2026/09/Multimodal-Web-Scraping-with-MiniMax.png) ](https://brightdata.com.br/blog/dados-do-site/multimodal-web-scraping-with-minimax "Scraping de Dados Multimodal com MiniMax")

 [Dados do site



 ![Antonello Zanini](https://media.brightdata.com.br/2022/12/Antonello-Zanini-2-50x50.jpg)

Antonello Zanini

Technical Writer





### Scraping de Dados Multimodal com MiniMax

Combine a API Web Unlocker da Bright Data com a visão MiniMax M3 para extrair dados estruturados de imagens e capturas de tela de páginas web.



 09-Sep-2026

 5 min de leitura

 ](https://brightdata.com.br/blog/dados-do-site/multimodal-web-scraping-with-minimax)

 [ ![The 10 Best CLI Tools for Codex in 2026](https://media.brightdata.com.br/2026/08/The-10-Best-CLI-Tools-for-Codex-in-2026.png) ](https://brightdata.com.br/blog/ai/best-cli-tools-for-codex "As 10 Melhores Ferramentas CLI para Codex em 2026 – Testadas & Classificadas")

 [AI



 ![Daniel Shashko](https://media.brightdata.com.br/2022/04/Daniel-Shashko-2-50x50.png)

Daniel Shashko

Web Data &amp; AI Expert





### As 10 Melhores Ferramentas CLI para Codex em 2026 – Testadas &amp; Classificadas

As 10 ferramentas CLI que tornam o Codex mais rápido e capaz, começando com o Bright Data CLI para acesso real à web de dentro do sandbox.



 06-Sep-2026

 24 min de leitura

 ](https://brightdata.com.br/blog/ai/best-cli-tools-for-codex)
