domingo, 3 de abril de 2022

Criando seu próprio Kubernetes Cluster com Kind

Índice

 

Introdução

Com a crescente migração das empresas para o ambiente cloud, a necessidade de utilização de containers se tornou cada vez maior. Sistemas com arquiteturas mais complexas se tornam mais facies de manter e integrar através de ferramentas de contêineres como Docker. Contêineres trazem simplicidade quando pensamos em unidades, mas o que acontece quando temos que gerenciar o ciclo de vida de vários contêineres ao mesmo tempo? Nesse caso, precisamos de um orquestrador de contêineres e há diversas opções (AWS EKS, Open Shift, Nomad, Docker Swarm), porém vamos falar do mais popular de todos, Kubernetes.

Kubernetes

O Kubernetes é uma plataforma para gerenciamento de cargas de trabalho e serviços distribuídos em contêineres, que facilita tanto a configuração declarativa quanto a automação.

Para publicar um contêiner no kubernetes podemos utilizar a ferramenta kubectl que é a ferramenta oficial de linha de comando. Você pode encontrar mais detalhes neste link.

kubectl apply -f ./aplicacao.yaml

Os recursos que queremos publicar no kubernetes podem ser descritos em arquivos YAML como no exemplo abaixo

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

Não vamos aprofundar muito no kubernetes pois não é o objetivo deste artigo.

Como testar arquivos do kubernetes?

Uma parte importante do ciclo de desenvolvimento são os testes. É muito importante que a gente consiga testar inclusive os arquivos que descrevem os recursos que vamos publicar no kuberntes.

O que é o Kind?

kind é uma ferramenta para executar clusters locais do Kubernetes usando “nós” em contêiner do Docker.

Isso significa que podemos iniciar um cluster kubernetes no docker instalado na máquina e assim conseguir testar nossos arquivos.

Como Funciona?

De maneira simples, o kind cria um container docker que irá representar o cluster kubernetes.



Mostrando na prática

Instalando o kind

Há diversas formas de instalar o kind e os detalhes podem ser vistos aqui.

Exemplo utilizando o brew para instalar no MAC:

brew install kind

Criando cluster

A forma mais simples é indo direto no comando create

kind create cluster

Também é possível criar a partir de um arquivo de configuração:

kind: Cluster
apiVersion: kind.x-k8s.io/v1
name: cluster-kind
kind create cluster --config cluster.yaml

Obtendo informações do cluster

O comando abaixo lista os clusteres que foram criados.

kind get clusters

O comando abaixo detalha um cluster específico

kubectl cluster-info --context cluster-kind

Deletando o cluster

Para deletar o cluster é tão simples quanto cirar:

kind delete cluster --name cluster-kind

Configurando ingress controller

Esse exemplo mostra como criar um Ingress Controller que será responsável por estabelecer uma conexão entre o ambiente local o cluster kubernetes.

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: cluster-kind
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"    
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP

Agora basta criar o cluster

kind create cluster --config cluster.yaml

Publicando recursos no kubernetes local

Os comandos abaixo dependem da instalação do kubectl e como não é escopo detalhar o kubernetes, vou coloca-los apenas para fins de exemplo.

kind: Pod
apiVersion: v1
metadata:
  name: kind-app
  labels:
    app: kind-app
spec:
  containers:
  - name: kind-app
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=Hello World! This is a Kubernetes with kind App"
---
kind: Service
apiVersion: v1
metadata:
  name: kind-service
spec:
  selector:
    app: kind-app
  ports:
  # Default port used by the image
  - port: 5678
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kind-ingress
spec:
  rules:
  - http:
      paths:
      - pathType: Prefix
        path: "/kind"
        backend:
          service:
            name: kind-service
            port:
              number: 5678
---
kubectl apply -f kind-app.yml

Agora é só acessar o link http://localhost:5678/kind para ver o resultado

Outros recursos

Para aplicar outros recursos como config-map, secrets, deployments, cron-job, etc basta seguir a mesma ideia.

Conclusão

Isso foi uma introdução bem superficial ao kind, apenas para sentir o gostinho. Há muito mais para explorar. O mais importante é entender que o kind permite a realização de testes locais de todos os artefatos antes mesmo de executa-los no ambiente real.

Referência

terça-feira, 1 de março de 2022

Google Docs API - Criando e atualizando planilhas.

Índice



 

Introdução

Esse artigo vai te ensinar a criar uma planilha no google sheets usando a linguagem python com as libs gspread e a lib oficial do google google-api-python-client.

Vamos usar o CSV gerado no post Selenium com Python para criar uma planilha no Google Drive.

Para isso, será necessário criar um projeto no google cloud, configurar o acesso a API do Google Sheets e configurar uma credencial de acesso para o nosso programa, que será feito em python.

Criando um App no Google

O primeiro passo é criar um Projeto no Google Cloud clicando aqui. Para criar um projeto, no canto superior esquerdo, clique em → IAM & Admin → Create a Project, como mostra a figura:



Dê um nome para seu projeto e aperte em Create:



Em seguida, precisamos configurar o nosso projeto para permitir que ele acesse a API do Google Sheets. Para isso acesso o menu → API & Services → Library



No campo de pesquisa, digite "sheets" e aperte "enter" para pesquisar a API que queremos.


Selecione a API "Google Sheets API”:


