🎯 Um navegador em nuvem personalizável e anti-detecção alimentado por Chromium desenvolvido internamente, projetado para rastreadores web e agentes de IA. 👉Experimente agora
De volta ao blog

Como Construir um Pipeline Local de RAG Com Scrapeless e Ollama

Ava Wilson
Ava Wilson

Expert in Web Scraping Technologies

30-Jul-2026

TL;DR:

  • Todo o loop roda localmente, sem chave de API de provedor de embedding ou LLM. Scrapeless é a única chamada paga neste pipeline — buscando as páginas fonte. Chunking, embedding, armazenamento de vetores, recuperação e geração de respostas ocorrem todos nesta máquina.
  • A recuperação é provada, não assumida. Duas perguntas sobre duas pessoas diferentes extraídas retornam cada uma o chunk de biografia correto de 48 chunks que abrangem 5 autores — com as distâncias de similaridade reais capturadas abaixo, não afirmadas.
  • A etapa de geração é uma chamada local ao vivo, não uma resposta em template. Um modelo Ollama com 494M de parâmetros lê apenas o chunk recuperado e responde corretamente, fundamentado em texto extraído minutos antes.
  • Chunking e embedding permanecem pequenos de propósito. Cada biografia se torna de 4 a 14 chunks sobrepostos de 60 palavras, transformados em vetores de 384 dimensões com sentence-transformers em uma chamada local em batch — sem necessidade de GPU.
  • Esta é a metade a jusante de dois outros pipelines Scrapeless. Se você já tiver chunks limpos ou um índice embutido da OpenAI, passe para a Etapa 3 ou Etapa 5 abaixo — as diferenças são indicadas em linha.
  • Gratuito para começar. Crie sua chave de API Scrapeless no plano gratuito em app.scrapeless.com.

Pipeline em um Relance

A geração aumentada por recuperação, conforme definido no papel RAG original, emparelha um recuperador sobre um corpus externo com um gerador que lê o que o recuperador encontra. A maioria dos tutoriais sobre essa ideia para em uma metade dela. Este construí ambas as metades, de ponta a ponta, contra páginas reais extraídas:

  1. Buscar — puxar HTML renderizado para um conjunto fixo de páginas de autor através da API Universal Scraping da Scrapeless.
  2. Extrair e chunk — analisar cada página em texto biográfico, dividido em janelas de palavras sobrepostas com proveniência.
  3. Embed — transformar cada chunk em um vetor localmente com sentence-transformers.
  4. Armazenar — persistir os vetores e seus metadados fontes em uma coleção Chroma local.
  5. Recuperar — incorporar uma pergunta, perguntar ao Chroma pelos chunks mais próximos e verificar se a pessoa certa retornou.
  6. Gerar — passar o chunk recuperado para um modelo Ollama local e obter uma resposta fundamentada, sem chamadas de LLM na nuvem em lugar algum.

Outros dois posts da Scrapeless já cobrem partes disso. O guia de ingestão de texto da web busca, extrai e chunk em corpus.jsonl, e então para de propósito: embeddings são "a jusante, com qualquer stack que você já utiliza." O guia do pipeline de texto LLM vai mais longe: descobrir, extrair, chunk e embed com OpenAI em Chroma. Mas ele para no registro embutido — nada consulta o armazenamento ou gera uma resposta — e precisa de uma chave paga OPENAI_API_KEY. Este guia é a parte que nenhum dos outros cobre: embeddings locais sem chave de provedor de embedding, uma prova ao vivo de que a recuperação encontra a fonte certa, e um modelo local que realmente responde à pergunta. (Para o conceito de RAG em si, em vez de uma construção, veja O que é Geração Aumentada por Recuperação.)

Pré-requisitos

  • Python 3.10 ou posterior.
  • Uma chave de API Scrapeless, exportada como SCRAPELESS_API_KEY — a fase de busca é a única que precisa dela.
  • Ollama instalado e funcionando, com um modelo pequeno baixado: ollama pull qwen2.5:0.5b.
  • pip install sentence-transformers chromadb beautifulsoup4 requests.

Instalação

sentence-transformers exige PyTorch, então a primeira instalação leva alguns minutos e aproximadamente um gigabyte de disco. Tudo depois disso — carregamento de modelo, embedding, e consulta — roda offline uma vez que o modelo está em cache.

bash Copy
pip install sentence-transformers chromadb beautifulsoup4 requests
ollama pull qwen2.5:0.5b
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"

