Activepieces + Scrapeless: Um Fluxo de Leads Local Sem Código
Web Data Collection Specialist
TL;DR:
- Activepieces alcança dados de pesquisa ao vivo com seu componente HTTP embutido, portanto, um fluxo de leads não precisa de um componente personalizado, de um pacote publicado ou de um serviço para hospedagem.
- O ator Google Search retorna cerca de 20 empresas locais por solicitação — capturas ficaram em 20, 21 e 22 — então um fluxo local geralmente precisa de uma única chamada mais uma chave de desduplicação em vez de um loop de paginação.
- A resposta analisada está sob
body, o que faz{{step_1.body.local_results.places}}a referência de trabalho; eliminebodye o loop roda zero vezes dentro de um fluxo que ainda reporta sucesso. - O campo
phonecontém um número de telefone em apenas cerca de metade dos registros — o resto carrega horários de funcionamento ou um rótulo de serviço — portanto, um componente Code deve validá-lo, não apenas cortá-lo. - Armazene a chave da API como um valor em nível de projeto em vez de digitá-la no campo de cabeçalho, porque fluxos são exportados e compartilhados.
O que este fluxo oferece a você
Um fluxo Activepieces funcional que transforma uma categoria e uma cidade em linhas de negócios locais — nome, categoria, avaliação, contagem de avaliações e telefone — prontos para um CRM, uma planilha ou um banco de dados.
Activepieces orquestra aplicativos bem e não busca páginas. Os resultados da pesquisa vêm do Deep SerpApi através de seu ator scraper.google.search, que retorna o pacote local analisado como JSON. O fluxo abaixo foi construído em uma instância Activepieces 0.82.0 auto-hospedada com piece-http 0.11.18.
Pré-requisitos
- Uma instância Activepieces, em nuvem ou auto-hospedada
- Uma chave de API Scrapeless — crie uma conta gratuita
- Um componente de destino para as linhas: Google Sheets, Airtable, Postgres ou seu CRM
Coloque a chave em um valor de nível de projeto ou em uma conexão, não no campo de cabeçalho do passo. Uma definição de fluxo carrega seus valores de campo literais, e os fluxos são exportados, duplicados e compartilhados entre projetos.
Configure o passo HTTP
Adicione HTTP → Enviar solicitação HTTP e preencha cinco campos:
| Campo | Valor |
|---|---|
| Método | POST |
| URL | https://api.scrapeless.com/api/v1/scraper/request |
| Cabeçalhos | x-api-token → sua chave |
| Tipo de corpo | JSON |
| Corpo | o objeto abaixo |
json
{
"actor": "scraper.google.search",
"input": {
"q": "plumbers in Austin, TX",
"tbm": "lcl"
}
}
tbm definido como lcl é o que retorna negócios em vez de páginas da web. A consulta precisa de intenção local: "plumbers in Austin, TX" retorna um pacote local, enquanto um "plumbing" nu geralmente não.
Antes de conectar o restante do fluxo, confirme a solicitação fora do construtor. A mesma chamada de um shell informa se um resultado vazio é culpa da consulta ou do fluxo:
bash
curl -sS -X POST https://api.scrapeless.com/api/v1/scraper/request \
-H 'Content-Type: application/json' \
-H "x-api-token: $SCRAPELESS_API_KEY" \
-d '{"actor":"scraper.google.search","input":{"q":"plumbers in Austin, TX","tbm":"lcl"}}' \
| python3 -c 'import json, sys
data = json.load(sys.stdin)
places = (data.get("local_results") or {}).get("places") or []
print(len(places), "places")
if places:
print("raw phone:", repr(places[0]["phone"]), "| raw type:", repr(places[0]["type"]))
else:
print("no local pack in this response; top-level keys were", sorted(data))'
Isso imprime a contagem de locais e o phone bruto e type do primeiro registro — os dois campos que chegam preenchidos. Ler as chaves na ramificação else em vez de indexar diretamente em local_results é o mesmo hábito que o fluxo precisa: verifique se a forma que você espera está presente antes de acessá-la.
O que volta
Uma execução bem-sucedida retorna cerca de 20 locais sob local_results.places, cada um carregando title, type, rating, reviews, phone e address. Capturas ficaram em 20, 21 e 22 locais para a mesma consulta, portanto, trate o tamanho da página como aproximado em vez de fixo: adicione "start": 20 ao objeto input para a próxima página e desduplicate pelo nome mais telefone em vez de assumir limites exatos de página.
Com tbm definido como lcl o envelope é local_results, metadata, pagination e search_information — sem organic_results e sem related_searches. Uma chamada rejeitada retorna um envelope contendo code e message em vez de quaisquer resultados, razão pela qual o fluxo deve ramificar na presença de local_results em vez de apenas no status HTTP.
O caminho de referência é a parte que vale a pena acertar. No Activepieces, o JSON analisado vive sob body:
| Referência | Resultado |
|---|---|
{{step_1.body.local_results.places}} |
o array de locais |
{{step_1.body.organic_results}} |
resultados da web, quando tbm é omitido |
{{step_1.organic_results}} |
nada — sem erro, sem aviso |
Essa última linha é a mais cara. Uma referência faltando o segmento body se resolve em nada, o passo Loop on Items itera zero vezes e a execução ainda termina como bem-sucedida. Uma tabela de destino vazia parece idêntica, seja a consulta retornou nada ou a referência estava errada, portanto verifique o caminho primeiro.
Adicione Loop on Items sobre {{step_1.body.local_results.places}} para que cada negócio seja tratado como seu próprio item em vez de uma massa escrita em uma única célula.
Construir isso no plano gratuito é suficiente para alcançar uma resposta completa do pacote local — comece com uma conta Scrapeless e mantenha a chave em um valor de projeto.
Normalize Antes de Armazenar
Duas comportamentos decidem se suas linhas são utilizáveis, e o segundo é a razão pela qual um fluxo de leads precisa de código.
Strings chegam preenchidas. phone, type e hours trazem um espaço em branco à frente — " Plumber", " (512) 690-4935". Isso não é cosmético: um número de telefone preenchido usado como uma chave de deduplicação cria um segundo registro para o mesmo negócio na próxima execução, e um filtro de categoria em "Plumber" não corresponde a nada. A recomendação do plano de numeração da ITU-T é a razão para normalizar um número de telefone para uma forma canônica antes de se tornar um identificador.
Vários campos estão presentes, mas não trazem nada utilizável. Na mesma captura, place_id, thumbnail e lsig estavam vazios em todos os 20 registros. gps_coordinates é a armadilha: ele está presente como {"latitude": 0, "longitude": 0}, então uma verificação de veracidade passa e um passo de mapeamento coloca cada negócio no mesmo ponto no equador. Pegue a localização de address e trate o par de coordenadas como ausente, a menos que ambos os valores sejam diferentes de zero.
O campo phone nem sempre é um número de telefone. Em uma captura de 20 lugares para "plumbers in Austin, TX", apenas 11 registros apresentaram um valor em formato de telefone. Os outros nove continham texto de horários de funcionamento, como " Closes 6 PM " ou um rótulo de serviço, como "Online estimates". Mapeie esse campo diretamente em uma coluna de CRM e quase metade das linhas chega inutilizável, sem erro em lugar algum no fluxo. Algumas dessas strings de horas também contêm um espaço estreito sem quebra (U+202F) em vez de um espaço normal, então uma divisão ingênua em " " se comporta de maneira inesperada, mesmo após o corte.
Valide o campo em vez de confiar em seu nome e mantenha o texto descartado em vez de descartá-lo:
Adicione um Código entre a solicitação e o destino. Activepieces envolve o corpo como export const code = async (inputs) => { … }; a lógica interna é JavaScript simples:
javascript
// `phone` sometimes carries opening hours or a service label instead of a number,
// so the value is validated before it becomes a contact field.
const PHONE = /\(?\d{3}\)?[ -]?\d{3}-?\d{4}/;
const code = async (inputs) => {
const clean = (value) => (typeof value === 'string' ? value.trim() : value);
const places = inputs.response?.local_results?.places ?? [];
return places.map((place) => {
const contact = clean(place.phone) ?? '';
const isPhone = PHONE.test(contact);
return {
name: clean(place.title),
category: clean(place.type),
rating: place.rating ?? null,
reviews: place.reviews ?? 0,
phone: isPhone ? contact : null,
phone_field_note: isPhone ? null : contact,
address_snippet: clean(place.address),
};
});
};
const sample = {
response: {
local_results: {
places: [
{
title: 'Radiant Plumbing, Air Conditioning, & Electrical',
type: ' Plumber',
rating: 4.8,
reviews: 18000,
phone: ' (512) 690-4935',
address: '25+ years in business \u00b7 Austin, TX',
},
{
title: 'Beyond Wow Plumbing & Drains',
type: ' Plumber',
rating: 4.9,
phone: ' Closes 6\u202fPM ',
address: 'Austin, TX',
},
],
},
},
};
code(sample).then((rows) => console.log(JSON.stringify(rows, null, 2)));
Passe {{step_1.body}} na entrada response do trecho. Dois detalhes importam aqui. O padrão ?? 0 existe porque um negócio sem avaliações não tem chave reviews alguma, e uma coluna de destino numérica rejeita undefined enquanto aceita 0. E phone_field_note mantém o que ocupava o campo quando não era um número, para que um operador possa ver que uma linha tem horários de funcionamento em vez de um telefone ausente.
Classificação e contagem de avaliações são os dois campos que valem a pena manter numéricos. Todo o resto é texto, e se o fluxo termina em uma exportação de planilha em vez de em um banco de dados, a especificação do formato de valores separados por vírgula decide como um nome de negócio contendo uma vírgula sobrevive à viagem de ida e volta.
Tratando Dados de Contato Empresarial de Forma Responsável
Este fluxo coleta detalhes de contato empresarial, então algumas obrigações vêm com isso. Colete apenas de resultados de busca públicos e apenas os campos que o fluxo de trabalho precisa. Mantenha uma base legal para armazenar dados de contato e honre as solicitações de exclusão, já que um número de telefone empresarial ainda pode identificar um empresário individual como uma pessoa — o Regulamento Geral sobre a Proteção de Dados se aplica a dados pessoais mesmo em um contexto comercial, e regras equivalentes existem em outras jurisdições. Respeite os termos de cada plataforma de destino para contatos importados, defina um período de retenção em vez de manter linhas indefinidamente e siga as regras de consentimento de marketing do país que você está contatando. Nada disso é aconselhamento jurídico; verifique suas próprias obrigações antes de realizar ações de alcance.
Conclusão
Três partes compõem todo o fluxo: HTTP para chamar o ator, Código para aparar e definir os campos, Loop em Itens para escrever uma linha por negócio. O modo de falha a ser observado reside no caminho de referência: descarte body e uma execução bem-sucedida escreve uma tabela vazia.
Daqui, troque a consulta por uma lista de cidades e o mesmo fluxo se torna uma construção de território. O guia de integração do Make cobre a mesma solicitação de um construtor sem código diferente, e a construção de monitoramento Dify mostra a versão dirigida por agente do mesmo ator.
Pronto para construí-lo? Revise a documentação da Deep SerpApi para o conjunto completo de parâmetros, compare planos e volume incluído, e comece no plano gratuito.
FAQ
Q: Eu preciso de um piece personalizado do Activepieces para usar o Scrapeless?
Não. O piece HTTP embutido cobre todos os atores, pois a API recebe um único POST com um campo actor e um objeto input. Um piece personalizado só ajuda se você quiser um passo de marca com campos tipados para uma equipe que não deve ver a solicitação bruta, e isso é uma decisão de embalagem, em vez de capacidade.
Q: Por que meu passo Loop on Items itera zero vezes quando o passo HTTP teve sucesso?
A resposta analisada está aninhada sob body, então {{step_1.local_results.places}} resolve para nada enquanto {{step_1.body.local_results.places}} resolve para o array. Uma referência ausente não gera erro, então o fluxo reporta sucesso com um loop vazio. Verifique o caminho da referência antes de investigar a consulta.
Q: Quantos resultados uma solicitação retorna, e como eu obtenho mais?
Uma solicitação de local-pack retorna cerca de 20 lugares; capturas repetidas de uma consulta retornaram 20, 21 e 22. Adicione "start": 20 ao objeto input para a próxima página, "start": 40 para a seguinte e assim por diante. Como o tamanho da página não é exatamente fixo, evite duplicar pelo nome mais telefone, em vez de confiar em offsets para alinhar, e trate uma página final curta como o fim do conjunto.
Q: Por que place_id e gps_coordinates são inutilizáveis em resultados locais?
place_id, thumbnail, e lsig retornam vazios em cada registro de local-pack, então um fluxo que requer um place_id descarta todos os 20 resultados. gps_coordinates se comporta de maneira diferente e mais perigosa: é populado com {"latitude": 0, "longitude": 0}, que sobrevive a uma verificação de vazio enquanto aponta cada negócio para a mesma coordenada. Use address para localização e confie apenas no par de coordenadas quando ambos os números forem diferentes de zero.
Q: Onde a chave da API deve estar em um fluxo do Activepieces?
Em um valor a nível de projeto ou uma conexão, referenciada a partir do campo do cabeçalho. As definições de fluxo carregam valores literais de campo e são exportadas e duplicadas entre projetos, então uma chave digitada diretamente no passo viaja com cada cópia.
Q: Esse fluxo pode ser executado em um cronograma em vez de um webhook?
Sim. Troque o gatilho por Schedule e o restante do fluxo permanece inalterado, que é a forma usual para um refresh de território. Mantenha a frequência da execução correspondente a quão frequentemente as classificações locais realmente se movem; diariamente é suficiente para a maioria das categorias, e uma cadência mais lenta mantém o volume previsível.
Q: O mesmo fluxo funciona para resultados da web em vez de negócios?
Sim. Remova tbm do objeto input e os resultados chegam sob {{step_1.body.organic_results}} em vez disso, cada um com title, link, e snippet. O piece Code precisa de seu caminho atualizado para coincidir, e o corte é desnecessário em resultados da web.
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.