Aperte no botão “enable”



Agora temos que repetir esse último passo mas desta vez para a API do Google Drive. Acesse novamente o menu → API & Services → Library e pesquise:


Selecione a API "Google Drive API”:


Aperte no botão "enable”


Gerando Credenciais de acesso.

Agora que temos tudo pronto, precisamos gerar as credencias para que a nossa aplicação possa se autenticar na nossa conta e com isso criar a manipular as planilhas.

O Google permite dois tipos de autenticação:

  • User authentication: Esse tipo de autenticação irá solicitar ao usuário que autentique com sua conta google sempre que o programa for executado. É um modelo OAutho2
  • App authentication: Nesse tipo de autenticação o usuário com a conta google permite que o programa se autentique em seu nome utilizando credenciais pré configuradas.

Não é escopo desse posto aprofundar no mecanismo de autenticação do Google. Se quiser se aprofundar mais, clique aqui e acesse a documentação oficial.

Para criar a credencial de acesso, acesse o menu → API & Services → Credentials


Na tela que abrir, aperte no botão "+ CREATE CREDENTIALS” e em seguida "OAuth Client ID”:


Na tela que abrir, aperte no botão abaixo para configurar um tela de consentimento, que será exibida para o usuário consentir que o nosso programa terá acesso a sua planilha:


Agora você tem duas opções:

  • Internal: Disponível apenas para usuários do Google Workspace que é a versão para empresa das ferramentas do Google.
  • External: É a opção para o público em geral.

Nesse post, vou seguir com a opção external, mas caso você possua conta no Google Workspace, pode selecionar a primeira opção.

Aperte no botão "create”.


Na tela que aparecer, preencha as informações obrigatórias e aperte "Save And Continue”


Essa é a parte mais importante, que é configurar os escopos da nossa credencial. Em outras palavras, aqui vamos definir quais permissões nosso programa irá precisar para executar seu trabalho. Essas opções serão apresentadas para o usuário dizer se permite ou não que a nossa aplicação tenha esses acessos.

Aperte no botão ADD OR REMOVE SCOPES



Você pode pesquisar ou adicionar manualmente os escopos necessários. Como a pesquisa não é muito boa, vamos fazer manualmente. Vá até a opção "Manually add Scopes" e adicione os seguintes escopos:


Quando finalizar, aperte no botão "update" e em seguida no botão "Save and continue".

O próximo passo é necessário por que o Google entende que você que o seu programa será disponibilizado para o público em geral e por tanto precisa passar pela avaliação técnica de uma equipe da Google. Isso serve para google garantir que não é um programa de fraude e nem nada do tipo.

Enquanto seu programa não é autorizado pela equipe técnica do google, você pode cadastrar alguns usuários de teste que terão acesso imediato ao programa.

Para configurar quais usuários de teste terão acesso a esse credencial aperte no botão "+ add users” e adicione o seu usuário, depois aperte no botão "save and continue”.


Agora volte na tela abaixo para finalmente criar a credencial:


Preencha os campos e aperte no botão "create":


Na próxima tela, faça download do json que contem as credenciais e guarde pois vamos utilizar no código:


Pronto, sua credencial está criado e devidamente configurado.

Uffa! vamos ao código!

Bibliotecas Python

Para o nosso exemplo, vamos utilizar 4 bibliotecas:

As 3 primeiras bibliotecas são as bibliotecas oficiais da google, com elas é possível realizar todas as operações que precisamos (copiar, criar, editar e excluir documentos), porém essas libs tem uma estrutura verbosa e difícil de utilizar, então vamos utiliza-las apenas para gerenciar nossas credencias e token.

Para realizar as operações nas planilhas, vamos utilizar uma biblioteca bem simples mas ao mesmo tempo muito completa chamada gspread. Essa biblioteca é uma interface de comunicação com a API do Google Sheets, abstraindo toda sua complexidade e verbosidade, tornando nosso desenvolvimento mais rápido e fácil. Com ela é possível realizar diversas operações como:

  • abrir planilhas por título, chave e url.
  • ler, escrever e formatar um conjunto de celulas
  • compartilhar a planilha com outros usuários
  • fazer atualizações em lote
  • Diversas outras operações.

Para saber mais sobre como utilizar a biblioteca, basta clicar aqui e ler a documentação no github.

Codificando nosso exemplo

Vamos relembrar o nosso exemplo: vamos criar uma planilha no Google Drive que servirá de template e nela vamos criar duas abas: Gráficos e Ações.

A aba "Ações" servirá para carregar o nosso arquivo CSV com os dados. A aba "Gráficos" irá apresentar um gráfico de pizza com o preço das ações da primeira aba.

Nosso programa irá copiar essa template e criar uma nova planilha onde vamos fazer o upload do CSV. Dessa forma, podemos criar várias planilhas com dados e gráficos diferentes.

Passo 1 - Criar a Template

Vamos criar uma planilha em branco para servir de template. Vá até seu google drive crie um diretório, em seguida clique com o botão direito e crie uma planilha. Dê o nome de Template e crie duas abas chamadas de Gráficos e Ações.





Copie e cole (command+shift+v no MAC e ctrl+shit+v no windows) o conteúdo abaixo na célula A1 da aba Ações

