Aviso importante: Este artigo destina-se exclusivamente a fins educacionais sobre acesso a dados publicamente disponíveis na internet. Não constitui aconselhamento legal. O LinkedIn proíbe explicitamente a recolha de dados nos seus Termos de Serviço. Consulte sempre um advogado especializado antes de qualquer projeto de scraping. Respeite o robots.txt, os Termos de Serviço de cada plataforma, e as leis aplicáveis como CFAA (EUA), GDPR (UE) e LGPD (Brasil).
O acesso a dados públicos na web tem sido objeto de intenso debate legal nos últimos anos. O caso hiQ Labs v. LinkedIn trouxe clarificação parcial: dados que qualquer utilizador anónimo pode ver num browser — sem autenticação — podem, em certas circunstâncias, ser considerados publicamente acessíveis. No entanto, isto não significa que scraping seja legalmente imune, e cada jurisdição tem as suas próprias regras.
O Que Pode Ser Acedido Sem Login no LinkedIn
O LinkedIn oferece três categorias principais de conteúdo publicamente acessível a visitantes não autenticados:
Perfis Públicos
Utilizadores podem optar por tornar os seus perfis visíveis publicamente. Estes URLs típicos linkedin.com/in/username mostram informações básicas quando acedidos sem login: nome, headline, experiência atual, formação e, por vezes, publicações recentes. A quantidade de informação varia conforme as definições de privacidade de cada utilizador.
Páginas de Empresa
As páginas linkedin.com/company/nome-empresa são geralmente públicas. Incluem descrição, setor, tamanho da empresa, localização, e por vezes posts recentes. Não requerem login para visualização básica.
Anúncios de Emprego Públicos
A secção linkedin.com/jobs/search/ permite pesquisa de ofertas sem autenticação. Cada anúncio individual em linkedin.com/jobs/view/ID é tipicamente acessível, mostrando título, descrição, requisitos, localização e empresa.
Princípio fundamental: Se necessita de login para ver a informação, essa informação não está publicamente acessível. O scraping de dados atrás de login, de sessões privadas, ou de funcionalidades como Sales Navigator, Recruiter Lite, ou Learning, viola quase certamente os ToS e pode ter implicações legais.
Porquê Proxies Residenciais São Essenciais para LinkedIn
O LinkedIn possui um dos sistemas anti-bot mais sofisticados da indústria. Compreender o porquê é crucial para qualquer projeto de recolha de dados legítima.
Fingerprinting Agressivo de IPs de Datacenter
Endereços IP de datacenter são imediatamente suspeitos. O LinkedIn mantém bases de dados extensas de IPs associados a provedores de cloud (AWS, Azure, GCP, DigitalOcean) e bloqueia ou limita esses ranges de forma preventiva. Um pedido de um IP de datacenter é muitas vezes recebido com CAPTCHAs ou erros 429 antes mesmo de qualquer conteúdo ser servido.
Limites por IP Rigorosos
O LinkedIn impõe rate limits granulares por endereço IP. Mesmo visitantes não autenticados enfrentam limites que variam consoante o padrão de acesso. Aceder a 50 perfis em 2 minutos de um único IP residencial pode funcionar; o mesmo padrão de um IP de datacenter será bloqueado quase instantaneamente.
Browser Fingerprinting
Além do IP, o LinkedIn analisa assinaturas de browser: User-Agent, resolução de ecrã, fontes instaladas, comportamento de JavaScript, e dezenas de outras variáveis. Bibliotecas como Puppeteer ou Playwright, sem configuração cuidadosa, deixam rastos óbvios de automação.
| Tipo de Proxy | Detetabilidade | Rate Limit Típico | Custo | Ideal Para |
|---|---|---|---|---|
| Datacenter | Alta (frequentemente bloqueado) | 5-10 pedidos/hora | $1-3/GB | Testes iniciais |
| Residencial rotativo | Baixa (parece tráfego real) | 50-100 pedidos/hora por IP | $8-15/GB | Scraping de perfis |
| Mobile 4G/5G | Mínima (IPs de operadoras) | 100+ pedidos/hora | $30-50/GB | Projetos de alta escala |
| Residencial estático | Baixa | Variável | $5-10/IP/mês | Sessões longas |
Para projetos de recolha de dados públicos do LinkedIn, proxies residenciais rotativos oferecem o melhor equilíbrio entre custo e eficácia. Cada pedido pode vir de um IP residencial diferente, distribuindo o risco de bloqueio.
Implementação Prática: Python + Playwright com Proxies Residenciais
O Playwright é superior ao Selenium para este caso de uso porque permite contexto de browser mais realistas e gestão mais fina de cookies, storage e fingerprints.
Configuração Base com Proxy Residencial
import asyncio
from playwright.async_api import async_playwright
import random
import time
# Configuração do proxy ProxyHat
PROXY_CONFIG = {
"server": "http://gate.proxyhat.com:8080",
"username": "user-country-US", # Geo-targeting opcional
"password": "PASSWORD"
}
async def create_stealth_context(browser):
"""Cria contexto de browser com fingerprints realistas."""
context = await browser.new_context(
proxy=PROXY_CONFIG,
viewport={"width": 1920, "height": 1080},
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
),
locale="en-US",
timezone_id="America/New_York",
geolocation={"latitude": 40.7128, "longitude": -74.0060},
permissions=["geolocation"],
)
return context
async def scrape_public_profile(page, profile_url):
"""Extrai dados de um perfil público sem login."""
try:
await page.goto(profile_url, wait_until="networkidle", timeout=30000)
# Aguardar carregamento do conteúdo principal
await page.wait_for_selector(".pv-top-card", timeout=10000)
# Extrair dados básicos
profile_data = await page.evaluate("""() => {
const name = document.querySelector('h1')?.innerText || '';
const headline = document.querySelector('.text-body-medium')?.innerText || '';
const location = document.querySelector('.pv-text-details__left-panel')?.innerText || '';
return { name, headline, location };
}"")
return profile_data
except Exception as e:
print(f"Erro ao aceder {profile_url}: {e}")
return None
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await create_stealth_context(browser)
page = await context.new_page()
# Rate limiting crítico - respeitar intervalos
profiles = [
"https://www.linkedin.com/in/example1",
"https://www.linkedin.com/in/example2",
]
for url in profiles:
data = await scrape_public_profile(page, url)
if data:
print(f"Extraído: {data}")
# Intervalo aleatório entre 8-15 segundos
await asyncio.sleep(random.uniform(8, 15))
await browser.close()
if __name__ == "__main__":
asyncio.run(main())
Gestão de Rate Limits e Rotação de IPs
Para escala moderada, implemente rotação de sessões que força mudança de IP residencial:
import requests
from urllib.parse import quote
# Formato ProxyHat com sessão rotativa
def get_proxy_url(session_id=None):
"""Gera URL de proxy com sessão opcional."""
username = "user-country-US"
if session_id:
username = f"user-country-US-session-{session_id}"
return f"http://{username}:PASSWORD@gate.proxyhat.com:8080"
# Verificar IP atual
response = requests.get(
"https://api.ipify.org?format=json",
proxies={"http": get_proxy_url(), "https": get_proxy_url()}
)
print(f"IP atual: {response.json()['ip']}")
Padrão de Rate Limiting Recomendado
import time
import random
from collections import deque
class RateLimiter:
"""Gestão de rate limits para scraping responsável."""
def __init__(self, requests_per_hour=50, min_interval=5):
self.requests_per_hour = requests_per_hour
self.min_interval = min_interval
self.request_times = deque(maxlen=requests_per_hour)
def wait_if_needed(self):
"""Aguarda se necessário para respeitar limites."""
now = time.time()
# Remover pedidos antigos da janela
while self.request_times and now - self.request_times[0] > 3600:
self.request_times.popleft()
# Verificar se estamos no limite
if len(self.request_times) >= self.requests_per_hour:
wait_time = 3600 - (now - self.request_times[0]) + 1
print(f"Rate limit atingido. Aguardando {wait_time:.0f}s")
time.sleep(wait_time)
# Intervalo mínimo entre pedidos
if self.request_times:
elapsed = now - self.request_times[-1]
if elapsed < self.min_interval:
jitter = random.uniform(0.5, 2.0)
time.sleep(self.min_interval - elapsed + jitter)
self.request_times.append(time.time())
# Uso
limiter = RateLimiter(requests_per_hour=40, min_interval=8)
for url in profile_urls:
limiter.wait_if_needed()
# fazer pedido...
Scraping de Anúncios de Emprego: Especificidades Técnicas
A secção de empregos do LinkedIn tem estrutura particular e requer atenção a filtros e paginação.
Estrutura de URLs de Pesquisa de Empregos
O endpoint /jobs/search/ aceita múltiplos parâmetros de filtro:
# Exemplos de URLs de pesquisa de empregos
BASE_URL = "https://www.linkedin.com/jobs/search/"
# Pesquisa simples
search_url = f"{BASE_URL}?keywords=python%20developer"
# Com filtros adicionais
filters = {
"keywords": "software engineer",
"location": "São Paulo",
"f_JT": "F", # Full-time
"f_E": "2", # Entry level (1=Intern, 2=Entry, 3=Associate, etc.)
"f_WT": "2", # Remote (1=On-site, 2=Remote, 3=Hybrid)
}
# Construir URL
params = "&".join(f"{k}={quote(v)}" for k, v in filters.items())
full_url = f"{BASE_URL}?{params}"
Extração de Lista de Empregos
async def scrape_job_search(page, search_url, max_pages=3):
"""Extrai lista de empregos de pesquisa pública."""
jobs = []
await page.goto(search_url, wait_until="networkidle")
await page.wait_for_selector(".jobs-search__results-list", timeout=15000)
for page_num in range(max_pages):
# Extrair empregos da página atual
page_jobs = await page.evaluate("""() => {
const jobCards = document.querySelectorAll('.job-card-container');
return Array.from(jobCards).map(card => ({
title: card.querySelector('.job-card-list__title')?.innerText || '',
company: card.querySelector('.job-card-container__company-name')?.innerText || '',
location: card.querySelector('.job-card-container__metadata-item')?.innerText || '',
url: card.querySelector('a')?.href || ''
}));
}"")
jobs.extend(page_jobs)
# Tentar próxima página
next_button = page.locator('button[aria-label="View next page"]')
if await next_button.count() == 0:
break
await next_button.click()
await asyncio.sleep(random.uniform(3, 6))
return jobs
Detalhes de um Anúncio Individual
async def scrape_job_details(page, job_url):
"""Extrai detalhes completos de um anúncio."""
await page.goto(job_url, wait_until="networkidle")
await page.wait_for_selector(".jobs-description-content", timeout=10000)
details = await page.evaluate("""() => {
return {
title: document.querySelector('.jobs-unified-top-card__job-title')?.innerText,
company: document.querySelector('.jobs-unified-top-card__company-name')?.innerText,
location: document.querySelector('.jobs-unified-top-card__bullet')?.innerText,
description: document.querySelector('.jobs-description-content')?.innerText,
posted: document.querySelector('.jobs-unified-top-card__posted-date')?.innerText
};
}"")
return details
O Que NÃO Deve Ser Extraído: Limites Éticos e Legais
Dados de Sessões Autenticadas
Qualquer informação visível apenas após login está protegida. Isto inclui:
- Perfis completos de utilizadores com privacidade restrita
- Informações de contacto (email, telefone)
- Conexões e rede de contactos
- Atividade recente privada
- Recomendações e competências
Sales Navigator e Recruiter
Estas são funcionalidades premium pagas. O acesso automatizado, mesmo com credenciais válidas, viola os Termos de Serviço. O caso hiQ Labs não se aplica aqui — estes dados não são publicamente acessíveis.
Dados de Utilizadores que Optaram por Privacidade
Se um utilizador configurou o seu perfil como privado, respeite essa escolha. O facto de tecnicamente ser possível contornar algumas restrições não o torna ético ou legal.
Escalamento Agressivo
Mesmo dados públicos não devem ser extraídos em escala massiva sem consideração. Milhares de pedidos por hora podem:
- Sobrecarregar infraestrutura alheia
- Violar termos de serviço
- Constituir uso indevido sob leis de acesso fraudulento
O Caso hiQ Labs v. LinkedIn: Contexto Legal
Em 2017, o LinkedIn enviou uma carta de cease-and-desist à hiQ Labs, uma empresa que analisava dados públicos de perfis para prever rotatividade de funcionários. A hiQ processou, alegando que o LinkedIn violava antitrust ao bloquear o acesso a dados públicos.
Decisões Chave
- 2019: O Tribunal de Distrito concedeu injunção preliminar a favor da hiQ, considerando que dados publicamente acessíveis podem não estar protegidos sob CFAA.
- 2022: O Tribunal de Apelações do 9º Circuito manteve que o LinkedIn não demonstrou probabilidade de sucesso no mérito.
- 2023: A disputa terminou com acordo confidencial.
Importante: Este caso criou precedente no 9º Circuito (EUA) sobre dados publicamente acessíveis e CFAA. No entanto, não é uma "carta branca" para scraping indiscriminado. Cada projeto deve ser avaliado individualmente com aconselhamento legal.
Alternativas Legítimas: APIs Oficiais do LinkedIn
Para muitos casos de uso, as APIs oficiais são a abordagem mais sustentável.
LinkedIn Marketing API
Para anunciantes e plataformas de marketing. Permite gerir campanhas, aceder a métricas de anúncios, e dados de páginas de empresa. Requer aprovação do LinkedIn.
LinkedIn Talent Solutions API
Disponível para parceiros de ATS (Applicant Tracking Systems). Permite gestão de candidaturas e algumas funcionalidades de recrutamento. Acesso restrito a parceiros aprovados.
LinkedIn Learning API
Para integração de conteúdo de formação em plataformas empresariais.
API de Share e Profile
Limitada, permite partilha de conteúdo e acesso básico ao perfil do utilizador autenticado via OAuth.
| API | Caso de Uso | Disponibilidade | Limitações |
|---|---|---|---|
| Marketing API | Anúncios, métricas de página | Parceiros aprovados | Requer aplicação |
| Talent Solutions | Recrutamento, ATS | Parceiros apenas | Altamente restrita |
| Share API | Publicação de conteúdo | Aberta | Apenas utilizador autenticado |
| Profile API | Dados do próprio perfil | Aberta via OAuth | Apenas dados do utilizador |
Considerações Éticas e Melhores Práticas
Respeite robots.txt
Verifique sempre linkedin.com/robots.txt antes de qualquer projeto. Embora não seja legalmente vinculativo em todas as jurisdições, demonstra intenção de respeitar os desejos do site.
Identificação e Contacto
Considere incluir um User-Agent identificável com informação de contacto. Isto permite ao site comunicar problemas diretamente.
Minimização de Dados
Recolha apenas o estritamente necessário para o seu caso de uso. Não arquive perfis completos se precisa apenas de headlines.
Respeite a Privacidade
Não publique nem venda dados de indivíduos sem consentimento. O GDPR e LGPD impõem obrigações significativas sobre dados pessoais.
Use APIs Quando Disponíveis
Se existe uma API oficial para o seu caso de uso, use-a. É mais estável, legal, e sustentável a longo prazo.
Key Takeaways
- Dados públicos vs privados: Apenas dados visíveis sem login podem ser considerados publicamente acessíveis; o resto está protegido por ToS e potencialmente por lei.
- Proxies residenciais são obrigatórios: O LinkedIn bloqueia agressivamente IPs de datacenter; proxies residenciais rotativos são essenciais para qualquer projeto de escala.
- Rate limiting é crítico: Mantenha pedidos sob 50/hora por IP, com intervalos aleatórios de 8-15 segundos entre pedidos.
- Caso hiQ Labs: Criou precedente limitado sobre dados publicamente acessíveis nos EUA, mas não é imunidade legal — consulte um advogado.
- Alternativas existem: APIs oficiais do LinkedIn, embora limitadas, são mais sustentáveis para projetos de longo prazo.
- Ética importa: Respeite privacidade, minimize dados recolhidos, e considere o impacto nos utilizadores cujos dados acede.
Para projetos de recolha de dados legítimos que respeitam estes princípios, a ProxyHat oferece proxies residenciais rotativos com geo-targeting em 190+ países. A nossa rede é otimizada para acesso a dados públicos de baixa latência com IPs que parecem tráfego residencial real.






