Ir para o conteúdo

Busca

Pergunte para a web e receba resultados ranqueados. Eles são lidos na hora de um buscador de verdade, por uma saída residencial, nunca do cache de um revendedor que você não pode redistribuir.

Uma chamada

POST /v1/search
curl -X POST https://api.scrapa.app/v1/search \
  -H "authorization: Bearer sc_live_your_key" \
  -H "content-type: application/json" \
  -d '{"query": "quanto custa abrir um MEI", "proxy": {"country": "br"}}'
A resposta
{
  "data": {
    "query": "quanto custa abrir um MEI",
    "topic": "general",
    "engine": "brave",
    "pool": "residential",
    "bytes": 311402,
    "cached": false,
    "answer": null,
    "results": [
      {
        "position": 1,
        "url": "https://www.gov.br/empresas-e-negocios/pt-br/empreendedor",
        "title": "Portal do Empreendedor",
        "snippet": "A formalização como MEI é gratuita. O empreendedor paga apenas o DAS…",
        "publishedAt": null,
        "image": null
      }
    ]
  },
  "meta": { "cost": 4, "creditsRemaining": 983, "requestId": "0f3c…" }
}

Cada resultado traz a posição, a URL, o título e o trecho que o índice guarda. Não existe nota de relevância, porque nenhum buscador publica uma. Um número aqui seria inventado, e você construiria em cima dele.

A ordem é o ranking: results[0] é o que o buscador colocou em primeiro. Leia como lista, não como saco.

A web de qual país

O país da saída também escolhe o mercado: uma saída brasileira devolve o que alguém no Brasil vê, em português, com os sites brasileiros na posição em que o Brasil os coloca. É `us` se você não disser outra coisa. As mesmas duas letras que escolhem a saída de um scrape escolhem aqui.

Notícias, e as únicas datas em que confiamos

topic "news"
{
  "query": "eleições",
  "topic": "news",
  "timeRange": "week",
  "proxy": { "country": "br" }
}

Peça news e os resultados vêm de um índice de notícias: jornalismo datado, mais recente primeiro, com `timeRange` para fechar no último dia, semana ou mês. É o tópico que preenche `publishedAt` com data de verdade. Na web geral ele costuma vir null, e null é a resposta honesta. Nunca chutamos a data de uma página.

Imagens

topic "images"
{
  "query": "bolo de cenoura",
  "topic": "images",
  "proxy": { "country": "br" }
}
Um resultado
{
  "position": 1,
  "url": "https://receitas.example.com/bolo-de-cenoura",
  "title": "Bolo de cenoura com cobertura de chocolate",
  "snippet": "",
  "publishedAt": null,
  "image": {
    "url": "https://cdn.example.com/fotos/bolo-de-cenoura.jpg",
    "thumbnailUrl": "https://ts2.mm.bing.net/th?id=OIP.example&pid=15.1",
    "width": 1272,
    "height": 764
  }
}

Peça images e cada resultado é uma página que mostra uma imagem. A `url` é essa página. A imagem fica em `image`: o endereço dela, uma cópia pequena que sempre carrega e o tamanho em pixels. O endereço da imagem pode ter sumido ou recusar o download, então guarde a cópia pequena como reserva.

Não sabemos de quem é a imagem, e nenhum resultado diz. Dê o crédito à página que está em `url` e peça autorização ao dono antes de publicar a imagem.

Imagens que o buscador marca como adultas ficam de fora, então uma página pode trazer menos resultados do que você pediu. Aqui o `includeDomains` aceita um domínio só, não dez. Dois ou mais dá erro 400 e não custa nada.

Só os seus domínios

includeDomains
{
  "query": "engenheiro de dados",
  "includeDomains": ["linkedin.com", "github.com"],
  "limit": 20
}

Passe até dez domínios sem esquema nem caminho e a resposta está nessa lista ou não está lá. `linkedin.com` cobre `br.linkedin.com` e nunca `naolinkedin.com`. Pedimos isso ao buscador e conferimos de novo na saída, então o que volta é o que você pediu, faça o buscador o que fizer com o operador.

Do resultado para a página

Buscar e depois ler
import { Scrapa } from "@scrapa/sdk";

const scrapa = new Scrapa(process.env.SCRAPA_API_KEY!);

const { results } = await scrapa.search({ query: "melhores CRMs para PME" });
const pages = await Promise.all(
  results.slice(0, 3).map((hit) => scrapa.scrape({ url: hit.url })),
);

Uma busca devolve URLs e uma frase de cada. É o suficiente para escolher, quase nunca para trabalhar. Leia as que valem a pena com o scrape. São duas chamadas de propósito: assim uma página que te barra é uma falha que você vê e repete, e não onze escondidas dentro de uma requisição que você está esperando.

Uma resposta escrita

answer: true acrescenta um parágrafo curto escrito em cima dos resultados, no idioma em que a pergunta foi feita, sem nada que não esteja nos trechos. Custa um crédito a mais e vem desligado: se o seu próprio modelo vai ler os resultados de qualquer jeito, essa ida e volta não serve para nada.

Quanto custa

Quatro créditos por página de dez resultados. Um limit acima de dez lê uma segunda página e cobra uma segunda linha, que é o único jeito honesto de precificar uma segunda busca. Mais um crédito pela resposta escrita.

Uma busca que todos os buscadores bloquearam não custa nada, e nem o que a gente quebrou. Uma busca que rodou e não achou nada é cobrada. O índice respondeu, e "não tem nada" é uma resposta. Repetir a mesma pergunta em poucos minutos é de graça e a resposta avisa com cached: true.

A forma para o terminal

GET /v1/search
curl "https://api.scrapa.app/v1/search?query=pncp&topic=news&domains=gov.br" \
  -H "authorization: Bearer sc_live_your_key"

A mesma operação em GET, para quando o corpo JSON atrapalha: a lista de domínios vira uma string com vírgulas e o bloco de proxy se abre em parâmetros de query. Uma capacidade, duas escritas. O SDK e a ferramenta MCP têm só uma.