ativo	valor_atual	min	max	yield	valorizacao
MGLU3	6,33	5,74	26,22	0,20%	-3,06%
LREN3	26,2	21,69	43,95	1,60%	-1,76%
BBAS3	32,23	26,49	35,24	7,00%	-0,19%
ITSA4	10,27	8,79	11,1	4,70%	1,99%

Agora na aba Gráficos, vá no menu Insert → Charts e crie um gráfico de pizza com as seguintes configurações:


Passo 2 - Autenticar e gerar o token.

Para manipular uma planilha, precisamos criar um cliente do gspread e para isso precisamos autenticar na api do google utilizando a credencial que baixamos nos passos anteriores.

import gspread
from gspread.spreadsheet import Spreadsheet
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow

CREDENTIALS_FILE=f'<CAMINHO PARA A CREDENCIAL BAIXADA>/credentials.json'
TOKEN_FILE=f'<CAMINHO ONDE O TOKEN SERA GERADO>/acesso/token.json'

SCOPES = [
    'https://www.googleapis.com/auth/drive',
    'https://www.googleapis.com/auth/drive.readonly',
    'https://www.googleapis.com/auth/spreadsheets',
    'https://www.googleapis.com/auth/spreadsheets.readonly'
]

def get_credentials():
    credentials = None
    if os.path.exists(TOKEN_FILE):
        credentials = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
    if not credentials or not credentials.valid:
        if credentials and credentials.expired and credentials.refresh_token:
            credentials.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            credentials = flow.run_local_server(port=0)
        with open(TOKEN_FILE, 'w') as token:
            token.write(credentials.to_json())
    return credentials

def main():
    client = gspread.authorize(get_credentials())

if __name__ == '__main__':
    main()

Ao executar o código acima, um browser irá se abrir para confirmar o acesso solicitado pelos escopos. Basta confirmar e fechar o browser que o arquivo token.json será criado. Esse arquivo irá conter o token de acesso que precisamos para comunicar com a api do google. O token tem prazo de validade e precisará ser renovado quando o tempo expirar.

Passo 3 - Criar uma nova planilha a partir da template

Para realizar um cópia da template para uma planilha final, vamos primeiro verificar se essa planilha já existe e caso exista ela será removida.

TEMPLATE_FOLDER_ID = f'1WPriGQaE5cScXL8UK16FD-NCJoKUcls4'
TEMPLATE_ID = f'1b05snBcvehGex_jsN1mgtBYm7jOJZQKkuLIHJ9E4RV8'

planilha_nova = 'Versão nova'    
print(f"Verificando se planilha {planilha_nova} existe")
try:
    sh = client.open(planilha_nova, TEMPLATE_FOLDER_ID)
    print(f"Panilha {planilha_nova} já existe e será removida")
    client.del_spreadsheet(sh.id)
except:
    print(f"Panilha {planilha_nova} ainda não existe")

print(f"Copiando a template para: {planilha_nova}")
client.copy(TEMPLATE_ID, title=planilha_nova, copy_permissions=True)

A variável TEMPLATE_FOLDER_ID é o identificador único do diretório que criamos e pode ser obtido direto da URL do diretório:


A variável TEMPLATE_ID também pode ser obtida da mesma maneira:


Não se esqueça de substituir pelos valores correspondentes do diretório e planilhas que você gerou no seu google drive.

Passo 4 - Upload do CSV

Por último, vamos fazer upload do arquivo CSV para a a aba "Ações” na planilha que acabou de ser gerada

CSV_FILE = f'<CAMINHO DO CSV>/acoes.csv'

print(f"Atualizando CSV: {CSV_FILE}")
spreadsheet: Spreadsheet = client.open(planilha_nova)

with open(CSV_FILE, 'r') as file:
    spreadsheet.values_update(
        'Ações',
        params={'valueInputOption': 'USER_ENTERED'},
        body={'values': list(csv.reader(file))}
    )

Passo 5 - Formatando celula (opcional)

Caso seja necessário, podemos formatar células e colunas da planilha:

print(f"Formatando Cabecalho")
aba_acoes = spreadsheet.get_worksheet(1)
aba_acoes.format('A1:F1', {
    'horizontalAlignment': 'CENTER',
    'textFormat': {'bold': True}
    })

No nosso caso toda formatação que estiver na template será mantida na cópia mas está ai o exemplo caso seja necessário fazer alguma formatação customizada.

Dicas

Caso seu google sheets esteja configurado com o separador decimal "." você deve configurar para usar a ",”. Vá no menu File → Settings e configure o Locale para Brasil.





Conclusão

Esse foi um artigo bem extenso devido a configuração do aplicativo no google mas espero que possa ser útil para você.

O código fonte completo pode ser encontrado aqui.

Referências

sábado, 5 de fevereiro de 2022

Selenium com Python

Índice



Introdução

Selenium automates browsers. That's it! What you do with that power is entirely up to you. Primarily it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should) also be automated as well.

Essa frase foi retirada diretamente do site oficial do Selenium. Traduzindo, o Selenium começou como um framework para testes funcionais de aplicações web, porém com o passar do tempo foi amplamente utilizado para automatizar tarefas como acessar e coletar informações de páginas web.

Neste artigo, vamos abordar esse aspecto de automação e webscraping em um exemplo prático, onde vamos buscar informações sobre ações no site Fundamentus e montar um arquivo CSV que possa ser importado em qualquer lugar (banco de dados, Google Sheets, etc.)