Etapa 1 — Buscar as Páginas Fonte

O corpus para este guia é composto por cinco páginas de biografia de autores em quotes.toscrape.com, um site público criado para prática de scraping: Albert Einstein, Jane Austen, Marilyn Monroe, J.K. Rowling e Thomas A. Edison. Cinco pessoas diferentes com cinco locais de nascimento, datas e carreiras genuinamente diferentes é o que torna a verificação de recuperação da Etapa 5 significativa — uma pergunta sobre um deles tem uma fonte correta, não cinco plausíveis.

Um POST por página para a API Universal Scraping retorna o HTML renderizado no campo data da resposta:

python Copy
# fetch.py -- puxa páginas de biografia de autores renderizadas pela API Universal Scraping Scrapeless
import os
import pathlib

import requests

ENDPOINT = "https://api.scrapeless.com/api/v2/unlocker/request"
HEADERS = {
    "Content-Type": "application/json",
    "x-api-token": os.environ["SCRAPELESS_API_KEY"],
}
SOURCE_HOST = "https://quotes.toscrape.com"

AUTHORS = ["Albert-Einstein", "Jane-Austen", "Marilyn-Monroe", "J-K-Rowling", "Thomas-A-Edison"]

pathlib.Path("pages").mkdir(exist_ok=True)
for slug in AUTHORS:
    url = f"{SOURCE_HOST}/author/{slug}"
    resp = requests.post(
        ENDPOINT,
        headers=HEADERS,
        json={"actor": "unlocker.webunlocker", "input": {"url": url, "js_render": False, "redirect": True}},
        timeout=120,
    )
    resp.raise_for_status()
    html = resp.json()["data"]
    pathlib.Path("pages", f"{slug}.html").write_text(html, encoding="utf-8")
    print(f"{slug}: {len(html):,} bytes")

Essas páginas são renderizadas no lado do servidor sem JavaScript no lado do cliente construindo a biografia, portanto js_render permanece False — o desbloqueador ainda lida com a solicitação e retorna a página de qualquer maneira, mas não há custo de renderização a pagar por um conteúdo que já está no HTML inicial. Uma execução ao vivo retorna cinco contagens de bytes distintas, uma por autor:

text Copy
Albert-Einstein: 5.329 bytes
Jane-Austen: 3.413 bytes
Marilyn-Monroe: 3.663 bytes
J-K-Rowling: 5.347 bytes
Thomas-A-Edison: 2.648 bytes

Obtenha sua chave da API no plano gratuito: app.scrapeless.com

Etapa 2 — Extrair e Dividir

Cada página contém os mesmos três campos em classes fixas: author-title, author-born-date, author-born-location e author-description. Extraia-os com BeautifulSoup, inicie a biografia com uma frase de nascimento explícita para que o fato ancore um chunk por conta própria, depois divida em janelas sobrepostas de 60 palavras com uma sobreposição de 15 palavras:

python Copy
# chunk_corpus.py -- pages/*.html -> corpus.jsonl (chunks de janelas de palavras sobrepostas com proveniência)
import json
import pathlib
import re

from bs4 import BeautifulSoup

CHUNK_WORDS = 60
OVERLAP_WORDS = 15


def extract_author(html: str) -> dict:
    soup = BeautifulSoup(html, "html.parser")
    name = soup.find("h3", class_="author-title").get_text(strip=True)
    born_date = soup.find("span", class_="author-born-date").get_text(strip=True)
    born_loc = soup.find("span", class_="author-born-location").get_text(strip=True)
    desc = soup.find("div", class_="author-description").get_text(" ", strip=True)
    return {"name": name, "born_date": born_date, "born_location": born_loc,
            "description": re.sub(r"\s+", " ", desc)}


def chunk(words: list[str]):
    step = CHUNK_WORDS - OVERLAP_WORDS
    for start in range(0, max(len(words) - OVERLAP_WORDS, 1), step):
        yield start, " ".join(words[start:start + CHUNK_WORDS])


total = 0
with open("corpus.jsonl", "w", encoding="utf-8") as out:
    for page in sorted(pathlib.Path("pages").glob("*.html")):
        author = extract_author(page.read_text(encoding="utf-8"))
        lead = f"{author['name']} nasceu em {author['born_date']} {author['born_location']}."
        words = f"{lead} {author['description']}".split()
        for start, body in chunk(words):
            out.write(json.dumps({"id": f"{page.stem}-{start}", "source": page.stem,
                                   "author": author["name"], "word_offset": start,
                                   "text": body}) + "\n")
            total += 1
