Open turicas opened 4 years ago
Sobre a solução A, poderia também usar o endereço IP pra evitar ter que criar chaves. Se quiser evitar limitações pra pessoas com mesmo IP (numa Universidade, por exemplo), pode pegar uma combinação do IP e o user-agent.
Isso só limita a quantidade de acessos por minuto. Se alguém realmente precisar usar a API pra pegar os dados, não tem problema. Só vai ser na velocidade que a gente estabelecer.
Achei este projeto que implementa isso https://django-ratelimit.readthedocs.io/en/stable/. Ele usa o Django Cache, que pode ser configurado pra colocar na memória ou usar Memcached/Redis.
No futuro, quando for realmente implementar api keys e outras funcionalidades, sugeriria dar uma olhada no Kong (https://konghq.com/kong/).
Acho a solução A a de menor custo de implementação incialmente. Isso porque o Django Rest Framework já possui uma interface de throttling que dá bastante conta do recado. Já usei em outros projetos e funciona desde uma simples entrada no settings.py
até customização endpoint a endpoint.
O benefício do DRF é que não precisamos implementar nada de token, api key ou coisas do tipo. Aqui tem mais informações sobre como os clientes são identificados.
Atualizações sobre essa issue:
@berinhard , como anda essa issue? Pela minha pouca experiencia com crawlers , ja tive dificuldades de transpassar sites com gerenciamento de acesso pela cloudflare , pode ser uma saida tambem.
@turicas podemos fechar essa issue dado a introdução do django-ratelimit
e também do uso do controle de throttling via Django Rest Framework, né?
@turicas podemos fechar essa issue dado a introdução do
django-ratelimit
e também do uso do controle de throttling via Django Rest Framework, né?
Acho melhor ainda não. Parece que vamos ter que mudar a estratégia de bloqueio do ratelimit, pois estão usando proxies e user-agents diferentes. :-/ Estou pensando que talvez valha a pena fazer o acesso à API apenas para quem está autenticado e limitar muito o acesso não autenticado ao site (e limitar menos para os autenticados).
Analisando os logs de acesso, vi que diversas requisições provavelmente abusivas são feitas na API e na interface do site, como por exemplo:
Um sinal que pode ajudar a detectar essas requisições são números de páginas muito grandes, como
/api/dataset/covid19/caso/data/?&format=json&page=52
- nas últimas 24 horas esse endpoint foi o que mais teve hits no backend (!).O objetivo aqui é limitar o uso abusivo para preservar os recursos do servidor (atualmente a concorrência por processamento está grande e algumas páginas estão lentas). A API foi feita para facilitar o trabalho de quem precisa eventualmente fazer algum filtro nos dados e não quer/precisa baixar o dataset completo, inclusive para utilização diretamente em alguma interface frontend sem a necessidade de um backend fornecer os dados (pegando diretamente da API do Brasil.IO), mas se algumas pessoas utilizam a API de forma abusiva (ex: percorrendo todas as páginas), essas pessoas prejudicam a abertura do site para todas as outras (o percentual de cache na CDN diminui, aumenta o processamento do Django e do PostgreSQL etc.)
Possíveis Soluções
Pensei, inicialmente, em três possibilidades para resolver o problema. Acredito que a melhor solução seria B & C. cc @berinhard
Solução A: Limitar a quantidade de requisições na API por cliente
Esse é o padrão do mercado, mas acho que pode ser ruim por vários motivos, como: A.1- Para que conseguíssemos rastrear as requisições, todas as pessoas que acessam a API teriam que se cadastrar e gerar um token (ou passar a enviar um cookie); A.2- Teríamos mais tarefas para executar no backend a cada requisição na API (gravar quantas requisições cada token fez, checar limites etc.); A.3- Limitaríamos clientes que realmente precisam fazer muitas requisições e não estão abusando da API.
Solução B: Limitar a quantidade de páginas que podem ser consultadas
Poderíamos estabelecer um limite, por exemplo: no máximo será permitido pegar via API os 1.000.000 primeiros registros (só para referência: a funcionalidade "baixar como CSV" da interface permite hoje download de, no máximo, 350.000 registros). Na API cada página tem 1.000 registros por padrão (esse número pode ser alterado com o
page_size
, para até 10.000), então isso quer dizer que aceitaríamos requisições em quepage * page_size <= 1_000_000
, caso contrário devolveríamos um erro 4xx para o cliente, com um JSON explicando o motivo do erro, um link para baixar os dados completos e outro link para a documentação da API. Dessa forma, não afetaríamos os clientes que já acessam a API (não precisariam se cadastrar, gerar token etc.) e os que acessam de maneira abusiva receberiam o erro, educando-os sobre as melhores práticas de acesso à API. Isso poderia valer para a interface também: em geral não faz muito sentido uma pessoa acessar a página 200 de uma consulta: ou ela vai filtrar por algo bem específico e já ter o resultado na tela (e copiar/colar do próprio navegador), ou vai filtrar por filtros que devolvem muitos registros e vai baixar o CSV com esse recorte (só lembrando que na interface a quantidade de itens por página é de 50). O bônus pra essa abordagem é que evitaríamos consultas com oOFFSET
muito grande no PostgreSQL (essas consultas podem ser bastante custosas).Solução C: limitar os User-Agents aceitos na interface
Essa solução poderia ser utilizada em conjunto com uma das duas acima. Se alguém está fazendo scraping na interface do Brasil.IO, claramente está utilizando o serviço de forma inadequada, dado que a API existe para isso. Bloqueando as requisições feitas para a interface com uma mensagem de erro educativa, conseguimos ensinar às essas pessoas que devem utilizar a API (seria legal termos uma página de referência no site para linkar na resposta dessas requisições - essa página poderia ter explicações sobre a API). Lista de User-Agents que poderíamos bloquear nesse caso (o ideal seria pegar por tudo que vem antes da "/", para pegar qualquer versão):