Arquitetura

Para começar precisamos entender como funciona a arquitetura do selenium que está dividida em três partes.

  • Cliente: é uma biblioteca específica para uma determinada linguagem feita para interagir com o selenium
  • Driver: é o agente que irá permitir a interação entre o cliente e o browser de fato
  • Browser: é o ambiente onde a página será renderizada e executada.







Instalações

Neste exemplo, vamos utilizar a linguagem python, então precisamos baixar duas coisas, a biblioteca oficial do selenium para python e o driver correspondente ao browser que estamos utilizando.

A instalação da biblioteca é bem simples:

pip install selenium

(ou você pode usar o poetry)

A instalação do driver pode ser um pouco complicada pois a versão dele deve ser idêntica a versão do browser instalado na máquina, então para facilitar, é possível utilizar a imagem docker do selenium.

version: '3.8'

services:
    selenium-chrome:
        image: selenium/standalone-chrome:latest
        hostname: selenium-chrome
        networks:
            - selenium-network
        privileged: true
        shm_size: 2g
        ports:
            - "4444:4444"
            - "7900:7900"
        healthcheck:
            test: ["CMD", "curl", "-f", "http://localhost:7900"]
            interval: 10s
            timeout: 10s
            retries: 5
    app:
        build: ./
        volumes:
            - /Users/geraldoferraz/repositorios/blog/selenium-with-python:/tmp/
        networks:
            - selenium-network
        depends_on:
            selenium-chrome:
                condition: service_healthy

networks:
    selenium-network:
      driver:  bridge



Site Fundamentus

Agora que temos tudo que precisamos, vamos analisar a página do Fundamentus e entender onde estão as informações que queremos coletar. Vamos acessar a página de análise da ação da Magazine Luiza (MGLU3). Para isso, acesse este link e você verá uma página semelhante a essa:




Inspect do Chrome

A forma mais fácil de localizar um elemento é utilizando o XPATH. Vamos utilizar a ferramenta de inspeção do browser para recuperar o xpath das informações de “Cotação”, “Min 52 sem”, “Max 52 sem”, “Div. Yield” e a Oscilação “Dia”.

  • Encontre a informação desejada, clique com o botão direito do mouse e selecione a opção “inspect”


  • Na ferramenta de inspeção que se abriu, no item selecionado, clique com o botão direito do mouse e selecione a opção “Copy”→ “Copy full xpath”



Hands-on

Agora que sabemos como chegar no nosso elemento, vamos ver o código necessário para realizar a coleta.

Primeiro, a partir do driver, precisamos construir o objeto que irá representar o browser.

from selenium import webdriver
from selenium.webdriver.chrome.service import Service

def main():
    option = webdriver.ChromeOptions()

    s = Service('/Users/geraldoferraz/Downloads/chromedriver')
    navegador = webdriver.Chrome(service=s, options=option)

    #para trabalhar dentro do docker
    #navegador = webdriver.Remote('http://selenium-chrome:4444/wd/hub', options=option)

if __name__ == '__main__':
    main()

Segundo, precisamos carregar a página desejada e utilizar o XPATH que obtivemos no passo anterior para encontrar as informações que queremos.

navegador.get("<https://fundamentus.com.br/detalhes.php?papel=MGLU3>")

try:
    xpath_valor_atual = '/html/body/div[1]/div[2]/table[1]/tbody/tr[1]/td[4]/span'        
    valor_atual = navegador.find_element(By.XPATH, xpath_valor_atual).text
    print(f'valor atual={valor_atual}')
finally:
    navegador.close()

A lib do selenium possui diversas formas de encontrar elementos na página sendo o XPATH o mais preciso:

  • By.ID
  • By.XPATH
  • By.NAME
  • By.TAG_NAME
  • By.CLASS_NAME
  • By.CSS_SELECTOR

Você deve escolher a melhor forma de acordo com a sua lógica. Para o nosso exemplo, o XPATH será suficiente.

No exemplo a seguir, vamos ver como pegar todas as informações que precisamos de um conjunto de ações:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By

def main():
    option = webdriver.ChromeOptions()
    s = Service('/Users/geraldoferraz/Downloads/chromedriver')
    navegador = webdriver.Chrome(service=s, options=option)

    #para trabalhar dentro do docker
    #navegador = webdriver.Remote('http://selenium-chrome:4444/wd/hub', options=option)

    acoes = ['MGLU3', 'LREN3', 'BBAS3', 'ITSA4']

    try:
        for acao in acoes:
            navegador.get(f"https://fundamentus.com.br/detalhes.php?papel={acao}")
            xpath_valor_atual = '/html/body/div[1]/div[2]/table[1]/tbody/tr[1]/td[4]/span'
            xpath_min = '/html/body/div[1]/div[2]/table[1]/tbody/tr[3]/td[4]/span'
            xpath_max = '/html/body/div[1]/div[2]/table[1]/tbody/tr[4]/td[4]/span'
            xpath_yield = '/html/body/div[1]/div[2]/table[3]/tbody/tr[9]/td[4]/span'
            xpath_valorizacao = '/html/body/div[1]/div[2]/table[3]/tbody/tr[2]/td[2]/span/font'

            valor_atual = navegador.find_element(By.XPATH, xpath_valor_atual).text
            min = navegador.find_element(By.XPATH, xpath_min).text
            max = navegador.find_element(By.XPATH, xpath_max).text
            _yield = navegador.find_element(By.XPATH, xpath_yield).text
            valorizacao = navegador.find_element(By.XPATH, xpath_valorizacao).text

            print(f'valor atual={valor_atual}, min={min}, max={max}, min={_yield}, min={valorizacao} ')
    finally:
        navegador.close()

