4 pontos por GN⁺ 2023-11-03 | 1 comentários | Compartilhar no WhatsApp
  • O Bear Blog implementou um sistema próprio de analytics que funciona sem JavaScript no cliente, por restrições de velocidade, eficiência e estabilidade
  • Scripts de analytics comuns podem ser bloqueados por ad blockers, e só logs de servidor podem misturar crawlers, scrapers e até parsers baseados em GPT, distorcendo as estatísticas de visita
  • No CSS de cada página, quando body:hover ocorre, border-image chama o endpoint /hit/{{ post.id }}/, usando hover ou rolagem no celular como sinal de leitura
  • O servidor verifica pelo user-agent se é bot, navegador e plataforma, e usa o endereço IP apenas para determinar o país, removendo leituras duplicadas com um hash de IP+data
  • Se a leitura vier do mesmo IP no mesmo dia, mesmo em vários dispositivos, conta como uma vez, mas ainda permite contabilizar de forma simples o número de leituras únicas por página sem armazenar informações identificáveis

Criando um evento de leitura com CSS hover

  • O sistema de analytics do Bear Blog segue a restrição de não usar JavaScript no cliente
  • Ferramentas de analytics comuns conseguem julgar em parte a autenticidade do tráfego ou se é bot com JavaScript no cliente, mas muitos ad blockers bloqueiam não só o Google Analytics como também scripts de analytics como Fathom e Plausible
  • Só analisar logs do servidor pode misturar crawlers de mecanismos de busca, scrapers e parsers baseados em GPT como se fossem tráfego normal, gerando uma visão distorcida
  • O Bear insere o CSS abaixo em cada página para que body:hover aconteça quando o usuário passa o cursor sobre a página ou faz rolagem no celular
body:hover {
  border-image: url("/hit/{{ post.id }}/?ref={{ request.META.HTTP_REFERER }}");
}
  • Quando body:hover é acionado, a URL de hit daquele post é chamada
  • A informação explicitamente adicionada de novo nessa requisição é o referrer, e a grafia HTTP_REFERER é uma forma com erro ortográfico que acabou permanecendo como padrão
  • Partindo do princípio de que bots não fazem hover, o Bear usa a chamada baseada em body:hover como sinal de leitor humano
  • Depois, no servidor, verifica se o user-agent é de bot e extrai navegador e plataforma da string de user-agent

Removendo leituras duplicadas sem informações de identificação

  • A segunda restrição é não armazenar no cookie do navegador nem no servidor informações que possam identificar o leitor
  • O endereço IP é usado apenas para determinar o país e, antes de ser armazenado, é hasheado junto com a data
  • Requisições posteriores para a mesma página são comparadas com o hash de endereço IP + data e descartadas se forem duplicadas
  • Nesse modelo, ao longo de um dia, um endereço IP conta como uma única leitura para uma página
  • O IP original não é armazenado, e o hash com a data tem efeito de expiração diária
user_agent = httpagentparser.detect(self.request.META.get('HTTP_USER_AGENT', None))
if user_agent.get('bot', False):
    print('Bot traffic')
    return

ip_hash = hashlib.md5(f"{client_ip(self.request)}-{timezone.now().date()}".encode('utf-8')).hexdigest()
country = get_user_location(client_ip(self.request)).get('country_name', '')
device = user_agent.get('platform', {}).get('name', '')
browser = user_agent.get('browser', {}).get('name', '')
referrer = self.request.GET.get('ref', '')

if referrer:
    referrer = urlparse(referrer)
    referrer = '{uri.scheme}://{uri.netloc}/'.format(uri=referrer)

Hit.objects.get_or_create(
    post_id=self.pk,
    ip_address=ip_hash,
    referrer=referrer,
    country=country,
    device=device,
    browser=browser)
  • O hash do IP é usado apenas para impedir hits duplicados ao longo do dia, enquanto cada visualização de página é tratada como única por padrão
  • No fim de cada dia, uma tarefa em segundo plano apaga o hash dos logs de hit para evitar conflito com interpretações excessivamente rígidas da GDPR
  • Uma desvantagem desse método é que, se vários dispositivos lerem no mesmo dia a partir do mesmo IP, isso ainda conta como uma única leitura
  • O Bear considera que esses casos são uma pequena parte do tráfego e vê a abordagem baseada em CSS como mais enxuta, simples e capaz de fornecer uma contagem de leituras mais precisa do que vários outros métodos de coleta de analytics