print(f"{total} chunks -> corpus.jsonl")

Uma execução ao vivo contra as cinco páginas buscadas produz 48 chunks, divididos de forma desigual conforme o quanto cada biografia realmente diz:

text Copy
Albert Einstein: 615 palavras -> 14 chunks
J.K. Rowling: 644 palavras -> 14 chunks
Jane Austen: 327 palavras -> 7 chunks
Marilyn Monroe: 376 palavras -> 9 chunks
Thomas A. Edison: 195 palavras -> 4 chunks
48 chunks -> corpus.jsonl

Sessenta palavras com uma sobreposição de 25% é uma configuração de pequeno corpus, deliberadamente mais apertada do que a janela de 220/40 palavras no guia de ingestão vinculado acima — essas biografias têm algumas centenas de palavras cada, não os artigos de várias milhares de palavras que guiam os chunks. Ajuste a janela ao comprimento de sua fonte, não a um padrão fixo.

Etapa 3 — Incorporar Localmente

Cada pedaço se torna um vetor de 384 dimensões com all-MiniLM-L6-v2, um modelo de incorporação de sentença compacto o suficiente para rodar em CPU e avaliado ao lado de modelos maiores na suíte de benchmark MTEB. O modelo é baixado uma vez (aproximadamente 90 MB) e cada chamada de codificação depois disso é feita offline:

python Copy
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("all-MiniLM-L6-v2")
vectors = model.encode(texts, show_progress_bar=False).tolist()

Etapa 4 — Armazenar em um Banco de Dados Vetorial Local

O PersistentClient do Chroma grava a coleção no disco, de modo que o índice sobrevive entre execuções sem um servidor para gerenciar. Cada vetor mantém seu autor e página de origem como metadados, o que permite que um pedaço recuperado seja rastreável de volta a uma página específica em vez de uma string anônima de texto:

python Copy
import chromadb

client = chromadb.PersistentClient(path=".chroma")
collection = client.create_collection("author_bios")
collection.add(
    ids=[c["id"] for c in chunks],
    documents=[c["text"] for c in chunks],
    embeddings=vectors,
    metadatas=[{"author": c["author"], "source": c["source"], "word_offset": c["word_offset"]}
               for c in chunks],
)

Uma execução ao vivo contra todos os 48 pedaços confirma que a coleção contém tudo o que foi incorporado:

text Copy
incorporados 48 pedaços, dim 384, contagem da coleção 48

Etapa 5 — Recuperar o Pedaço Certo

Esta é a etapa que a maioria dos tutoriais local-RAG ignora: provar que a recuperação realmente retorna a fonte correta em vez de apenas retornar algo. Incorpore uma pergunta da mesma forma que os pedaços foram incorporados, depois pergunte ao Chroma pelos vizinhos mais próximos pela sua distância L2 padrão, onde um número menor significa uma correspondência mais próxima:

python Copy
query_vector = model.encode([question]).tolist()
result = collection.query(query_embeddings=query_vector, n_results=2)
top_author = result["metadatas"][0][0]["author"]
top_distance = result["distances"][0][0]

Duas perguntas sobre duas pessoas diferentes, executadas contra o mesmo índice de 48 pedaços, cada uma retorna a pessoa correta e ninguém mais biografia:

text Copy
P: Onde nasceu Albert Einstein?
  autor da melhor correspondência: Albert Einstein | distância: 0.6465
  pedaço: Albert Einstein nasceu em 14 de março de 1879 em Ulm, Alemanha. Em 1879, Albert Einstein nasceu em Ulm, Alemanha. Ele completou seu doutorado na Universidade de Zurique b...
P: Onde nasceu J.K. Rowling?
  autor da melhor correspondência: J.K. Rowling | distância: 0.4566
  pedaço: J.K. Rowling nasceu em 31 de julho de 1965 em Yate, South Gloucestershire, Inglaterra, Reino Unido. Veja também: Robert Galbraith Embora escreva sob o nome fictício...

Nenhuma das perguntas recupera um pedaço de Jane Austen, Marilyn Monroe ou Thomas Edison — o espaço de incorporação separa cinco biografias não relacionadas suficientemente bem para que uma pergunta factual sobre uma pessoa não traga acidentalmente a de outra.

Etapa 6 — Gerar uma Resposta Fundamentada