if __name__ == '__main__':
    main()

A parte mais simples é gerar o CSV

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import csv

def main():
    option = webdriver.ChromeOptions()
    utilize esse formato para trabalhar localmente
    s = Service('/Users/geraldoferraz/Downloads/chromedriver')
    navegador = webdriver.Chrome(service=s, options=option)

    #para trabalhar dentro do docker
    #navegador = webdriver.Remote('http://selenium-chrome:4444/wd/hub', options=option)

    acoes = ['MGLU3', 'LREN3', 'BBAS3', 'ITSA4']

    try:
        with open('acoes.csv', 'w', newline='') as csvfile:
            cabecalho = ['valor_atual', 'min', 'max', 'yield', 'valorizacao']
            writer = csv.DictWriter(csvfile, delimiter=',', fieldnames=cabecalho)
            writer.writeheader()

            for acao in acoes:
                navegador.get(f"https://fundamentus.com.br/detalhes.php?papel={acao}")
                xpath_valor_atual = '/html/body/div[1]/div[2]/table[1]/tbody/tr[1]/td[4]/span'
                xpath_min = '/html/body/div[1]/div[2]/table[1]/tbody/tr[3]/td[4]/span'
                xpath_max = '/html/body/div[1]/div[2]/table[1]/tbody/tr[4]/td[4]/span'
                xpath_yield = '/html/body/div[1]/div[2]/table[3]/tbody/tr[9]/td[4]/span'
                xpath_valorizacao = '/html/body/div[1]/div[2]/table[3]/tbody/tr[2]/td[2]/span/font'

                valor_atual = navegador.find_element(By.XPATH, xpath_valor_atual).text
                min = navegador.find_element(By.XPATH, xpath_min).text
                max = navegador.find_element(By.XPATH, xpath_max).text
                _yield = navegador.find_element(By.XPATH, xpath_yield).text
                valorizacao = navegador.find_element(By.XPATH, xpath_valorizacao).text

                writer.writerow({'valor_atual': valor_atual, 'min': min, 'max': max, 
                'yield': _yield, 'valorizacao': valorizacao})
    finally:
        navegador.close()

if __name__ == '__main__':
    main()



Atenção!

  • Ao executar o programa, você deve ter notado que um browser se abriu. Para o desenvolvimento, pode ser interessante acompanhar visualmente tudo o que está acontecendo, assim podemos pegar alguma falha na nossa lógica, porém em um ambiente de produção, provavelmente não teremos um ambiente visual para que um browser seja aberto, então é interessante adicionar a configuração abaixo na construção do browser.
option.add_argument('headless')
  • Algumas vezes, o site pode ter algum tipo de proteção contra robôs, que podem prejudicar a performance do site, então, precisamos ficar atentos a isso, caso contrário pode ser que o nosso robô não funcione. Não é o escopo desse artigo mostrar como contornar esse cenário, mas saiba que é possível, basta realizar uma pesquisa rápida no google.
  • Para trabalhar dentro do docker, basta descomentar a linha abaixo e executar o docker-compose.yml
navegador = webdriver.Remote('http://selenium-chrome:4444/wd/hub', options=option)



Conclusão

O selenium é uma ferramenta extremamente versátil que permite automatizar diversas tarefas. A criatividade é o limite.

Github do projeto



Referências

segunda-feira, 8 de julho de 2019

Estrutura de Dados - Ordenação Parte 1 - Bubble sort e Quick sort

Ordenação
Hoje em dia temos que lidar com um grande volume de informações e será mais fácil lidar com elas se estiverem ordenadas com base em algum critério.

Imagina uma agenda de contatos onde os nomes são colocados de maneira sequencial, sem nenhum critério de ordenação. Ao buscar um nome específico, será necessário percorrer essa lista inteira até encontrar. No melhor caso o será  o primeiro e o pior caso será o último. Esse processo pode ser demorado caso a quantidade de informações seja muito grande.

A escolha do algoritmo de ordenação ideal é deve levar em consideração uma  série de variáveis como volume de informação, estrutura de dados de armazenamento, etc.

Hoje vamos falar sobre ordenação por troca, isto é, os elementos serão comparados uns aos outros e trocados de acordo com o critério desejado.

Os dois algoritmos mais básicos desse tipo de ordenação são o Bubble Sort e o Quick Sort

Bubble Sort
O algoritmo mais simples e conhecido, de fácil compreensão e implementação. Neste algoritmo, cada elemento será comparado com seu sucessor e trocado caso esteja fora de ordem. Com isso podemos perceber que será necessário várias iterações até que os elementos estejam completamente ordenados.


Apesar de ser simples de implementar, esse algoritmo possui uma ordem de N AO QUADRADO. Isso significa que seu desempenho terá uma curva crescente em função da quantidade de elementos.


Esse algoritmo então, pode não ser a escolha ideal caso tenhamos muitos elementos para ordenar e sim em um conjunto finito e controlado.

