├── .gitignore ├── Dockerfile ├── README.md ├── app_1.py ├── app_2.py ├── app_3.py ├── app_4.py ├── app_5.py ├── app_6.py ├── app_7.py ├── app_8_postgres.py ├── exemplo.env ├── iphone_prices.db ├── pics └── 1731445656096.jfif └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | .env -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Usa uma imagem base do Python 2 | FROM python:3.12-slim 3 | 4 | # Define o diretório de trabalho dentro do contêiner 5 | WORKDIR /app 6 | 7 | # Copia o arquivo de dependências para o diretório de trabalho 8 | COPY requirements.txt . 9 | 10 | # Instala as dependências do Python 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | # Copia o código da aplicação para o contêiner 14 | COPY . . 15 | 16 | # Expõe a porta do PostgreSQL (opcional, caso o banco esteja no mesmo contêiner) 17 | EXPOSE 5432 18 | 19 | # Instrução CMD para rodar o aplicativo 20 | CMD ["python", "app_8_postgres.py"] 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
5 |6 | Nossa missão é fornecer o melhor ensino em engenharia de dados 7 |
8 | 9 | Bem-vindo a **Jornada de Dados** 10 | 11 | # Monitoramento de Preço com Web Scraping e Notificações no Telegram 12 | 13 | Este projeto realiza o monitoramento de preços de produtos em sites de e-commerce. Utilizando técnicas de web scraping, o projeto coleta preços e envia notificações no Telegram quando o valor atinge um limite específico definido pelo usuário. A aplicação é modular, dividida em partes para facilitar o desenvolvimento e a manutenção. 14 | 15 | ## Esse projeto faz parte do workshop de Web Scraping 16 | 17 | Assista ao vídeo completo aqui 18 | 19 | [](https://youtube.com/live/z1EOlFV8g7g) 20 | 21 | ## Arquitetura 22 | 23 | ```mermaid 24 | sequenceDiagram 25 | participant User as Usuário 26 | participant Bot as Bot Telegram 27 | participant Script as Script Principal 28 | participant DB as Banco de Dados (SQLite) 29 | participant ML as Mercado Livre 30 | 31 | User->>Script: Inicia o script 32 | Script->>ML: fetch_page() - Requisição para obter HTML da página 33 | ML-->>Script: Responde com HTML da página 34 | 35 | Script->>Script: parse_page() - Extrai informações de preço e produto 36 | Script->>DB: create_connection() - Conecta ao banco de dados 37 | Script->>DB: setup_database() - Cria tabela 'prices' se não existir 38 | Script->>DB: get_max_price() - Consulta maior preço registrado 39 | 40 | alt Se não houver preço registrado 41 | Script->>Bot: send_telegram_message() - "Novo preço maior detectado" 42 | Bot-->>User: Notificação via Telegram 43 | else Se houver preço registrado 44 | Script->>Script: Compara o preço atual com o maior preço registrado 45 | alt Se o preço atual for maior 46 | Script->>Bot: send_telegram_message() - "Novo preço maior detectado" 47 | Bot-->>User: Notificação via Telegram 48 | else Se o preço atual não for maior 49 | Script->>Bot: send_telegram_message() - "Maior preço registrado é X" 50 | Bot-->>User: Notificação via Telegram 51 | end 52 | end 53 | 54 | Script->>DB: save_to_database() - Salva informações de preço e produto 55 | Script->>Script: Aguarda 10 segundos antes de repetir o processo 56 | loop Loop contínuo 57 | Script->>ML: Requisição para atualizar preço 58 | ML-->>Script: Responde com novo preço 59 | Script->>Script: Processo de verificação e notificação se repete 60 | end 61 | ``` 62 | 63 | ## Bibliotecas Utilizadas e Explicação 64 | 65 | 1. **requests**: Usada para fazer requisições HTTP e obter o HTML das páginas web. 66 | 2. **BeautifulSoup (bs4)**: Utilizada para analisar e extrair informações específicas do HTML das páginas, como o preço do produto. 67 | 3. **schedule**: Biblioteca para agendar tarefas, permitindo verificar preços em intervalos regulares. 68 | 4. **pandas**: Facilita a manipulação de dados, permitindo salvar e carregar históricos de preços em arquivos CSV. 69 | 5. **sqlite3**: Um banco de dados SQLite leve, usado para armazenar e organizar dados de preços ao longo do tempo. 70 | 6. **python-telegram-bot**: Biblioteca para enviar mensagens ao Telegram, notificando o usuário quando o preço atinge um valor específico. 71 | 7. **python-dotenv**: Carrega variáveis de ambiente de um arquivo `.env`, onde são armazenadas informações sensíveis como o token e o chat ID do Telegram. 72 | 73 | ## Pré-requisitos 74 | 75 | 1. **Python 3.6+**: Certifique-se de ter o Python 3.6 ou superior instalado. 76 | 2. **Dependências**: Instale as bibliotecas listadas no arquivo `requirements.txt`. 77 | 78 | Para instalar as dependências, execute o comando: 79 | ```bash 80 | pip install -r requirements.txt 81 | ``` 82 | 83 | ## Configuração 84 | 85 | 1. **Configuração do Telegram**: Crie um bot no Telegram usando o BotFather e obtenha o token de autenticação. 86 | 2. **Arquivo `.env`**: Crie um arquivo `.env` na raiz do projeto e insira as credenciais do Telegram: 87 | ``` 88 | TELEGRAM_TOKEN=SEU_TOKEN_DO_TELEGRAM 89 | TELEGRAM_CHAT_ID=SEU_CHAT_ID 90 | ``` 91 | - Substitua `SEU_TOKEN_DO_TELEGRAM` com o token do seu bot. 92 | - Substitua `SEU_CHAT_ID` com o ID do chat onde você deseja receber notificações. 93 | 94 | 3. **Configuração do Banco de Dados**: O banco de dados SQLite será criado automaticamente na primeira execução. 95 | 96 | ## Estrutura dos Aplicativos 97 | 98 | ### `app_1`: Coletor de Dados com `requests` 99 | Esse módulo faz requisições HTTP para acessar o conteúdo HTML das páginas de produtos. Ele coleta o HTML bruto que será processado pelo `app_2`. 100 | 101 | ### `app_2`: Parser de HTML com `BeautifulSoup` 102 | Esse módulo recebe o HTML do `app_1` e utiliza o `BeautifulSoup` para extrair informações específicas, como o preço atual do produto. 103 | 104 | ### `app_3`: Agendamento de Tarefas com `schedule` 105 | Esse módulo usa `schedule` para definir a frequência com que o monitoramento de preços é executado. Por exemplo, pode ser configurado para verificar o preço a cada 10 minutos. 106 | 107 | ### `app_4`: Manipulação de Dados com `pandas` 108 | Esse módulo organiza os dados coletados e pode salvar o histórico de preços em um arquivo CSV para facilitar a análise e o armazenamento. 109 | 110 | ### `app_5`: Banco de Dados com `sqlite3` 111 | Esse módulo gerencia o banco de dados SQLite, criando tabelas e armazenando informações do histórico de preços. 112 | 113 | ### `app_6`: Comparação de Preços (`max_price`) 114 | Esse módulo compara o preço atual do produto com o `max_price` definido pelo usuário. Caso o preço esteja abaixo do limite, ele envia uma notificação usando o `app_7`. 115 | 116 | ### `app_7`: Envio de Notificação com Telegram 117 | Esse módulo utiliza a biblioteca `python-telegram-bot` para enviar uma mensagem ao Telegram informando que o preço atingiu o valor desejado. 118 | 119 | ## Como Executar 120 | 121 | 1. **Clone o Repositório**: 122 | ```bash 123 | git clone https://github.com/lvgalvao/IphoneProjectWebScraping 124 | cd IphoneProjectWebScraping 125 | ``` 126 | 127 | 2. **Instale as Dependências**: 128 | ```bash 129 | pip install -r requirements.txt 130 | ``` 131 | 132 | 3. **Configure o `.env`**: 133 | - Siga as instruções em "Configuração" e adicione o arquivo `.env` com as variáveis de ambiente para o bot do Telegram. 134 | 135 | 4. **Execute o Script**: 136 | ```bash 137 | python app_8_postgres.py 138 | ``` 139 | 140 | O projeto agora iniciará o monitoramento do preço de produtos, verificando em intervalos regulares e notificando o usuário via Telegram caso o preço atinja o valor desejado. 141 | 142 | Caso queira fazer um teste local 143 | 144 | 5. **Execute o Script 6 para um teste local**: 145 | ```bash 146 | python app_6.py 147 | ``` 148 | 149 | ## Migrando para Postgres 150 | 151 | Para migrar de SQLite para PostgreSQL, você pode usar a biblioteca `psycopg2` para conectar-se ao banco de dados PostgreSQL. Abaixo está o código atualizado para suportar o PostgreSQL. Vou explicar as mudanças e as etapas adicionais necessárias para configurar o ambiente. 152 | 153 | 1. Primeiro, instale o `psycopg2`: 154 | ```bash 155 | pip install psycopg2-binary 156 | ``` 157 | 158 | 2. Atualize o arquivo `.env` com as credenciais do PostgreSQL: 159 | ```env 160 | TELEGRAM_TOKEN=SEU_TOKEN_DO_TELEGRAM 161 | TELEGRAM_CHAT_ID=SEU_CHAT_ID 162 | POSTGRES_DB=nome_do_banco 163 | POSTGRES_USER=seu_usuario 164 | POSTGRES_PASSWORD=sua_senha 165 | POSTGRES_HOST=localhost 166 | POSTGRES_PORT=5432 167 | ``` 168 | 169 | ### Alterações Realizadas 170 | 171 | - **Substituição do SQLite pelo PostgreSQL**: 172 | - O módulo `sqlite3` foi substituído por `psycopg2`, que conecta-se ao PostgreSQL. 173 | - As variáveis de ambiente foram configuradas para receber as credenciais de conexão ao PostgreSQL. 174 | 175 | - **Criação da Tabela `prices`**: 176 | - Utilizamos a sintaxe SQL específica do PostgreSQL para a criação da tabela `prices`. 177 | 178 | - **Salvamento e Consulta de Dados**: 179 | - A função `get_max_price` consulta o maior preço registrado até o momento na tabela `prices` do PostgreSQL. 180 | - `save_to_database` salva o registro atual utilizando um `DataFrame` pandas. 181 | 182 | ### Observação 183 | Caso deseje simplificar, você pode substituir a função `save_to_database` para um `INSERT` direto ao invés de `pandas.to_sql`, caso tenha dificuldades com integração pandas e PostgreSQL. 184 | 185 | ## Docker 186 | 187 | Aqui estão os comandos para construir e executar o contêiner Docker com o `.env`: 188 | 189 | 1. **Construir a Imagem Docker**: 190 | Navegue até o diretório onde o `Dockerfile` está localizado e execute: 191 | 192 | ```bash 193 | docker build -t app_8 . 194 | ``` 195 | 196 | Esse comando cria uma imagem Docker chamada `app_8` usando o `Dockerfile` atual. 197 | 198 | 2. **Executar o Contêiner com as Variáveis de Ambiente do `.env`**: 199 | Para iniciar o contêiner e carregar as variáveis de ambiente do arquivo `.env`, use: 200 | 201 | ```bash 202 | docker run -d --env-file .env --name app_8_container app_8 203 | ``` 204 | 205 | - `-d`: Executa o contêiner em segundo plano (modo "detached"). 206 | - `--env-file .env`: Carrega as variáveis de ambiente definidas no arquivo `.env`. 207 | - `--name app_8_container`: Nomeia o contêiner como `app_8_container`. 208 | - `app_8`: Especifica a imagem que você criou no comando de build. 209 | 210 | Esse processo configurará o contêiner para rodar o `app_8.py` com as variáveis de ambiente do `.env`. 211 | 212 | ## Amazon 213 | 214 | Aqui está o passo a passo completo atualizado para configurar uma instância Ubuntu e executar seu projeto Docker, com todos os comandos usando `sudo` para evitar problemas de permissão. 215 | 216 | ### 1. Conectar à Instância EC2 217 | 218 | Conecte-se à sua instância Ubuntu na AWS via SSH: 219 | ```bash 220 | ssh -i "seu-arquivo.pem" ubuntu@seu-endereco-ec2 221 | ``` 222 | 223 | ### 2. Atualizar o Sistema 224 | 225 | Atualize o sistema e os pacotes: 226 | ```bash 227 | sudo apt update -y 228 | sudo apt upgrade -y 229 | ``` 230 | 231 | ### 3. Instalar Git 232 | 233 | Instale o Git para clonar o repositório do projeto: 234 | ```bash 235 | sudo apt install git -y 236 | ``` 237 | 238 | ### 4. Clonar o Repositório 239 | 240 | Clone o repositório do projeto no diretório `/home/ubuntu`: 241 | ```bash 242 | sudo git clone https://github.com/lvgalvao/IphoneProjectWebScraping.git 243 | cd IphoneProjectWebScraping 244 | ``` 245 | 246 | ### 5. Instalar o Docker 247 | 248 | #### 5.1 Instalar Dependências do Docker 249 | 250 | Primeiro, instale os pacotes necessários para adicionar o repositório do Docker: 251 | ```bash 252 | sudo apt install apt-transport-https ca-certificates curl software-properties-common -y 253 | ``` 254 | 255 | #### 5.2 Adicionar o Repositório Docker 256 | 257 | Adicione a chave GPG do Docker e o repositório oficial: 258 | ```bash 259 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 260 | echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 261 | ``` 262 | 263 | #### 5.3 Instalar Docker 264 | 265 | Atualize novamente os pacotes e instale o Docker: 266 | ```bash 267 | sudo apt update -y 268 | sudo apt install docker-ce docker-ce-cli containerd.io -y 269 | ``` 270 | 271 | #### 5.4 Iniciar e Habilitar o Docker 272 | 273 | Inicie o Docker e configure-o para iniciar automaticamente ao ligar o sistema: 274 | ```bash 275 | sudo systemctl start docker 276 | sudo systemctl enable docker 277 | ``` 278 | 279 | ### 6. Configurar o Arquivo `.env` com as Variáveis de Ambiente 280 | 281 | No diretório `/home/ubuntu/IphoneProjectWebScraping`, crie o arquivo `.env` para armazenar as variáveis de ambiente necessárias: 282 | 283 | ```bash 284 | sudo nano .env 285 | ``` 286 | 287 | Dentro do editor `nano`, insira as variáveis de ambiente do projeto: 288 | 289 | ```dotenv 290 | # Telegram Bot 291 | TELEGRAM_TOKEN=XXX 292 | TELEGRAM_CHAT_ID=XXX 293 | 294 | # PostgreSQL Database 295 | POSTGRES_DB=XXX 296 | POSTGRES_USER=XXX 297 | POSTGRES_PASSWORD=XXX 298 | POSTGRES_HOST=XXX 299 | POSTGRES_PORT=XXX 300 | ``` 301 | 302 | Pressione `Ctrl + X` para sair, `Y` para confirmar as alterações e `Enter` para salvar. 303 | 304 | ### 7. Construir a Imagem Docker 305 | 306 | No diretório do projeto, onde o `Dockerfile` está localizado, construa a imagem Docker usando `sudo`: 307 | 308 | ```bash 309 | sudo docker build -t iphone_project . 310 | ``` 311 | 312 | Esse comando cria uma imagem Docker chamada `iphone_project` com base no `Dockerfile`. 313 | 314 | ### 8. Executar o Contêiner com o Arquivo `.env` 315 | 316 | Agora que a imagem foi construída, execute o contêiner e carregue as variáveis de ambiente do `.env`: 317 | 318 | ```bash 319 | sudo docker run -d --env-file .env --name iphone_project_container iphone_project 320 | ``` 321 | 322 | Explicação dos parâmetros: 323 | - `-d`: Executa o contêiner em segundo plano. 324 | - `--env-file .env`: Carrega as variáveis de ambiente do arquivo `.env`. 325 | - `--name iphone_project_container`: Nome do contêiner. 326 | - `iphone_project`: Nome da imagem Docker criada. 327 | 328 | ### 9. Verificar os Logs do Contêiner 329 | 330 | Para garantir que o contêiner está rodando corretamente, você pode verificar os logs com: 331 | 332 | ```bash 333 | sudo docker logs iphone_project_container 334 | ``` 335 | 336 | Esse processo completo deve configurar sua instância Ubuntu com Git e Docker, permitir que você crie o `.env`, e rode o contêiner Docker do seu projeto com todos os comandos usando `sudo`. -------------------------------------------------------------------------------- /app_1.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | def fetch_page(): 4 | url = 'https://www.mercadolivre.com.br/apple-iphone-16-pro-1-tb-titnio-preto-distribuidor-autorizado/p/MLB1040287851#polycard_client=search-nordic&wid=MLB5054621110&sid=search&searchVariation=MLB1040287851&position=6&search_layout=stack&type=product&tracking_id=92c2ddf6-f70e-475b-b41e-fe2742459774' 5 | response = requests.get(url) 6 | return response.text 7 | 8 | # Teste da função 9 | if __name__ == '__main__': 10 | page_content = fetch_page() 11 | print(page_content) # Mostra os primeiros 500 caracteres do HTML 12 | -------------------------------------------------------------------------------- /app_2.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup # pip install beautifulsoup4 3 | 4 | def fetch_page(): 5 | url = 'https://www.mercadolivre.com.br/apple-iphone-16-pro-1-tb-titnio-preto-distribuidor-autorizado/p/MLB1040287851#polycard_client=search-nordic&wid=MLB5054621110&sid=search&searchVariation=MLB1040287851&position=6&search_layout=stack&type=product&tracking_id=92c2ddf6-f70e-475b-b41e-fe2742459774' 6 | response = requests.get(url) 7 | return response.text 8 | 9 | def parse_page(html): 10 | soup = BeautifulSoup(html, 'html.parser') 11 | product_name = soup.find('h1', class_='ui-pdp-title').get_text(strip=True) 12 | prices = soup.find_all('span', class_='andes-money-amount__fraction') 13 | old_price = int(prices[0].get_text(strip=True).replace('.', '')) 14 | new_price = int(prices[1].get_text(strip=True).replace('.', '')) 15 | installment_price = int(prices[2].get_text(strip=True).replace('.', '')) 16 | 17 | return { 18 | 'product_name': product_name, 19 | 'old_price': old_price, 20 | 'new_price': new_price, 21 | 'installment_price': installment_price 22 | } 23 | 24 | # Teste das funções 25 | if __name__ == '__main__': 26 | page_content = fetch_page() 27 | product_info = parse_page(page_content) 28 | print(product_info) 29 | -------------------------------------------------------------------------------- /app_3.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import time 4 | 5 | def fetch_page(): 6 | url = 'https://www.mercadolivre.com.br/apple-iphone-16-pro-1-tb-titnio-preto-distribuidor-autorizado/p/MLB1040287851#polycard_client=search-nordic&wid=MLB5054621110&sid=search&searchVariation=MLB1040287851&position=6&search_layout=stack&type=product&tracking_id=92c2ddf6-f70e-475b-b41e-fe2742459774' 7 | response = requests.get(url) 8 | return response.text 9 | 10 | def parse_page(html): 11 | soup = BeautifulSoup(html, 'html.parser') 12 | product_name = soup.find('h1', class_='ui-pdp-title').get_text(strip=True) 13 | prices = soup.find_all('span', class_='andes-money-amount__fraction') 14 | old_price = int(prices[0].get_text(strip=True).replace('.', '')) 15 | new_price = int(prices[1].get_text(strip=True).replace('.', '')) 16 | installment_price = int(prices[2].get_text(strip=True).replace('.', '')) 17 | 18 | timestamp = time.strftime('%Y-%m-%d %H:%M:%S') 19 | 20 | return { 21 | 'product_name': product_name, 22 | 'old_price': old_price, 23 | 'new_price': new_price, 24 | 'installment_price': installment_price, 25 | 'timestamp': timestamp 26 | } 27 | 28 | # Teste das funções 29 | if __name__ == '__main__': 30 | while True: 31 | page_content = fetch_page() 32 | product_info = parse_page(page_content) 33 | print(product_info) 34 | time.sleep(10) 35 | -------------------------------------------------------------------------------- /app_4.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import time 4 | import pandas as pd 5 | 6 | def fetch_page(): 7 | url = 'https://www.mercadolivre.com.br/apple-iphone-16-pro-1-tb-titnio-preto-distribuidor-autorizado/p/MLB1040287851#polycard_client=search-nordic&wid=MLB5054621110&sid=search&searchVariation=MLB1040287851&position=6&search_layout=stack&type=product&tracking_id=92c2ddf6-f70e-475b-b41e-fe2742459774' 8 | response = requests.get(url) 9 | return response.text 10 | 11 | def parse_page(html): 12 | soup = BeautifulSoup(html, 'html.parser') 13 | product_name = soup.find('h1', class_='ui-pdp-title').get_text(strip=True) 14 | prices = soup.find_all('span', class_='andes-money-amount__fraction') 15 | old_price = int(prices[0].get_text(strip=True).replace('.', '')) 16 | new_price = int(prices[1].get_text(strip=True).replace('.', '')) 17 | installment_price = int(prices[2].get_text(strip=True).replace('.', '')) 18 | 19 | timestamp = time.strftime('%Y-%m-%d %H:%M:%S') 20 | 21 | return { 22 | 'product_name': product_name, 23 | 'old_price': old_price, 24 | 'new_price': new_price, 25 | 'installment_price': installment_price, 26 | 'timestamp': timestamp 27 | } 28 | 29 | def save_to_dataframe(product_info, df): 30 | new_row = pd.DataFrame([product_info]) 31 | """Salva uma linha de dados no banco de dados SQLite usando pandas.""" 32 | df = pd.concat([df, new_row], ignore_index=True) 33 | return df 34 | 35 | # Teste das funções 36 | if __name__ == '__main__': 37 | # DataFrame para acumular os resultados 38 | df = pd.DataFrame() 39 | 40 | while True: 41 | # Faz a requisição e parseia a página 42 | page_content = fetch_page() 43 | product_info = parse_page(page_content) 44 | # Converte o dicionário em DataFrame e adiciona ao acumulado usando pd.concat 45 | df = save_to_dataframe(product_info, df) 46 | # Exibe o DataFrame atualizado 47 | print(df) 48 | # Aguarda 10 segundos antes da próxima execução 49 | time.sleep(10) -------------------------------------------------------------------------------- /app_5.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import time 4 | import sqlite3 5 | import pandas as pd 6 | 7 | def fetch_page(): 8 | url = 'https://www.mercadolivre.com.br/apple-iphone-16-pro-1-tb-titnio-preto-distribuidor-autorizado/p/MLB1040287851#polycard_client=search-nordic&wid=MLB5054621110&sid=search&searchVariation=MLB1040287851&position=6&search_layout=stack&type=product&tracking_id=92c2ddf6-f70e-475b-b41e-fe2742459774' 9 | response = requests.get(url) 10 | return response.text 11 | 12 | def parse_page(html): 13 | soup = BeautifulSoup(html, 'html.parser') 14 | product_name = soup.find('h1', class_='ui-pdp-title').get_text(strip=True) 15 | prices = soup.find_all('span', class_='andes-money-amount__fraction') 16 | old_price = int(prices[0].get_text(strip=True).replace('.', '')) 17 | new_price = int(prices[1].get_text(strip=True).replace('.', '')) 18 | installment_price = int(prices[2].get_text(strip=True).replace('.', '')) 19 | 20 | timestamp = time.strftime('%Y-%m-%d %H:%M:%S') 21 | 22 | return { 23 | 'product_name': product_name, 24 | 'old_price': old_price, 25 | 'new_price': new_price, 26 | 'installment_price': installment_price, 27 | 'timestamp': timestamp 28 | } 29 | 30 | def create_connection(db_name='iphone_prices.db'): 31 | """Cria uma conexão com o banco de dados SQLite.""" 32 | conn = sqlite3.connect(db_name) 33 | return conn 34 | 35 | def setup_database(conn): 36 | """Cria a tabela de preços se ela não existir.""" 37 | cursor = conn.cursor() 38 | cursor.execute(''' 39 | CREATE TABLE IF NOT EXISTS prices ( 40 | id INTEGER PRIMARY KEY AUTOINCREMENT, 41 | product_name TEXT, 42 | old_price INTEGER, 43 | new_price INTEGER, 44 | installment_price INTEGER, 45 | timestamp TEXT 46 | ) 47 | ''') 48 | conn.commit() 49 | 50 | def save_to_database(conn, data): 51 | """Salva uma linha de dados no banco de dados SQLite usando pandas.""" 52 | df = pd.DataFrame([data]) # Converte o dicionário em um DataFrame de uma linha 53 | df.to_sql('prices', conn, if_exists='append', index=False) # Salva no banco de dados 54 | 55 | # Teste das funções 56 | if __name__ == '__main__': 57 | # Configuração do banco de dados 58 | conn = create_connection() 59 | setup_database(conn) 60 | 61 | while True: 62 | # Faz a requisição e parseia a página 63 | page_content = fetch_page() 64 | product_info = parse_page(page_content) 65 | 66 | # Salva os dados no banco de dados SQLite 67 | save_to_database(conn, product_info) 68 | print("Dados salvos no banco:", product_info) 69 | 70 | # Aguarda 10 segundos antes da próxima execução 71 | time.sleep(10) 72 | 73 | # Fecha a conexão com o banco de dados 74 | conn.close() 75 | -------------------------------------------------------------------------------- /app_6.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import time 4 | import sqlite3 5 | import pandas as pd 6 | 7 | def fetch_page(): 8 | url = 'https://www.mercadolivre.com.br/apple-iphone-16-pro-1-tb-titnio-preto-distribuidor-autorizado/p/MLB1040287851#polycard_client=search-nordic&wid=MLB5054621110&sid=search&searchVariation=MLB1040287851&position=6&search_layout=stack&type=product&tracking_id=92c2ddf6-f70e-475b-b41e-fe2742459774' 9 | response = requests.get(url) 10 | return response.text 11 | 12 | def parse_page(html): 13 | soup = BeautifulSoup(html, 'html.parser') 14 | product_name = soup.find('h1', class_='ui-pdp-title').get_text(strip=True) 15 | prices = soup.find_all('span', class_='andes-money-amount__fraction') 16 | old_price = int(prices[0].get_text(strip=True).replace('.', '')) 17 | new_price = int(prices[1].get_text(strip=True).replace('.', '')) 18 | installment_price = int(prices[2].get_text(strip=True).replace('.', '')) 19 | 20 | timestamp = time.strftime('%Y-%m-%d %H:%M:%S') 21 | 22 | return { 23 | 'product_name': product_name, 24 | 'old_price': old_price, 25 | 'new_price': new_price, 26 | 'installment_price': installment_price, 27 | 'timestamp': timestamp 28 | } 29 | 30 | def create_connection(db_name='iphone_prices.db'): 31 | """Cria uma conexão com o banco de dados SQLite.""" 32 | conn = sqlite3.connect(db_name) 33 | return conn 34 | 35 | def setup_database(conn): 36 | """Cria a tabela de preços se ela não existir.""" 37 | cursor = conn.cursor() 38 | cursor.execute(''' 39 | CREATE TABLE IF NOT EXISTS prices ( 40 | id INTEGER PRIMARY KEY AUTOINCREMENT, 41 | product_name TEXT, 42 | old_price INTEGER, 43 | new_price INTEGER, 44 | installment_price INTEGER, 45 | timestamp TEXT 46 | ) 47 | ''') 48 | conn.commit() 49 | 50 | def save_to_database(conn, data): 51 | """Salva uma linha de dados no banco de dados SQLite usando pandas.""" 52 | df = pd.DataFrame([data]) # Converte o dicionário em um DataFrame de uma linha 53 | df.to_sql('prices', conn, if_exists='append', index=False) # Salva no banco de dados 54 | 55 | def get_max_price(conn): 56 | """Consulta o maior preço registrado até o momento.""" 57 | cursor = conn.cursor() 58 | cursor.execute("SELECT MAX(new_price), timestamp FROM prices") 59 | result = cursor.fetchone() 60 | if result and result[0] is not None: 61 | return result[0], result[1] 62 | return None, None 63 | 64 | # Função principal 65 | if __name__ == '__main__': 66 | # Configuração do banco de dados 67 | conn = create_connection() 68 | setup_database(conn) 69 | 70 | while True: 71 | # Faz a requisição e parseia a página 72 | page_content = fetch_page() 73 | product_info = parse_page(page_content) 74 | current_price = product_info['new_price'] 75 | 76 | # Obtém o maior preço já salvo 77 | max_price, max_price_timestamp = get_max_price(conn) 78 | 79 | # Comparação de preços 80 | if max_price is None or current_price > max_price: 81 | print(f"Preço maior detectado: {current_price}") 82 | max_price = current_price # Atualiza o maior preço 83 | max_price_timestamp = product_info['timestamp'] # Atualiza o timestamp do maior preço 84 | else: 85 | print(f"O maior preço registrado é {max_price} em {max_price_timestamp}") 86 | 87 | # Salva os dados no banco de dados SQLite 88 | save_to_database(conn, product_info) 89 | print("Dados salvos no banco:", product_info) 90 | 91 | # Aguarda 10 segundos antes da próxima execução 92 | time.sleep(10) 93 | 94 | # Fecha a conexão com o banco de dados 95 | conn.close() 96 | -------------------------------------------------------------------------------- /app_7.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import time 4 | import sqlite3 5 | import pandas as pd 6 | import asyncio 7 | from telegram import Bot 8 | import os 9 | from dotenv import load_dotenv 10 | 11 | load_dotenv() 12 | 13 | # Configurações do bot do Telegram 14 | TOKEN = os.getenv('TELEGRAM_TOKEN') 15 | CHAT_ID = os.getenv('TELEGRAM_CHAT_ID') 16 | bot = Bot(token=TOKEN) 17 | 18 | def fetch_page(): 19 | url = 'https://www.mercadolivre.com.br/apple-iphone-16-pro-1-tb-titnio-preto-distribuidor-autorizado/p/MLB1040287851#polycard_client=search-nordic&wid=MLB5054621110&sid=search&searchVariation=MLB1040287851&position=6&search_layout=stack&type=product&tracking_id=92c2ddf6-f70e-475b-b41e-fe2742459774' 20 | response = requests.get(url) 21 | return response.text 22 | 23 | def parse_page(html): 24 | soup = BeautifulSoup(html, 'html.parser') 25 | product_name = soup.find('h1', class_='ui-pdp-title').get_text(strip=True) 26 | prices = soup.find_all('span', class_='andes-money-amount__fraction') 27 | old_price = int(prices[0].get_text(strip=True).replace('.', '')) 28 | new_price = int(prices[1].get_text(strip=True).replace('.', '')) 29 | installment_price = int(prices[2].get_text(strip=True).replace('.', '')) 30 | 31 | timestamp = time.strftime('%Y-%m-%d %H:%M:%S') 32 | 33 | return { 34 | 'product_name': product_name, 35 | 'old_price': old_price, 36 | 'new_price': new_price, 37 | 'installment_price': installment_price, 38 | 'timestamp': timestamp 39 | } 40 | 41 | def create_connection(db_name='iphone_prices.db'): 42 | """Cria uma conexão com o banco de dados SQLite.""" 43 | conn = sqlite3.connect(db_name) 44 | return conn 45 | 46 | def setup_database(conn): 47 | """Cria a tabela de preços se ela não existir.""" 48 | cursor = conn.cursor() 49 | cursor.execute(''' 50 | CREATE TABLE IF NOT EXISTS prices ( 51 | id INTEGER PRIMARY KEY AUTOINCREMENT, 52 | product_name TEXT, 53 | old_price INTEGER, 54 | new_price INTEGER, 55 | installment_price INTEGER, 56 | timestamp TEXT 57 | ) 58 | ''') 59 | conn.commit() 60 | 61 | def save_to_database(conn, data): 62 | """Salva uma linha de dados no banco de dados SQLite usando pandas.""" 63 | df = pd.DataFrame([data]) # Converte o dicionário em um DataFrame de uma linha 64 | df.to_sql('prices', conn, if_exists='append', index=False) # Salva no banco de dados 65 | 66 | def get_max_price(conn): 67 | """Consulta o maior preço registrado até o momento.""" 68 | cursor = conn.cursor() 69 | cursor.execute("SELECT MAX(new_price), timestamp FROM prices") 70 | result = cursor.fetchone() 71 | if result and result[0] is not None: 72 | return result[0], result[1] 73 | return None, None 74 | 75 | async def send_telegram_message(text): 76 | """Envia uma mensagem para o Telegram.""" 77 | await bot.send_message(chat_id=CHAT_ID, text=text) 78 | 79 | async def main(): 80 | conn = create_connection() 81 | setup_database(conn) 82 | 83 | try: 84 | while True: 85 | # Faz a requisição e parseia a página 86 | page_content = fetch_page() 87 | product_info = parse_page(page_content) 88 | current_price = product_info['new_price'] 89 | 90 | # Obtém o maior preço já salvo 91 | max_price, max_price_timestamp = get_max_price(conn) 92 | 93 | # Comparação de preços 94 | if max_price is None or current_price > max_price: 95 | message = f"Novo preço maior detectado: {current_price}" 96 | print(message) 97 | await send_telegram_message(message) 98 | max_price = current_price 99 | max_price_timestamp = product_info['timestamp'] 100 | else: 101 | message = f"O maior preço registrado é {max_price} em {max_price_timestamp}" 102 | print(message) 103 | await send_telegram_message(message) 104 | 105 | # Salva os dados no banco de dados SQLite 106 | save_to_database(conn, product_info) 107 | print("Dados salvos no banco:", product_info) 108 | 109 | # Aguarda 10 segundos antes da próxima execução 110 | await asyncio.sleep(10) 111 | 112 | except KeyboardInterrupt: 113 | print("Parando a execução...") 114 | finally: 115 | conn.close() 116 | 117 | # Executa o loop assíncrono 118 | asyncio.run(main()) 119 | -------------------------------------------------------------------------------- /app_8_postgres.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import time 4 | import pandas as pd 5 | import asyncio 6 | from telegram import Bot 7 | import os 8 | from dotenv import load_dotenv 9 | import psycopg2 10 | from sqlalchemy import create_engine 11 | 12 | load_dotenv() 13 | 14 | # Configurações do bot do Telegram 15 | TOKEN = os.getenv('TELEGRAM_TOKEN') 16 | CHAT_ID = os.getenv('TELEGRAM_CHAT_ID') 17 | bot = Bot(token=TOKEN) 18 | 19 | # Configurações do banco de dados PostgreSQL 20 | POSTGRES_DB = os.getenv("POSTGRES_DB") 21 | POSTGRES_USER = os.getenv("POSTGRES_USER") 22 | POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD") 23 | POSTGRES_HOST = os.getenv("POSTGRES_HOST") 24 | POSTGRES_PORT = os.getenv("POSTGRES_PORT") 25 | 26 | # Cria o engine do SQLAlchemy para o PostgreSQL 27 | DATABASE_URL = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}" 28 | engine = create_engine(DATABASE_URL) 29 | 30 | def fetch_page(): 31 | url = 'https://www.mercadolivre.com.br/apple-iphone-16-pro-1-tb-titnio-preto-distribuidor-autorizado/p/MLB1040287851#polycard_client=search-nordic&wid=MLB5054621110&sid=search&searchVariation=MLB1040287851&position=6&search_layout=stack&type=product&tracking_id=92c2ddf6-f70e-475b-b41e-fe2742459774' 32 | response = requests.get(url) 33 | return response.text 34 | 35 | def parse_page(html): 36 | soup = BeautifulSoup(html, 'html.parser') 37 | product_name = soup.find('h1', class_='ui-pdp-title').get_text(strip=True) 38 | prices = soup.find_all('span', class_='andes-money-amount__fraction') 39 | old_price = int(prices[0].get_text(strip=True).replace('.', '')) 40 | new_price = int(prices[1].get_text(strip=True).replace('.', '')) 41 | installment_price = int(prices[2].get_text(strip=True).replace('.', '')) 42 | 43 | timestamp = time.strftime('%Y-%m-%d %H:%M:%S') 44 | 45 | return { 46 | 'product_name': product_name, 47 | 'old_price': old_price, 48 | 'new_price': new_price, 49 | 'installment_price': installment_price, 50 | 'timestamp': timestamp 51 | } 52 | 53 | def create_connection(): 54 | """Cria uma conexão com o banco de dados PostgreSQL.""" 55 | conn = psycopg2.connect( 56 | dbname=POSTGRES_DB, 57 | user=POSTGRES_USER, 58 | password=POSTGRES_PASSWORD, 59 | host=POSTGRES_HOST, 60 | port=POSTGRES_PORT 61 | ) 62 | return conn 63 | 64 | def setup_database(conn): 65 | """Cria a tabela de preços se ela não existir.""" 66 | cursor = conn.cursor() 67 | cursor.execute(''' 68 | CREATE TABLE IF NOT EXISTS prices ( 69 | id SERIAL PRIMARY KEY, 70 | product_name TEXT, 71 | old_price INTEGER, 72 | new_price INTEGER, 73 | installment_price INTEGER, 74 | timestamp TIMESTAMP 75 | ) 76 | ''') 77 | conn.commit() 78 | cursor.close() 79 | 80 | def save_to_database(data, table_name='prices'): 81 | """Salva uma linha de dados no banco de dados PostgreSQL usando pandas e SQLAlchemy.""" 82 | df = pd.DataFrame([data]) 83 | # Usa SQLAlchemy para salvar os dados no PostgreSQL 84 | df.to_sql(table_name, engine, if_exists='append', index=False) 85 | 86 | def get_max_price(conn): 87 | """Consulta o maior preço registrado até o momento com o timestamp correspondente.""" 88 | cursor = conn.cursor() 89 | cursor.execute(""" 90 | SELECT new_price, timestamp 91 | FROM prices 92 | WHERE new_price = (SELECT MAX(new_price) FROM prices); 93 | """) 94 | result = cursor.fetchone() 95 | cursor.close() 96 | if result and result[0] is not None: 97 | return result[0], result[1] 98 | return None, None 99 | 100 | async def send_telegram_message(text): 101 | """Envia uma mensagem para o Telegram.""" 102 | await bot.send_message(chat_id=CHAT_ID, text=text) 103 | 104 | async def main(): 105 | conn = create_connection() 106 | setup_database(conn) 107 | 108 | try: 109 | while True: 110 | # Faz a requisição e parseia a página 111 | page_content = fetch_page() 112 | product_info = parse_page(page_content) 113 | current_price = product_info['new_price'] 114 | 115 | # Obtém o maior preço já salvo 116 | max_price, max_price_timestamp = get_max_price(conn) 117 | 118 | # Comparação de preços 119 | if max_price is None or current_price > max_price: 120 | message = f"Novo preço maior detectado: {current_price}" 121 | print(message) 122 | await send_telegram_message(message) 123 | max_price = current_price 124 | max_price_timestamp = product_info['timestamp'] 125 | else: 126 | message = f"O maior preço registrado é {max_price} em {max_price_timestamp}" 127 | print(message) 128 | await send_telegram_message(message) 129 | 130 | # Salva os dados no banco de dados PostgreSQL 131 | save_to_database(product_info) 132 | print("Dados salvos no banco:", product_info) 133 | 134 | # Aguarda 10 segundos antes da próxima execução 135 | await asyncio.sleep(10) 136 | 137 | except KeyboardInterrupt: 138 | print("Parando a execução...") 139 | finally: 140 | conn.close() 141 | 142 | # Executa o loop assíncrono 143 | asyncio.run(main()) 144 | -------------------------------------------------------------------------------- /exemplo.env: -------------------------------------------------------------------------------- 1 | # Telegram Bot 2 | TELEGRAM_TOKEN=XXX 3 | TELEGRAM_CHAT_ID=XXX 4 | 5 | # PostgreSQL Database 6 | POSTGRES_DB=XXX 7 | POSTGRES_USER=XXX 8 | POSTGRES_PASSWORD=XXX 9 | POSTGRES_HOST=XXX 10 | POSTGRES_PORT=XXX 11 | -------------------------------------------------------------------------------- /iphone_prices.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgalvao/IphoneProjectWebScraping/8a54199620d19811aed81644366f8843282e6937/iphone_prices.db -------------------------------------------------------------------------------- /pics/1731445656096.jfif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgalvao/IphoneProjectWebScraping/8a54199620d19811aed81644366f8843282e6937/pics/1731445656096.jfif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | anyio==4.6.2.post1 2 | attrs==24.2.0 3 | Automat==24.8.1 4 | beautifulsoup4==4.12.3 5 | certifi==2024.8.30 6 | cffi==1.17.1 7 | charset-normalizer==3.4.0 8 | constantly==23.10.4 9 | cryptography==43.0.3 10 | cssselect==1.2.0 11 | defusedxml==0.7.1 12 | filelock==3.16.1 13 | greenlet==3.1.1 14 | h11==0.14.0 15 | httpcore==1.0.6 16 | httpx==0.27.2 17 | hyperlink==21.0.0 18 | idna==3.10 19 | incremental==24.7.2 20 | itemadapter==0.9.0 21 | itemloaders==1.3.2 22 | jmespath==1.0.1 23 | lxml==5.3.0 24 | numpy==2.1.3 25 | packaging==24.2 26 | pandas==2.2.3 27 | parsel==1.9.1 28 | Protego==0.3.1 29 | psycopg2-binary==2.9.10 30 | pyasn1==0.6.1 31 | pyasn1_modules==0.4.1 32 | pycparser==2.22 33 | PyDispatcher==2.0.7 34 | pyOpenSSL==24.2.1 35 | python-dateutil==2.9.0.post0 36 | python-dotenv==1.0.1 37 | python-telegram-bot==21.7 38 | pytz==2024.2 39 | queuelib==1.7.0 40 | requests==2.32.3 41 | requests-file==2.1.0 42 | Scrapy==2.11.2 43 | service-identity==24.2.0 44 | setuptools==75.4.0 45 | six==1.16.0 46 | sniffio==1.3.1 47 | soupsieve==2.6 48 | SQLAlchemy==2.0.36 49 | tldextract==5.1.3 50 | Twisted==24.10.0 51 | typing_extensions==4.12.2 52 | tzdata==2024.2 53 | urllib3==2.2.3 54 | w3lib==2.2.1 55 | zope.interface==7.1.1 56 | asttokens==2.4.1 57 | comm==0.2.2 58 | debugpy==1.8.6 59 | decorator==5.1.1 60 | executing==2.1.0 61 | ipykernel==6.29.5 62 | ipython==8.28.0 63 | jedi==0.19.1 64 | jupyter_client==8.6.3 65 | jupyter_core==5.7.2 66 | matplotlib-inline==0.1.7 67 | nest-asyncio==1.6.0 68 | parso==0.8.4 69 | prompt_toolkit==3.0.48 70 | psutil==6.0.0 71 | pure_eval==0.2.3 72 | Pygments==2.18.0 73 | pywin32==306 74 | pyzmq==26.2.0 75 | stack-data==0.6.3 76 | tornado==6.4.1 77 | traitlets==5.14.3 78 | wcwidth==0.2.13 79 | --------------------------------------------------------------------------------