1 comentários

 
GN⁺ 2023-11-03
Opiniões no Hacker News
  • Sou o autor. O hash do endereço IP serve, neste contexto, apenas para impedir visualizações duplicadas no mesmo dia
    Ele existe para tornar cada visualização de página basicamente única, e, ao fim de cada dia, uma tarefa de worker limpa o hash enquanto mantém as informações de visualização. Também adicionei uma alteração ao texto para deixar isso claro

    • Fico curioso se você considerou a abordagem de servir de fato uma pequena imagem transparente com um cabeçalho de cache privado que expira à meia-noite, sem armazenar IP nenhum
    • Se 10 usuários no mundo todo estiverem usando o mesmo IP em uma VPN compartilhada e acessarem o site, isso conta como apenas 1 pessoa? Também há casos como redes corporativas; IP é uma métrica ruim
  • Quando vi pela primeira vez a ideia de usar requisições geradas por CSS para analytics, achei realmente incrível
    Alguém no Twitter colocou uma grade de retângulos invisíveis sobre a página e a usou para rastreamento do mouse, fazendo com que cada célula carregasse uma imagem de fundo única ao receber hover. Cada imagem de fundo enviava uma requisição específica ao servidor, que então a interpretava
    Por diversão, em um verão, expandi essa ideia e criei um “chat web assíncrono só com CSS”, sem JavaScript: https://github.com/kkuchta/css-only-chat

  • Dizer que anonimiza endereços IP fazendo hash apenas da data e do IP é só teatro de segurança
    Hashes criptográficos são projetados para serem calculados rapidamente. Com hashcat, em um MacBook M1 Pro, dá para calcular 6 bilhões de hashes MD5 por segundo, e há apenas 4 bilhões de endereços IPv4. Dá para fazer força bruta em todo o espaço e descobrir os endereços IP, o que na prática equivale a reverter o hash
    O mesmo vale mesmo que se use um hash seguro, como SHA-256, em vez do MD5 quebrado

    • Independentemente de ser tecnicamente trivial recuperar o IP a partir do hash, as autoridades de proteção de dados da UE foram muito claras: fazer hash de dados pessoais não é anonimização
      Mesmo que você faça hash do nome completo de alguém, ainda é possível responder depois à pergunta “este hash corresponde a este nome completo específico?”. O fato de ser possível responder a essa pergunta significa que o processo de anonimização é reversível
    • Sou o autor. Também comentei abaixo, mas parece mais relevante nesta thread
      O hash do endereço IP serve, neste contexto, apenas para impedir visualizações duplicadas no mesmo dia. Ele existe para tornar cada visualização de página basicamente única, e, ao fim de cada dia, uma tarefa de worker remove o hash de IP que já não é mais necessário
    • Como referência, o mesmo problema apareceu em uma discussão sobre algo parecido que o Storybook fez em telemetria[0], e, mesmo sem nenhuma otimização, leva cerca de duas horas no meu notebook doméstico para calcular hashes com salt para todos os IPv4
      [0] https://news.ycombinator.com/item?id=37596757
    • Hashes precisam ter salt. Com salt, tudo bem; sem salt, não está tudo bem
      Se o salt será armazenado permanentemente ou trocado periodicamente é apenas detalhe de implementação; o ponto central ao adicionar salt a hashes para analytics é que o salt nunca sai do cliente
      Pela descrição do texto, parece não haver salt. Ou talvez a data atual esteja sendo usada como se fosse um salt, mas isso não é um salt aleatório, então qualquer pessoa que queira saber “o IP x.y.z.w visitou na data aa-mm-dd?” consegue adivinhar facilmente
      Do ponto de vista de um atacante, é fácil avaliar esse tipo de problema. Com os dados fornecidos, como eu faria para descobrir algo sobre uma pessoa específica? Se eu não conseguir, então provavelmente esses dados podem ser armazenados de modo geral sem grandes problemas
    • Não daria para usar um salt secreto ou rotativo? Como isso não aparece no código de exemplo, acho a crítica válida. Ainda assim, adicionar só isso já tornaria a solução razoavelmente segura
      Porém me preocupa que só esse tipo de teatro de segurança já possa ser suficiente para passar por várias leis e regulações de privacidade
  • Embora pareça engenhoso, body:hover provavelmente deixa passar quase completamente usuários que usam apenas teclado e agentes de usuário sem dispositivo apontador, ou seja, usuários de tecnologias assistivas
    Mesmo que esses grupos possam ser periféricos, ver qualquer forma de exclusão deles é sempre um sinal muito ruim
    Não sei se existe, só com CSS básico, uma forma 100% confiável de detectar em todos os agentes de usuário que “uma pessoa real está lendo este artigo” e enviar uma requisição HTTP; também desconfio que não. Alguns podem nem oferecer suporte a CSS ou podem ter desativado o carregamento de imagens decorativas via CSS
    Um seletor moderno que poderia ajudar é :root:focus-within, mas o usuário precisa de fato focar um elemento interativo, e nem todo agente de usuário garante isso. O moderníssimo @scroll-timeline, para animações vinculadas à rolagem, também poderia funcionar, mas leitores de braille ainda provavelmente ficariam de fora

    • Periféricos? Isso não afeta celulares e tablets que não dão suporte a :hover, ou seja, mais de 50% dos agentes de usuário? Claro, no caso de não haver um mouse conectado
    • Do jeito que está escrito, em dispositivos com ponteiro, depende da posição do ponteiro. Se o ponteiro estiver dentro da área de 760px no centro, é ativado; se estiver fora, não
      Essa área é a largura que inclui a coluna de conteúdo e 20px de padding de cada lado. Então alguns usuários de teclado são capturados, enquanto alguns usuários de mouse, especialmente os que usam viewports grandes, não são
  • A ideia de que “não só coisas ruins como o Google Analytics, mas também Fathom e Plausible têm dificuldade para registrar atividade em navegadores com bloqueio de anúncios” ocorre, a meu ver, porque eles estão tentando viver, na prática, em um terreno baldio tóxico.
    Usuários como nós já se cansaram desse conceito inteiro, então acho que, se a análise via CSS se popularizar, também surgirão tentativas de contorná-la.

    • Por quê?
      Eu desbloqueei manualmente Piwik/Matomo, Plausible e Fathom no uBlock. Não vejo dano no que eles rastreiam nem em como rastreiam. E eles dão aos operadores de sites informações úteis para “melhorar o serviço”.
      Por exemplo, o Plausible coleta menos informações sobre mim do que logs comuns do nginx ou do Apache. Do ponto de vista de um blogueiro, é importante ver se um texto chegou ao HN, se foi linkado em algum lugar, que conteúdo é recebido como valioso e qual é ignorado. Assim dá para escrever o que as pessoas realmente querem ler e divulgar pelos canais em que isso pode de fato ser percebido.
    • Colocar o access.log do servidor web em um serviço de analytics não impede nada.
      Pelo contrário, como é praticamente impossível filtrar todo o tráfego de bots olhando apenas para o user agent, os números podem acabar inflados.
    • O que faz a web parecer um terreno baldio tóxico para mim são todos os tipos de anúncios. Rastreamento é uma questão muito mais sutil, mas, no longo prazo, o dano está na possibilidade de criarem um gêmeo digital sobre o qual se possa experimentar para descobrir a melhor forma de me manipular.
      Não sei quantas pessoas de fato temem isso. Imagino reações indo de “sim, é assustador” até “que absurdo, isso é só ficção científica”.
    • Isso me lembra o uMatrix, que conseguia bloquear até o carregamento de CSS.
    • Esse método não é mais difícil de bloquear do que a abordagem com JavaScript. No fim, é só bloquear requisições que vão para um determinado padrão de URL.
  • Isso é conhecido há décadas como pixel de rastreamento.

    • Também é usado em e-mails. Carregar uma imagem transparente de 1x1 é mais confiável do que disparar um evento de hover, mas bloqueadores de anúncios costumam bloquear essas imagens.
    • Exato. Mas fazer isso com CSS traz alguns aspectos interessantes. Usar :hover permite filtrar bots que não usam um WebDriver completo, ou seja, a maioria dos bots.
      Em certo sentido, talvez fosse melhor carregar via @import, com supports, um arquivo .css quase vazio. Bloqueadores de anúncios detectam muito bem pixels transparentes de rastreamento de 1px, mas talvez sejam menos propensos a bloquear arquivos .css para não quebrar o layout. Só que, nesse caso, a vantagem inteligente do :hover desaparece.
  • É uma pergunta genuína, mas tenho receio de que soe como um comentário desdenhoso: qual é o objetivo de coletar dados de analytics em um blog pessoal não comercial como o Bearblog parece ser?

    • Posso falar do ponto de vista de alguém que mantém blog de forma constante desde por volta de 2000 e sempre teve muito interesse em “estatísticas”.
      O principal motivo para se interessar por analytics é ver se os textos estão sendo lidos. Superficialmente, e em certo sentido, é por vaidade, mas na prática tem a ver com a conexão entre autor e leitor. Tenho curiosidade genuína sobre aquilo a que os leitores reagem e quero entregar mais disso. Esse “disso” pode ser o tema, o tom ou a extensão. Ajuda a ajustar o material ao meu público. No fim, posso escrever sobre uma dúzia de assuntos de vinte e quatro maneiras diferentes. Claro que escrevo sobre o que gosto, mas lapido para ressoar melhor com os leitores.
      Nesse sentido, analytics também é uma forma de conhecer os leitores. Em blogs com alto engajamento, a análise me dava algo como um retrato difuso dos leitores. Eu podia ver não só do que gostavam, mas também quando gostavam. Dava para saber se liam logo pela manhã, no horário do almoço ou tarde da noite, e isso ajudava a decidir publicar em certos horários, ou a ter mais confiança nessa decisão. Claro que eram todas informações imprecisas, mas ajudavam muito a me conectar de forma mais ativa com os leitores.
    • É por causa do ciclo de feedback. Ao contrário do que muita gente pensa, analytics não serve apenas para anúncios ou venda de dados, mas para analisar o desempenho do site e do conteúdo.
      Claro que pode ser usado para publicidade e pode ser abusado, mas, se eu quero feedback sobre o que estou fazendo, é essencial.
      Se 12 ou 12.000 pessoas leem o site, isso pode não ter valor monetário. Mas, do ponto de vista pessoal, é bom saber o que as pessoas querem ler vindo de mim, dá para sentir que o tempo gasto escrevendo foi bem aproveitado e, se eu quiser, posso ajustar na direção do que é mais popular.
    • Talvez seja curiosidade? Quero saber se alguém lê o que escrevo. Também é útil saber no que as pessoas têm interesse.
      Mesmo um blogueiro pessoal pode querer ajustar o conteúdo aos leitores. É bom saber que 500 pessoas leram um texto sobre um tema e só 3 leram outro.
  • Tentei fazer algo assim no começo deste ano, mas perdi a motivação enquanto criava a UI web. Minha abordagem não era CSS; era simplesmente carregar imagens falsas por meio de tags.
    https://github.com/nolytics

  • Por que não obter essas informações diretamente do servidor HTTP?

    • O post do blog explica assim:
      “Sempre existe a opção de analisar logs do servidor, e isso permite ter uma noção aproximada dos tipos de tráfego que acessam o servidor. Mas, em geral, todo tráfego de servidor parece igual. Tecnicamente, bots deveriam ter um user agent que os identifica como bots, mas, como tentam coletar informações fingindo ser uma ‘pessoa’ usando um navegador, quase nunca se identificam assim. Essencialmente, se você usar apenas logs de servidor para analytics, vai ter uma visão distorcida do tráfego por causa de crawlers de mecanismos de busca, scrapers e, agora, parsers baseados em GPT.”
    • Os bots ficam todos misturados.
    • É difícil se você opera em modelo serverless.
  • Como os dados de analytics são armazenados?
    Imagine que você tem um site de e-commerce e produtos que quer vender. Além de analytics, decidiu registrar diretamente em logs algumas ações, como visitas à página de detalhes de um produto estando logado. Então você quer armazenar coisas como ID do usuário, ID do produto e timestamp
    Na prática, como isso deve ser armazenado? De forma ingênua, achei que bastava colocar em uma tabela. O DBA perguntou por quanto tempo os dados seriam necessários, e respondi que pelo menos um mês. Ele disse tudo bem, e provavelmente agendou um job para mover dados mais antigos para outra tabela
    Na prática, como esses logs são armazenados e por quanto tempo são mantidos?

    • Se não for uma escala enorme, colocá-los em uma tabela do Postgres é totalmente aceitável. Mesmo em escala grande, dá para particionar a tabela por data ou por algum outro atributo adequado, para não precisar lidar com índices gigantescos
      Já fiz isso antes e, até chegar a algo em torno de 1 bilhão de linhas, nem precisei pensar em particionamento. Ainda assim, é melhor particionar antes disso. Essa experiência não foi agradável
    • Um banco de dados para analytics é melhor. Coisas como ClickHouse ou BigQuery
      Eles fazem agregações muito mais rapidamente e também lidam bem com muitas colunas esparsas. Por exemplo, um evento paid tem o atributo amount, enquanto um evento page_view tem o atributo url
    • Mantemos 13 anos de dados no MySQL. É uma escala de 5 milhões de visitantes por ano. Fazer consultas ali é sofrido, então também mantemos uma cópia no ClickHouse. O ClickHouse é realmente muito confortável para consultas
    • Usamos Postgres e TimescaleDB. Se o site de e-commerce não estiver na escala da amazon.com, funciona bem
      O ponto bom do TimescaleDB é que ele cuida da criação de views materializadas para agregações de interesse, como visualizações de produtos por hora. Se houver muitos eventos e você quiser evitar que o banco de dados cresça demais, também pode optar por “descartar” os eventos em si e manter apenas as agregações
    • ClickHouse