public class BubbleSort {

 public static void ordenar(int[] arr) {
  int n = arr.length;
  int temp = 0;
  for (int i = 0; i < n; i++) {
   for (int j = 1; j < (n - i); j++) {
    if (arr[j - 1] > arr[j]) {
     temp = arr[j - 1];
     arr[j - 1] = arr[j];
     arr[j] = temp;
    }

   }
  }
 }

 public static void main(String[] args) {
  int arr[] = { 3, 60, 35, 2, 45, 320, 5 };

  System.out.println("Antes de ordenar");
  imprimir(arr);
  System.out.println();

  ordenar(arr);

  System.out.println("Depois de ordenar");
  imprimir(arr);
 }

 private static void imprimir(int[] arr) {
  for (int i = 0; i < arr.length; i++) {
   System.out.print(arr[i] + " ");
  }
 }

}


Quick Sort
Este algoritmos usa a estratégia "Dividir para conquistar". O primeiro passo é escolher um pivô, em seguida, todos os elementos a direita que forem "menores" serão passados para esquerda do pivô e os elementos da esquerda que forme "maiores" serão passados para direita do pivô. No final dessa iteração, o pivô estará exatamente na posição que deveria.
O próximo passo é repetir esse passo para as sublistas a direita e à esquerda do pivô.



public class QuickSort {
 
 public static void ordenar(int arr[], int inicio, int fim) {
  if (inicio < fim) {
         int particao = particionar(arr, inicio, fim);
  
         ordenar(arr, inicio, particao-1);
         ordenar(arr, particao+1, fim);
     }
 }
 
 private static int particionar(int arr[], int inicio, int fim) {
     int pivo = arr[fim];
     int i = (inicio-1);
  
     for (int j = inicio; j < fim; j++) {
         if (arr[j] <= pivo) {
             i++;
  
             int temp = arr[i];
             arr[i] = arr[j];
             arr[j] = temp;
         }
     }
  
     int temp = arr[i+1];
     arr[i+1] = arr[fim];
     arr[fim] = temp;
  
     return i+1;
 }

 
 public static void main(String[] args) {
  int arr[] = { 3, 60, 35, 2, 45, 320, 5 };

  System.out.println("Antes de ordenar");
  imprimir(arr);
  System.out.println();

  ordenar(arr,0,arr.length-1);

  System.out.println("Depois de ordenar");
  imprimir(arr);
 }

 private static void imprimir(int[] arr) {
  for (int i = 0; i < arr.length; i++) {
   System.out.print(arr[i] + " ");
  }
 }

}

domingo, 16 de junho de 2019

Estrutura de Dados Parte 5 - Tabela de Espalhamento

Tabela de espalhamento

A tabela de espalhamento ou tabela de hash é uma estrutura que consiste em indexar os elementos armazenados de maneira que seja fácil e rápido encontrar qualquer elemento a partir de sua chave.
Essa estrutura não tem a premissa de armazenar os elementos de maneira sequencial e sim de identificar uma categoria para o elemento e armazená-lo de acordo.

É uma estrutura muito usada tanto na computação quanto no dia a dia.

  1. Agenda telefônica
  2. Corredores de supermercado
  3. Organizar livros em uma biblioteca

O primeiro passo é identificar a qual categoria pertence o elemento que queremos armazenar, para isso temos a "função hash". Esta função é responsável por gerar o índice do elemento. Portanto, essa função deve ser escolhida com cuidado pois caso a função seja mal implementada a tabela terá uma performance ruim.

Vamos pegar um exemplo de uma agenda telefônica.

Para armazenar o nome "Geraldo", executamos a função hash que irá produzir o índice "G".
Já o nome "Nayara" irá produzir o índice "N" e o nome "Fábio" irá produzir o índice "F".

Desta maneira sabemos que os elementos serão armazenados da seguinte maneira:

    

Para recuperar o nome "Fábio" usamos novamente o índice "F" e acessamos o elemento diretamente.

Mesmo que a nossa "função hash" seja muito boa, ainda assim estamos sujeitos a colisões, que é quando dois elementos distintos produzem o mesmo índice. O nome "Francisco" também produz o índice "F" e no exemplo acima ocorreria uma colisão com o nome "Fábio".

Nesse caso precisamos combinar mais de uma estrutura para permitir que os dois elementos possam coexistir.



Podemos combinar as estruturas "tabela de espalhamento" e alguma outra estrutura que melhor encaixe no nosso contexto como Pilhas, Filas e Listas, etc.

public class TabelaEspalhamentoApp {
 
 public static void main(String[] args) {
  TabelaEspalhamento tabela = new TabelaEspalhamento();

  tabela.adiciona("Nayara");
  tabela.adiciona("Geraldo");
  tabela.adiciona("Gorge");
  tabela.adiciona("Fabio");

  System.out.println(tabela.contem("Geraldo"));

  tabela.remove("Gorge");
  System.out.println(tabela.contem("Geraldo"));
  System.out.println(tabela.contem("Gorge"));

 }

}

public class TabelaEspalhamento {

 VetorGenerico vetor = new VetorGenerico();

 public void remove(String elemento) {
  int indice = recuperarIndice(elemento);
  Vetor v = (Vetor) vetor.get(indice);
  if (v != null) {
   int posicao = v.indice(elemento);
   if (posicao >= 0) {
    v.remove(posicao);
   }

  }
 }