O pedaço recuperado se torna o único contexto que um modelo local de Ollama recebe. O prompt pede por um único campo JSON — apenas a resposta — porque a proveniência já existe nos metadados de recuperação da Etapa 5; pedir a um modelo pequeno para também reformular um campo de citação é pedir para ele fazer um trabalho que o banco de vetores já fez de graça, e não é um campo extra confiável para se pedir a um modelo de 494M parâmetros:

python Copy
import json
import requests

prompt = (
    'Responda à pergunta em uma frase curta usando SOMENTE o contexto abaixo. '
    'Responda com SOMENTE JSON {"answer": "..."}.\n\n'
    f"Contexto:\n{context}\n\nPergunta: {question}"
)
resp = requests.post(
    "http://localhost:11434/api/generate",
    json={"model": "qwen2.5:0.5b", "prompt": prompt, "stream": False,
          "format": "json", "options": {"temperature": 0, "num_predict": 40}},
    timeout=600,
)
answer = json.loads(resp.json()["response"])

Uma chamada ao vivo, alimentada apenas pelo pedaço recuperado de Einstein, responde corretamente sem nunca ter visto as outras quatro biografias:

text Copy
autor do pedaço recuperado (dos metadados do banco de vetores): Albert Einstein
pergunta: Onde nasceu Albert Einstein e em que ano?
resposta gerada: Albert Einstein nasceu em 14 de março de 1879 em Ulm, Alemanha.

Esse é o ciclo completo: uma pergunta entra, o banco de vetores reduz 48 candidatos a um pedaço relevante e um modelo rodando em localhost transforma esse pedaço em uma frase — sem chave OpenAI, sem Scraping Browser, nada saindo da máquina após a busca inicial.

O Pipeline Completo

Cada etapa acima se encadeia em um único script: buscar cinco páginas, dividi-las em pedaços, incorporar e armazenar o resultado, executar ambas as verificações de recuperação e, em seguida, gerar a resposta final. Nada aqui é forjado ou montado a partir de sessões separadas — são as mesmas seis etapas, executadas uma vez, de cima para baixo:

python Copy
# full_pipeline.py -- buscar -> fragmentar -> embutir -> armazenar -> recuperar -> gerar, do início ao fim
import json
import logging
import os
import pathlib
import re

os.environ.setdefault("HF_HOME", "/usr/local/share/hf-cache")
os.environ.setdefault("HF_HUB_OFFLINE", "1")

import chromadb
import requests
from bs4 import BeautifulSoup
from sentence_transformers import SentenceTransformer

logging.getLogger("chromadb.telemetry.product.posthog").setLevel(logging.CRITICAL)

