Como criar um Script Python para pegar informações de maneira mais rápida
Publicado também na Medium
Bom dia, boa tarde ou boa noite, guys.
Na última semana eu tive a experiência de pegar um serviço como Freelancer. O serviço não era dificil, ele precisava atualizar um sistema que tinha 47.965 produtos registrados, adicionar imagens e descrição ao produto. O cliente falou que pegava as informações na internet uma a uma, mas queria fazer um jeito mais rápido.
Então logo pensei em fazer um (webscrapper) Script com Python pra acelerar toda essa busca de informações e de pois atualiza o sistema com estas informações coletadas. Como nada na vida é em vão, vou compartilhar como foi que eu fiz a parte de webscrapping de um jeito bem rápido e fácil, falando sobre alguns problemas que tive no meio do caminho, de quebra, vou falar sobre um pouco de PaiTaOn.
Fluxo do Script
Certo, antes de começar o código em si, é bom entender o fluxo para depois entender o porquê de algumas coisas. O que eu queria que o script fizesse:
0. Criar uma pasta onde serão armazenadas as imagens
- Ler o arquivo e encontrar a primeira linha, guardar o código de barras e o nome do produto
2. Entrar no Google e pesquiar por um código de barras ou um nome de produto
3. Pegar os resultados e encontrar o primeiro resultado que fosse compativel com alguns dos sites que eu pré selecionei, que tem boas imagens e descrições detalhadas do produto.
4. Localizar as tags da imagem principal e guardar a informação do “src” desta tag.
5. Localizar as tags de descrição do produto e guardar o texto destas tags como “descrição curta” e “descrição longa”
6. Baixar a imagem e salva-la em uma pasta separada.
7. Atualizar o arquivo xls, xlsx ou csv com as descrições e o caminho da imagem e salva-lo.
8. Repetir
Passo a Passo para Criar o Script
Eu uso uma distro linux no PC, então se algo não fizer muito sentido pra ti, basta pesquisar como faz com o windows que é sucesso.
1. Instalar as Bibliotecas Necessárias
Primeiro, precisamos garantir que todas as bibliotecas necessárias estejam instaladas. Podemos usar pip
para instalar essas bibliotecas:
# Importação de módulos do sistema operacional
import os # Fornece funções para interagir com o sistema operacional, como criar diretórios e verificar se arquivos existem
# Importação de módulos para manipulação de datas e URLs
from datetime import datetime # Fornece classes para manipular datas e horários
from urllib.parse import urlparse, parse_qs, urljoin # Fornece funções para manipular URLs e parâmetros de consulta
# Importação de módulos para manipulação de tempo e imagens
import time # Fornece funções para trabalhar com tempo e relógio
from PIL import Image # Fornece classes para manipular imagens
from io import BytesIO # Fornece classes para trabalhar com fluxos de bytes
# Importação de módulos para manipulação de texto e requisições HTTP
import re # Fornece funções para trabalhar com expressões regulares
import requests # Fornece funções para enviar requisições HTTP
# Importação de módulos para manipulação de HTML e dados
from bs4 import BeautifulSoup # Fornece classes para parsear e manipular HTML
import pandas as pd # Fornece classes para manipular dados em estruturas tabulares
# Importação de módulos para interface gráfica
import tkinter as tk # Fornece classes para criar interfaces gráficas
from tkinter import filedialog, messagebox, ScrolledText # Fornece classes para criar caixas de diálogo e outros componentes de interface
# Garantir que openpyxl esteja instalado
try:
import openpyxl # Fornece classes para trabalhar com planilhas do Excel
except ImportError:
import pip # Fornece funções para instalar pacotes Python
pip.main(['install', 'openpyxl']) # Instala o pacote openpyxl se não estiver instalado
2. Criar a Interface Gráfica
Utilizei tkinter
para criar uma interface gráfica simples que permite selecionar um arquivo de entrada e uma pasta de saída para as imagens:
# Função para selecionar um arquivo
def select_file():
# Abre uma caixa de diálogo para selecionar um arquivo
file_path = filedialog.askopenfilename(
title="Select file", # Título da caixa de diálogo
filetypes=[ # Tipos de arquivos permitidos
("XLSX files", "*.xlsx"), # Arquivos XLSX
("XLS files", "*.xls"), # Arquivos XLS
("CSV files", "*.csv") # Arquivos CSV
]
)
# Limpa o campo de entrada de arquivo
entry_file.delete(0, tk.END)
# Insere o caminho do arquivo selecionado no campo de entrada
entry_file.insert(0, file_path)
# Função para selecionar uma pasta
def select_folder():
# Abre uma caixa de diálogo para selecionar uma pasta
folder_path = filedialog.askdirectory(title="Select output folder")
# Limpa o campo de entrada de pasta
entry_folder.delete(0, tk.END)
# Insere o caminho da pasta selecionada no campo de entrada
entry_folder.insert(0, folder_path)
3. Definir Funções para Encontrar Imagens e Descrições
Criamos funções para encontrar a tag <img>
e as descrições dos produtos nos sites.
Para encontrar as imagens, eu usei 3 métodos: encontrar pelo id da tag, pela class ou pelo src. No final das contas, o que teve menor chance de erros foi usar usar uma expressão regular do src da imagem, pois as classes poderiam se repetir nas páginas algumas vezes, e eram raros os casos em que eu encontrava imagem com id, mas o padrão do src me garantia que iria encontrar a imagem.
Para encontrar as descrições, utilizei o método de procurar pelo seletor CSS específico
# Função para encontrar a tag de imagem em uma página web
def find_img_tag(soup, website):
# Verifica se o website tem um ID de busca especificado
if website['searchId']:
# Procura a tag de imagem com o ID especificado
img_tag = soup.find('img', {'id': website['searchId']})
if img_tag:
# Retorna a tag de imagem se encontrada
return img_tag
# Procura a tag de âncora com o ID especificado (caso a imagem esteja dentro de uma âncora)
img_tag = soup.find('a', id=website['searchId'])
if img_tag:
# Retorna a tag de âncora se encontrada
return img_tag
# Procura a tag de div com o ID especificado (caso a imagem esteja dentro de uma div)
div_tag = soup.find('div', {'id': website['searchId']})
if div_tag:
# Procura a tag de imagem dentro da div
img_tag = div_tag.find('img')
if img_tag:
# Retorna a tag de imagem se encontrada
return img_tag
# Verifica se o website tem uma classe de busca especificada
if website['searchClass']:
# Divide a classe de busca em uma lista de classes (caso haja múltiplas classes)
classes = [cls.strip() for cls in website['searchClass'].split('.')]
# Procura a tag de imagem com a classe especificada
img_tag = soup.find('img', attrs={'class': ' '.join(classes)})
if img_tag:
# Retorna a tag de imagem se encontrada
return img_tag
# Procura a tag de âncora com a classe especificada (caso a imagem esteja dentro de uma âncora)
img_tag = soup.find('a', class_=website['searchClass'])
if img_tag:
# Retorna a tag de âncora se encontrada
return img_tag
# Verifica se o website tem um padrão de URL de imagem especificado
if website.get('srcPattern'):
# Compila o padrão de URL de imagem em uma expressão regular
pattern = re.compile(website['srcPattern'])
# Procura todas as tags de imagem na página
for img in soup.find_all('img'):
# Obtém o atributo src da tag de imagem
src = img.get('src', '')
# Verifica se o atributo src corresponde ao padrão de URL de imagem
if pattern.match(src):
# Retorna a tag de imagem se encontrada
return img
# Retorna None se nenhuma tag de imagem for encontrada
return None
# Função para encontrar as descrições de um produto em uma página web
def find_desc(soup, website):
# Inicializa um dicionário para armazenar as descrições
desc = {'descCurta': '', 'descLonga': ''}
# Verifica se o website tem um seletor de descrição curta especificado
if website['descricaoCurta']:
# Procura a descrição curta usando o seletor especificado
descCurta = soup.select_one(website['descricaoCurta'])
# Verifica se a descrição curta foi encontrada
if descCurta:
# Obtém o texto da descrição curta e remove espaços em branco
desc['descCurta'] = descCurta.get_text(strip=True)
# Verifica se o website tem um seletor de descrição longa especificado
if website['descricao']:
# Procura a descrição longa usando o seletor especificado
descLonga = soup.select_one(website['descricao'])
# Verifica se a descrição longa foi encontrada
if descLonga:
# Obtém o texto da descrição longa e remove espaços em branco
desc['descLonga'] = descLonga.get_text(strip=True)
# Retorna o dicionário de descrições
return desc
4. Função Principal do Script
A função principal do script carrega o arquivo selecionado, percorre cada linha e busca imagens e descrições nos sites especificados:
# Função para executar o script
def run_script():
# Imprime uma mensagem indicando que o script está iniciando
print("Iniciando o script...")
log_area.insert(tk.END, "Iniciando o script...\n")
# Obtém os caminhos do arquivo e da pasta de saída
file_path = entry_file.get()
folder_path = entry_folder.get()
# Verifica se ambos os campos foram preenchidos
if not file_path or not folder_path:
# Exibe um erro se algum dos campos estiver vazio
messagebox.showerror("Error", "Please select both file and output folder")
return
# Verifica o tipo de arquivo selecionado
if file_path.endswith('.xlsx'):
# Lê o arquivo Excel usando a biblioteca pandas
df = pd.read_excel(file_path)
elif file_path.endswith('.xls'):
# Lê o arquivo Excel usando a biblioteca pandas com o engine xlrd
df = pd.read_excel(file_path, engine='xlrd')
elif file_path.endswith('.csv'):
# Lê o arquivo CSV usando a biblioteca pandas
df = pd.read_csv(file_path, delimiter=';') # importante verificar no arquivo CSV que você for usar o tipo de delimiter que ele usa. no meu caso foi o ;
else:
# Exibe um erro se o tipo de arquivo não for suportado
messagebox.showerror("Error", "Unsupported file type")
return
# Define a lista de websites permitidos para a busca
websites = [
# Site Farmácias Nissei
{
'url': 'https://www.farmaciasnissei.com.br/',
'earchId': '',
'earchClass': '',
'rcPattern': 'https://farmaciasnissei\.com\.br/media/.*/[0-9]+(?:-[0-9]+)?(?:_400x400)?\.(jpg|webp)',
'descricaoCurta': '',
'descricao': 'div#myTabContent'
},
# Adicione outros sites conforme necessário
]
# Cria um diretório para armazenar as imagens
current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
image_dir = os.path.join(folder_path, f"images_{current_time}")
os.makedirs(image_dir, exist_ok=True)
log_area.insert(tk.END, f"Pasta criada: {image_dir}...\n")
# Itera sobre as linhas do arquivo
for index, row in df.iterrows():
print(f"Processando linha {index+1} de {len(df)}")
log_area.insert(tk.END, f"Processando linha {index+1} de {len(df)}...\n")
codigo = str(row['GTIN/EAN']).strip().replace('.0', '') # Resgata o código de barras do produto
name = str(row['Descrição']).strip() # Resgata o nome do produto
laboratorio = str(row['Marca']).strip() # Resgata a Marca / Laboratório do produto. Não cheguei a usar esta opção
if 'imgPath' in df.columns:
imgPathColumn = df.loc[index, 'imgPath'] or None
else:
imgPathColumn = None
# Verifica se a imagem já existe
if not pd.isnull(imgPathColumn): # Um callback para verificar se aquela linha onde vou salvar o caminho da imagem á foi preenchida
print(f"Imagem já existe: {imgPathColumn}")
log_area.insert(tk.END, f"Imagem já existe: {imgPathColumn}\n")
continue # Pula pra próxima iteração
# Cria a URL de busca no Google
search_url = f"https://www.google.com/search?q={(codigo+name).strip().replace(' ', '+')}"
# Define o Headers da requisição. Importante para fazer pesquisas no google, já que a google bloqueia pesquisa de dispositivos que não disponibilizam o user agent no header
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}
log_area.insert(tk.END, f"Pesquisando... {search_url}...\n")
response = requests.get(search_url, headers=headers)
soup = BeautifulSoup(response.content, 'html.parser') # Lê o html recebido da pesquisa
time.sleep(2) # um sleep de 2 segundos para não bloquear as pesquisas por atividade suspeita
# Procura dentro dos resultados, se algum deles é um dos sites pré definidos anteriormente
link = None # Define o url do site que corresponde a pesquisa
web_found = None # Define a referência do site encontrado
# Itera sobre a lista de sites pré definidos para pesquisa
for website in websites:o link da página de destino
# Procura alguma tag 'a' que tenha o href que contenha um valor do url do meu site predefinido
link = soup.find('a', href=lambda t: website['url'] in t and 'google.com' not in t)
if link:
# Pega o href e trata a informação salvando a url e a query dessa url
link_href = link.get('href')
parsed_href = urlparse(link_href)
query_params = parse_qs(parsed_href.query)
if 'url' in query_params:
link_url = query_params['url'][0]
# Guarda a informação de qual site foi encontrado
web_found = website
else:
link_url = link_href
web_found = website
break
# Se o link não for encontrado, pula para iterar o próximo website pré-definido
if not link:
log_area.insert(tk.END, "Não encontramos nenhum url conhecido...\n")
continue
# Guarda o URL do link encontrado.
url = link_url
log_area.insert(tk.END, f"Primeiro resultado encontrado {url}...\n")
# Define a imagem encontrada
image_found = False
# Itera novamente a lista de websites pré-definidos,
for website in websites:
# chega se o site encontrado é o site que está sendo iterado para evitar trabalho desnecessário
if website['url'] != web_found['url']:
continue
# Define o tempo de delay entre tentativas e o numero de tentativas para acessar um site
max_retries = 3
retry_delay = 10
for retry in range(max_retries):
try:
# Tenta acessar o url guardado e define o tempo de espera para aguardar a respota
response = requests.get(url, timeout=20)
break
except requests.exceptions.RequestException as e:
if retry < max_retries - 1:
log_area.insert(tk.END, f"Retrying in {retry_delay} seconds...\n")
time.sleep(retry_delay)
else:
log_area.insert(tk.END, f"Failed to request {url} after {max_retries} retries...\n")
continue
# Recebe a resposta e faz o parse do html
soup = BeautifulSoup(response.content, 'html.parser')
# Usa a função de busca de imagem e busca de descrição para receber as informações necessárias
img_tag = find_img_tag(soup, website)
desc = find_desc(soup, website)
# Faz um pequeno filtro para o src, pois em algumas estruturas as imagens podem ser acessadas direto no servidor sem precisar fazer requisição de uma API. Quando isso acontece, preciso resgatar este endereço e acessar usando a própria url do site
if img_tag:
img_src = img_tag.get('src') or img_tag.get('data-src')
# Faz o download da imagem
if img_src.startswith(('http://', 'https://')):
response = requests.get(img_src)
img_data = response.content
else:
img_src = urljoin(url, img_src)
response = requests.get(img_src)
img_data = response.content
# Cria o arquivo da imagem e escreve o resultado recebido do download
img_path = os.path.join(image_dir, f"{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')+(codigo+name)}.jpg")
image = Image.open(BytesIO(img_data))
image.save(img_path)
image_found = True
log_area.insert(tk.END, f"Imagem baixada e salva em {img_path}...\n")
# Atualiza o arquivo planilha com o caminho da imagem e a descrição
df.loc[index, 'imgPath'] = img_path
if 'descCurta' not in df.columns:
df['descCurta'] = ""
if 'descLonga' not in df.columns:
df['descLonga'] = ""
df.loc[index, 'descCurta'] = desc['descCurta']
df.loc[index, 'descLonga'] = desc['descLonga']
log_area.insert(tk.END, f"Descrição curta: {desc['descCurta']}\n")
log_area.insert(tk.END, f"Descrição longa: {desc['descLonga']}\n")
break
if not image_found:
log_area.insert(tk.END, "Imagem não encontrada...\n")
if file_path.endswith('.xlsx'):
output_file = os.path.join(folder_path, f"output_{current_time}.xlsx")
df.to_excel(output_file, index=False)
else:
output_file = os.path.join(folder_path, f"output_{current_time}.csv")
df.to_csv(output_file, index=False, sep=';')
log_area.insert(tk.END, f"Processamento finalizado. Arquivo salvo em {output_file}\n")
print("Processamento finalizado.")
5. Executar o Script
Para executar o script, basta clicar nos botões da interface gráfica para selecionar o arquivo de entrada e a pasta de saída, e então clicar no botão para iniciar o processo:
if __name__ == "__main__":
# Cria a janela principal da aplicação
root = tk.Tk()
root.title("Image Downloader") # Define o título da janela
# Cria e posiciona um rótulo para a seleção do arquivo de entrada
tk.Label(root, text="Select input file:").grid(row=0, column=0, padx=10, pady=5)
# Cria e posiciona uma caixa de entrada de texto para exibir o caminho do arquivo selecionado
entry_file = tk.Entry(root, width=50)
entry_file.grid(row=0, column=1, padx=10, pady=5)
# Cria e posiciona um botão que, ao ser clicado, abre a caixa de diálogo para selecionar o arquivo
tk.Button(root, text="Browse...", command=select_file).grid(row=0, column=2, padx=10, pady=5)
# Cria e posiciona um rótulo para a seleção da pasta de saída
tk.Label(root, text="Select output folder:").grid(row=1, column=0, padx=10, pady=5)
# Cria e posiciona uma caixa de entrada de texto para exibir o caminho da pasta selecionada
entry_folder = tk.Entry(root, width=50)
entry_folder.grid(row=1, column=1, padx=10, pady=5)
# Cria e posiciona um botão que, ao ser clicado, abre a caixa de diálogo para selecionar a pasta
tk.Button(root, text="Browse...", command=select_folder).grid(row=1, column=2, padx=10, pady=5)
# Cria e posiciona uma área de texto rolável para exibir logs e mensagens de status
log_area = ScrolledText(root, wrap=tk.WORD, width=80, height=20)
log_area.grid(row=2, column=0, columnspan=3, padx=10, pady=5)
# Cria e posiciona um botão que, ao ser clicado, inicia a execução do script
tk.Button(root, text="Start", command=run_script).grid(row=3, column=0, columnspan=3, padx=10, pady=5)
# Inicia o loop principal da aplicação, mantendo a janela aberta
root.mainloop()
Conclusão
Este script é um jeito prático de buscar e baixar imagens de produtos junto com suas descrições, e salvá-las em um arquivo Excel ou CSV. Ele pode ser adaptado para diferentes sites e necessidades específicas que você tiver, inclusive já penso em fazer um pra automatizar apostas na Bet kkkkkk
Enfim, espero que este tutorial algum dia seja útil pra vocês algum dia.