 public boolean contem(String elemento) {
  int indice = recuperarIndice(elemento);
  Vetor v = (Vetor) vetor.get(indice);
  if (v != null) {
   return v.has(elemento);
  }
  return false;
 }

 public void adiciona(String elemento) {
  int indice = recuperarIndice(elemento);
  Vetor v = (Vetor) vetor.get(indice);
  if (v == null) {
   v = new Vetor();
  }
  v.add(elemento);
  vetor.add(indice, v);
 }

 private int recuperarIndice(String elemento) {
  return elemento.charAt(0) % 75;
 }

}

public class VetorGenerico{

 Object[] elementos = new Object[1000];
 int indice;

 public void add(Object elemento) {
  elementos[indice] = elemento;
  indice++;
 }

 public Object get(int i) {
  return elementos[i];
 }

 public void add(int posicao, Object elemento) {
  for (int i = indice - 1; i >= posicao; i--) {
   elementos[i + 1] = elementos[i];
  }

  elementos[posicao] = elemento;
  indice++;
 }
 
 public void remove(int posicao) {
  elementos[posicao] = null;

  for (int i = posicao; i + 1 < elementos.length; i++) {
   elementos[i] = elementos[i + 1];
  }

  indice--;
 }
}

Estrutura de Dados - Pesquisa - Sequencial ou Linear, Binária e Interpolação

PESQUISA

Nos dias de hoje, o acesso rápido a informação pode ser determinante para o sucesso do aplicativo. Por isso o algoritmo de pesquisa é tão importante quanto a estrutura de armazenamento e o algoritmo de ordenação.


PESQUISA SEQUENCIAL

Vamos falar do mecanismo de pesquisa mais trivial que existe, a Pesquisa Linear ou Sequencial.

Esse algoritmo consiste em percorrer cada um dos elementos da nossa estrutura até encontrar o elemento desejado. Sua eficiência é inversamente proporcional a quantidade de elementos que tivermos que percorrer. Na melhor hipótese o elemento desejado será o primeiro e na pior hipótese o elemento desejado será o último.

Podemos melhorar significativamente o desempenho se os elementos estiverem ordenados. Por isso é tão importante escolher a estrutura certa assim como aplicar algoritmos de ordenação.

Por exemplo, se queremos encontrar um nome específico na nossa agenda telefônica, será muito mais rápido se a agenda estiver organizada por ordem alfabética. Assim podemos pular alguns nome que temos certeza que não irão atender ao critério de pesquisa.

public class PesquisaLinear {

 public static int pesquisar(String arr[], String argumento) {
  
  for (int i = 0; i < arr.length; i++) {
   if (arr[i] == argumento) {
    return i;
   }
  }

  return -1;
 }

 public static void main(String args[]) {
  String arr[] = { "A", "B", "C", "D" };
  System.out.println(pesquisar(arr, "C"));
 }

}


PESQUISA BINÁRIA

A pesquisa binária utiliza da técnica "dividir para conquisar". Consiste em dividir os elementos ao meio, em seguida é verificado se o elemento desejado é maior ou menor que o elemento central. Caso seja maior, então o elemento desejado obrigatoriamente deve estar do lado direito da divisão e caso seja menor então obrigatoriamente deve estar do lado esquerdo. Esse processo de divisão e verificação é repetido até encontrar o elemento.
Repare para que esse algoritmo funcione, os elementos devem estar obrigatoriamente ordenados, caso contrário pode não funcionar.


public class PesquisaBinaria {

 public static int pesquisar(int arr[], int l, int r, int x) {
  if (r >= l) {
   int mid = l + (r - l) / 2;

   if (arr[mid] == x)
    return mid;

   if (arr[mid] > x)
    return pesquisar(arr, l, mid - 1, x);

   return pesquisar(arr, mid + 1, r, x);
  }

  return -1;
 }

 public static void main(String args[]) {
  int arr[] = { 2, 3, 4, 10, 40 };
  int n = arr.length;
  int x = 10;
  int posicao = pesquisar(arr, 0, n - 1, x);
  if (posicao == -1)
   System.out.println("Elemento não encontrado");
  else
   System.out.println("Elemento encontrado na posição " + posicao);
 }
}


PESQUISA POR INTERPOLAÇÃO

Este algoritmo de pesquisa, assim como a busca binária, tem como premissa que os elementos esteja previamente ordenados, além disso o algoritmo é semelhante a busca binária, eles diferem na forma como os elementos são divididos. Enquanto a busca binária está sempre dividindo ao meio, a busca por interpolação divide os elementos com base na fórmula abaixo:

pos = lo + [ (x-arr[lo])*(hi-lo) / (arr[hi]-arr[Lo]) ]

public class PesquisaInterpolacao {

 public static int pesquisar(int[] arr, int x) {
  int lo = 0, hi = (arr.length - 1);

  while (lo <= hi && x >= arr[lo] && x <= arr[hi]) {

   if (lo == hi) {
    if (arr[lo] == x)
     return lo;
    return -1;
   }

   int pos = lo + (((hi - lo) / (arr[hi] - arr[lo])) * (x - arr[lo]));

   if (arr[pos] == x)
    return pos;

   if (arr[pos] < x)
    lo = pos + 1;

   else
    hi = pos - 1;
  }
  return -1;
 }