# Etapa 1: Buscar
END_POINT = "https://api.scrapeless.com/api/v2/unlocker/request"
HEADERS = {"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]}
SOURCE_HOST = "https://quotes.toscrape.com"
AUTORES = ["Albert-Einstein", "Jane-Austen", "Marilyn-Monroe", "J-K-Rowling", "Thomas-A-Edison"]

pathlib.Path("pages").mkdir(exist_ok=True)
for slug in AUTORES:
    url = f"{SOURCE_HOST}/author/{slug}"
    resp = requests.post(
        END_POINT, headers=HEADERS,
        json={"actor": "unlocker.webunlocker", "input": {"url": url, "js_render": False, "redirect": True}},
        timeout=120,
    )
    resp.raise_for_status()
    html = resp.json()["data"]
    pathlib.Path("pages", f"{slug}.html").write_text(html, encoding="utf-8")
    print(f"{slug}: {len(html):,} bytes")

# Etapa 2: Extrair e fragmentar
CHUNK_WORDS, OVERLAP_WORDS = 60, 15


def extrair_autor(html: str) -> dict:
    soup = BeautifulSoup(html, "html.parser")
    nome = soup.find("h3", class_="author-title").get_text(strip=True)
    data_nascimento = soup.find("span", class_="author-born-date").get_text(strip=True)
    local_nascimento = soup.find("span", class_="author-born-location").get_text(strip=True)
    desc = soup.find("div", class_="author-description").get_text(" ", strip=True)
    return {"name": nome, "born_date": data_nascimento, "born_location": local_nascimento,
            "description": re.sub(r"\s+", " ", desc)}


def fragmentar_palavras(palavras: list[str]):
    passo = CHUNK_WORDS - OVERLAP_WORDS
    for inicio in range(0, max(len(palavras) - OVERLAP_WORDS, 1), passo):
        yield inicio, " ".join(palavras[inicio:inicio + CHUNK_WORDS])


fragmentos = []
with open("corpus.jsonl", "w", encoding="utf-8") as out:
    for pagina in sorted(pathlib.Path("pages").glob("*.html")):
        autor = extrair_autor(pagina.read_text(encoding="utf-8"))
        introducao = f"{autor['name']} nasceu {autor['born_date']} {autor['born_location']}."
        palavras = f"{introducao} {autor['description']}".split()
        n_fragmentos = 0
        for inicio, corpo in fragmentar_palavras(palavras):
            rec = {"id": f"{pagina.stem}-{inicio}", "source": pagina.stem, "author": autor["name"],
                   "word_offset": inicio, "text": corpo}
            out.write(json.dumps(rec) + "\n")
            fragmentos.append(rec)
            n_fragmentos += 1
        print(f"{autor['name']}: {len(palavras)} palavras -> {n_fragmentos} fragmentos")
print(f"{len(fragmentos)} fragmentos -> corpus.jsonl")

# Etapa 3 & 4: Embutir e armazenar
modelo = SentenceTransformer("all-MiniLM-L6-v2")
cliente = chromadb.PersistentClient(path=".chroma", settings=chromadb.Settings(anonymized_telemetry=False))
if "author_bios" in [c.name for c in cliente.list_collections()]:
    cliente.delete_collection("author_bios")
coleção = cliente.create_collection("author_bios")

textos = [c["text"] for c in fragmentos]
vetores = modelo.encode(textos, show_progress_bar=False).tolist()
coleção.add(
    ids=[c["id"] for c in fragmentos],
    documents=textos,
    embeddings=vetores,
    metadatas=[{"author": c["author"], "source": c["source"], "word_offset": c["word_offset"]} for c in fragmentos],
)
print(f"embutido {len(fragmentos)} fragmentos, dimensão {len(vetores[0])}, contagem da coleção {coleção.count()}")

# Etapa 5: Recuperar
for pergunta in ["Onde nasceu Albert Einstein?", "Onde nasceu J.K. Rowling?"]:
    vetor_query = modelo.encode([pergunta]).tolist()
    resultado = coleção.query(query_embeddings=vetor_query, n_results=2)
    autor_top = resultado["metadatas"][0][0]["author"]
    distancia_top = resultado["distances"][0][0]
    texto_top = resultado["documents"][0][0]
    print(f"P: {pergunta}")
    print(f"  melhor autor correspondido: {autor_top} | distância: {distancia_top:.4f}")
    print(f"  fragmento: {texto_top[:160]}...")

# Etapa 6: Gerar
pergunta = "Onde nasceu Albert Einstein, e em que ano?"
vetor_query = modelo.encode([pergunta]).tolist()
resultado = coleção.query(query_embeddings=vetor_query, n_results=1)
contexto = resultado["documents"][0][0]
autor_recuperado = resultado["metadatas"][0][0]["author"]

prompt = (
    "Responda à pergunta em uma frase curta usando APENAS o contexto abaixo. "
    'Responda com APENAS JSON {"answer": "..."}.\n\n'
    f"Contexto:\n{contexto}\n\nPergunta: {pergunta}"
)
resp = requests.post(
    "http://localhost:11434/api/generate",
    json={"model": "qwen2.5:0.5b", "prompt": prompt, "stream": False,
          "format": "json", "options": {"temperature": 0, "num_predict": 40}},
    timeout=600,
)
resp.raise_for_status()
resposta = json.loads(resp.json()["response"])
```python
print("autor do trecho recuperado (a partir de metadados do armazenamento vetorial):", retrieved_author)
print("pergunta:", question)
print("resposta gerada:", answer["answer"])

Uma única execução de cima para baixo reproduz cada número mostrado etapa por etapa acima: cinco contagens de bytes, 48 trechos, uma coleção de 48 vetores com 384 dimensões, duas recuperações corretas e uma resposta fundamentada.

Fonte com Responsabilidade

Páginas de biografia descrevem pessoas reais, mesmo em um site de prática criado para raspagem. Três práticas mantêm um corpus de recuperação defensável quando você aponta este padrão para fontes de produção: colete apenas páginas públicas e verifique os termos de serviço de cada site e as diretivas de robôs antes de adicionar um domínio à lista de busca; mantenha a URL de origem e o nome do autor anexados a cada trecho, da forma como os metadados deste pipeline fazem, para que uma resposta gerada possa ser rastreada até de onde veio; e trate o volume de solicitações da maneira como este guia faz - cinco páginas, uma vez, não uma raspagem contínua contra um site que não concordou em fazer isso.

Conclusão

Cinco páginas adentro, uma sentença fundamentada para fora, e cada etapa após a busca inicial é executada nesta máquina. A metade de recuperação é a parte que vale a pena confiar antes da metade de geração: 48 trechos, duas perguntas, dois autores corretos, com as distâncias para provar isso - e somente então o modelo local pode responder. Troque as páginas de origem, o modelo de incorporação ou o modelo Ollama, e a forma permanece; o pipeline não muda quando o conteúdo muda.

Pronto para Construir Seu Pipeline RAG?

Junte-se à comunidade que constrói pipelines de dados no Scrapeless: Discord · Telegram.

Inscreva-se em app.scrapeless.com para créditos de teste gratuitos, e aponte a Etapa 1 para as páginas de que seu próprio corpus de recuperação precisa. A documentação do desenvolvedor cobre a forma da solicitação unlocker.webunlocker, e os planos atuais estão na página de preços.

FAQ

Q: Alguma parte deste pipeline precisa de uma chave de API em nuvem além do Scrapeless?

Não. O Scrapeless é a única chamada paga, e é necessária apenas para a busca da Etapa 1. A incorporação é executada localmente por meio de sentence-transformers, o armazenamento vetorial é um arquivo Chroma no disco e a geração é executada por meio de um modelo local Ollama - nada mais exige uma chave de provedor.

Q: Como isso é diferente dos outros posts relacionados a RAG do Scrapeless?

Cobertura, não sobreposição. O guia de ingestão busca, extrai e fragmenta, depois para deliberadamente antes das incorporações. O guia de pipeline LLM-text incorpora com OpenAI e armazena no Chroma, depois para antes de consultar ou gerar. Este guia é a metade local, sem chave de incorporação, que fecha o ciclo: recuperar, depois gerar.

Q: Preciso de uma GPU?

Não. all-MiniLM-L6-v2 e qwen2.5:0.5b foram executados na CPU para este guia. Uma GPU acelera um modelo local maior, mas nem a etapa de incorporação nem um pequeno modelo de geração exigem uma para construir e testar o pipeline.

Q: Como escolho um tamanho de trecho?

Combine-o com o quanto uma única fonte realmente diz. As biografias deste guia têm algumas centenas de palavras cada, então 60 palavras com uma sobreposição de 15 palavras mantém um trecho em torno de uma ideia. Um artigo com várias milhares de palavras exige uma janela mais ampla - o guia de ingestão acima usa 220 palavras com 40 de sobreposição por exatamente essa razão.

Q: E se a recuperação retornar o trecho errado?

Normalmente significa que o corpus tem conteúdo quase duplicado que o modelo de incorporação não consegue separar, ou a pergunta está formulada distante de como o texto da fonte está redigido. Ampliar a sobreposição de trechos, incorporar com um modelo maior ou adicionar uma segunda passagem de reclassificação sobre os principais candidatos ajudam; as cinco biografias distintas deste guia são deliberadamente fáceis de distinguir, o que torna um resultado de recuperação limpo possível de mostrar, em vez de apenas reivindicar.

Q: Posso trocar por um modelo local maior para geração?

Sim - a forma da solicitação não muda, apenas a string model muda. qwen2.5:0.5b é rápido o suficiente para demonstrar o ciclo na CPU; um modelo Ollama de 3B ou maior responde de forma mais confiável em contextos mais confusos, se o hardware tiver a memória necessária disponível.
Q: É legal extrair informações de páginas de biografia e gerar respostas a partir delas?

Extrair informações de páginas acessíveis ao público é amplamente permitido, mas os termos do site e a jurisdição são importantes. Colete apenas páginas públicas, verifique os termos de serviço e as diretrizes de robôs do site-alvo primeiro, mantenha o volume controlado e trate quaisquer dados pessoais no corpus de acordo com as leis de privacidade que se aplicam a você.

Na Scorretless, acessamos apenas dados disponíveis ao público, enquanto cumprem estritamente as leis, regulamentos e políticas de privacidade do site aplicáveis. O conteúdo deste blog é apenas para fins de demonstração e não envolve atividades ilegais ou infratoras. Não temos garantias e negamos toda a responsabilidade pelo uso de informações deste blog ou links de terceiros. Antes de se envolver em qualquer atividade de raspagem, consulte seu consultor jurídico e revise os termos de serviço do site de destino ou obtenha as permissões necessárias.

Artigos mais populares

Catálogo