gasolina: raspagem e processamento dos preços dos combustíveis disponibilizados pelo PROCON de Joinville/SC
O PROCON de Joinville/SC, disponibiliza, por meio do seu site, o preço dos combustíveis em todos os postos de gasolina do município.
Assim, o objetivo deste projeto é coletar esses dados e alimentar um banco de dados, a partir do qual é gerado um dashboard e tabelas nas quais é possível fazer a filtragem por bairro.
O back-end do projeto é estruturado em:
- scraper: faz a raspagem do site do PROCON e determina o arquivo PDF a ser baixado.
- parser: processa o arquivo PDF, carregando os valores dele no banco de dados.
- api: uma API utilizando o fastapi, com um front-end simples.
O front-end é implemetado no diretório templates dentro de api, usando Bootstrap, bootstrap-table e chart.js.
- Para o scraper:
- Python 3.11 ou mais recente;
- requests;
- BeautifulSoup.
- Para o parser:
- Para a API:
- FastAPI
- Para o front-end:
- não há nenhuma dependência em específico
- TODO: ver o uso de um ORM, substituindo o uso de SQL puro.
-
Para rodar o scraper dentro de um container:
- Construir o container - usei o podman, mas é igual para o Docker - a partir da raiz:
podman build -t docker-scraper -f scraper/Dockerfile . - Como o scraper escreve para um diretório, é preciso executar o container indicando onde gravar os dados.
podman run -d -v [local onde gravar os dados]:/app/data docker-scraper:latestÉ ideal que o local seja um caminho absoluto, para evitar ambiguidades.
- Construir o container - usei o podman, mas é igual para o Docker - a partir da raiz:
-
Para rodar o parser:
- No diretório
parser, rodar o scriptrun_parser.sh, fornecendo um arquivo adequado como parâmetro.
- No diretório
-
Para rodar a api durante o desenvolvimento:
- No diretório
api, rodarfastapi dev main.pypara o modo de desenvolvedor.
- No diretório
-
Para rodar em ambiente de desenvolvimento, com um BD separado:
- Setar a variável de ambiente
DB_PATHpara o nome do BD a ser utilizado. Se essa variável não estiver setada, presume-se que estamos usando o BD completo.
- Setar a variável de ambiente
Primeiramente, é preciso construir o container: podman build -t gasolina-api -f api/Dockerfile ..
Para facilitar o deploy, adicionei a máquina remota no podman system connection add REMOTE [ENDEREÇO DA MÁQUINA]:22/usr/lib/systemd/user/podman.socket e, então, basta executar o comando podman image scp gasolina-api:latest REMOTE::. É preciso que o acesso via ssh a essa máquina remota esteja sendo feito por chave e não por senha.
Em seguida, na máquina remota, é preciso executar o comando podman run -dt -v [LOCAL ONDE FICA O BD]:/data:Z --name gasolina-api -p 8000:8000 --replace gasolina-api para subir o servidor. O local onde fica o BD deve ser configurado conforme o servidor.
No servidor já deverá existir um proxy reverso, como o Apache ou o nginx, operacional. Ele irá receber as requisições na porta 80 e encaminhar para a porta 8000.
Um mínimo exemplo de configuração do nginx é:
server {
server_name [URL DA APLICAÇÃO];
listen 80;
listen [::]:80;
location / {
proxy_pass http://localhost:8000;
include uwsgi_params;
}
}
sendo preciso, também, gerar um certificado (se estivermos usando o Let's Encrypt, por exemplo) e configurar o nginx corretamente. Não vou entrar em detalhes aqui, porque cada configuração do nginx será diferente.
Para gerenciar a execução do container, temos duas opções:
-
A forma "manual", usando o
podman run, como visto acima. Normalmente é usável em desenvolvimento. -
Escrever um arquivo
.container, para uso com quadlet, o equivalente ao Docker Compose no podman. Isso pode ser obtido criando-se um arquivogasolina.containerno diretório home do usuário sob o qual a aplicação irá executar, cujo conteúdo poderá ser, minimamente:[Container] Image=localhost/gasolina-api:latest AutoUpdate=registry PublishPort=8000:8000 Network=host Volume=/home/renan/gasolina/data:/data:Z [Service] Restart=always [Install] WantedBy=default.target
Novamente, as configurações irão variar conforme o caso de uso. Feito o deploy, é preciso reiniciar o container (no caso de usarmos quadlet, basta reiniciar o serviço).
(em nenhuma ordem)
- Automatizar o scraping.
- Fazer a raspagem comunicar via API em vez de chamadas SQL diretamente no BD. Já funciona, mas ainda falta simplificar o código e fazer em batch em vez de uma entrada por vez.
- Elaborar uma forma de, do zero, criar uma carga inicial de dados (talvez usando os PDFs de 2023 a 2025) e construir o BD sem precisar fornecer um binário no repositório. Parcialmente resolvido, com "dump" do BD.
Atualmente, a imagem está muito grande. Ver se eu consigo reduzir o tamanho dela.Resolvido usando Alpine, e não Debian.- Integrar os testes com o container.
- Configurar Actions para rodar os testes e fazer o deploy automaticamente.
- Adicionar autenticação e logging.
- Fazer com que seja possível usar outro BD (ex. PostgreSQL) no back-end.
- Documentar os endpoints da API.
- Integração com o Google Maps ou OpenStreetMap.
- Usar TypeScript e algum framework, como React ou vue, no front-end; ou explorar outras bibliotecas para geração de tabelas.
- Mais análises (preço médio, máximo, mínimo, etc...).
- A tabela fornecida pela prefeitura é inconsistente, com postos repetidos ou que mudaram de nome. Isso irá influenciar na estrutura do BD.
Licenciado sob a licença MIT; ver o arquivo LICENSE para mais detalhes.