 public static void main(String[] args) {
  int x = 18;
  int arr[] = new int[] { 10, 12, 13, 16, 18, 19, 20, 21, 22, 23, 24, 33, 35, 42, 47 };
  int posicao = pesquisar(arr, x);

  if (posicao != -1)
   System.out.println("Elemento encontrado na posição " + posicao);
  else
   System.out.println("Elemento não encontrado.");
 }

}

Estrutura de Dados - Ordenação Parte 2 - Selection, Insertion e Shell sort

Selection sort.

O algoritmo consiste em selecionar o menor (ou maior) elemento fora de ordem colocá-lo em sua posição correta. Para fazer isso, primeiro os elementos devem ser divididos em duas partes: os elementos já ordenados e os elementos que ainda não foram ordenados.

Inicialmente a parte ordenada estará vazia, enquanto a parte não ordenada possuirá todos os elementos.

Após a divisão, o algoritmo irá encontrar o menor elemento do lado não ordenado e colocá-lo no lado ordenado. Esse passo irá se repetir até que o lado não ordenado fique vazio.

Este algoritmo possui duas grandes vantagens, primeiro, é um algoritmo simples de ser implementado e segundo, não há consumo de memória além daquele já alocado para os elementos originais, uma vez que a divisão de lado ordenado e lado não ordenado é feita no próprio vetor que queremos ordenar.

A maior desvantagem é que é um algoritmo que não tem um bom desempenho quando temos uma quantidade de elementos muito grande.



public class SelectionSort {

 public static void ordenar(int arr[]) {
  int n = arr.length;

  for (int i = 0; i < n - 1; i++) {
   int min = i;
   for (int j = i + 1; j < n; j++)
    if (arr[j] < arr[min])
     min = j;

   int temp = arr[min];
   arr[min] = arr[i];
   arr[i] = temp;
  }
 }

 public static void main(String args[]) {
  int arr[] = { 64, 25, 12, 22, 11 };

  System.out.println("Antes de ordenar");
  imprimir(arr);

  ordenar(arr);

  System.out.println("Depois de ordenar");
  imprimir(arr);
 }

 public static void imprimir(int arr[]) {
  int n = arr.length;
  for (int i = 0; i < n; ++i)
   System.out.print(arr[i] + " ");
  System.out.println();
 }

}


Insertion sort

O algoritmos consiste inicialmente em selecionar um pivô e comparar os elementos seguintes, um a um. Se o elemento seguinte for maior que o pivô, esse elemento agora passará a ser o novo pivô. Se o elemento for menor que o pivô, os elementos anteriores ao pivô serão percorridos até ser encontrado o elemento maior que o elemento encontrado anteriormente colocando-o logo antes do elemento maior.

Este algoritmo possui uma vantagem: não há consumo de memória além daquele já alocado para os elementos originais, uma que toda a ordenação ocorre dentro dos próprios elementos.

A maior desvantagem é que é um algoritmo que não tem um bom desempenho quando temos uma quantidade de elementos muito grande, assim como o selection sort.



public class InsertionSort {

 public static void sort(int arr[]) {
  int n = arr.length;
  for (int i = 1; i < n; ++i) {
   int k = arr[i];
   int j = i - 1;

   while (j >= 0 && arr[j] > k) {
    arr[j + 1] = arr[j];
    j = j - 1;
   }
   arr[j + 1] = k;
  }
 }

 public static void main(String args[]) {
  int arr[] = { 12, 11, 13, 5, 6 };
  
  System.out.println("Antes de ordenar");
  imprimir(arr);

  sort(arr);

  System.out.println("Depois de ordenar");
  imprimir(arr);
 }

 public static void imprimir(int arr[]) {
  int n = arr.length;
  for (int i = 0; i < n; ++i)
   System.out.print(arr[i] + " ");

  System.out.println();
 }

}


Shell sort

Este algoritmo consiste em realizar trocas entre os elementos que estão posicionados a uma distância determinada. A distância inicial pode ser definida por d = n /  2 e a cada iteração diminuímos a essa distância, por exemplo, se você tem 10 elementos

iteração 1) d = 10 / 2 = 5;
iteração 2) d =  5 / 2 = 3;
iteração 3) d =  3 / 2 = 2
iteração 4) d =  2 / 2 = 1

Os elementos estarão ordenados quando a distância for menor ou igual a zero.





public class ShellSort {

 public static int ordenar(int arr[]) {
  int n = arr.length;

  for (int gap = n / 2; gap > 0; gap /= 2) {
   for (int i = gap; i < n; i += 1) {
    int temp = arr[i];

    int j;
    for (j = i; j >= gap && arr[j - gap] > temp; j -= gap)
     arr[j] = arr[j - gap];

    arr[j] = temp;
   }
  }
  return 0;
 }

 public static void main(String args[]) {
  int arr[] = { 12, 34, 54, 2, 3 };
  
  System.out.println("Antes de ordenar");
  imprimir(arr);

  ordenar(arr);

  System.out.println("Depois de ordenar");
  imprimir(arr);
 }

 public static void imprimir(int arr[]) {
  int n = arr.length;
  for (int i = 0; i < n; ++i)
   System.out.print(arr[i] + " ");
  System.out.println();
 }
}