Categorias
Tecnologia

Usando BeautifulSoup para pegar jogos, resultados e previsões do Brasileirão 2016

O pessoal no escritório está se divertindo muito com o Cartola FC. Graças a Liga que criamos por lá, todos estamos envolvidos em escalar nossos times e para isso eu gosto de ver as probabilidades de resultado dos jogos. Descobri a pouco que o Bing faz uma previsão quando você busca por dois times que irão se enfrentar. Então resolvi automatizar essa busca para me divertir e experimentar o BeautifulSoup4  (BS) para fazer crawler.

A ideia de um crawler é pegar um HTML de um site e procurar informações dentro dele. O BeautfulSoup4 é quem faz o parse deste HTML e nos permite executar métodos que fazem essa busca por informações no mesmo.

Comecei fazendo um app de um arquivo só com o Flask apenas para executar os crawlers facilmente e exibir as informações e dentro dele criei dois métodos que executam crawlers. O primeiro atualiza a tabela de jogos com os resultados (e na primeira execução popula o banco com os jogos) e o segundo executa a busca no Bing e me retorna a previsão de resultado de cada jogo.

Então o primeiro passo é buscar esse HTML no site onde você tem a informação. No caso das informações dos jogos eu fui buscar no site tabeladobrasileirao.net.

Começo utilizando o requests do Python para retornar o HTML dos jogos e seus resultados (no caso dos que já aconteceram).

import requests
r = requests.get('http://www.tabeladobrasileirao.net/')

Depois é a vez do BeautifulSoup4 entrar em ação. Passo o resultado para o BS (com o encode) e ele “parseia” o HTML.

from bs4 import BeautifulSoup
soup = BeautifulSoup(r.text.encode('utf-8'), 'html.parser')

E aí começo a procurar. Depois de analisar o HTML do site, descubro onde as informações estão e uso o método find do BS para pegar as informações.

table = soup.find('table', id="jogos")
for row in table.findAll("tr")[1:]:
    cells = row.findAll("td")
    if len(cells) == 12:
        game = {}
        game['round'] = int(cells[0].find(text=True))

        date_string = cells[1].find(text=True)
        date_string = '{}/{}'.format(date_string, '2016')
        game['date'] = datetime.datetime.strptime(date_string, "%d/%m/%Y").date()

        game['home_team'] = cells[4].find(text=True)

        home_team_result = cells[5].find(text=True)
        if home_team_result:
            game['home_team_result'] = int(cells[5].find(text=True))
        else:
            game['home_team_result'] = None

        away_team_result = cells[7].find(text=True)
        if away_team_result:
            game['away_team_result'] = int(cells[7].find(text=True))
        else:
            game['away_team_result'] = None
            
        game['away_team'] = cells[8].find(text=True)

Neste código estou buscando a tabela com id igual a “jogos” e iterando sobre suas células, a partir das linhas da tabela. Como já identifiquei onde estão os dados, comecei a guardá-los em um dicionário (tratando datas, tipos e resultados nulos) que usarei para salvar no banco de dados posteriormente. Por exemplo, cada jogo está em uma linha e a data dele está na segunda coluna, então uso cells[1].find(text=true) para pegar somente o texto da célula.

Omiti a parte de salvar no banco de dados, mas o código todo está disponível no meu Github.

Com todos os jogos salvos é a vez de procurar pela previsão de resultados no Bing.

Mais uma vez começo com o requests e dou um parse no resultado com o BS. Leve em consideração que a variável “game” no código abaixo retorna a string com o jogo em questão, por exemplo, “Internacional x Grêmio” ou “Corinthians x Flamengo”.

r = requests.get(u'http://www.bing.com/search?q={}'.format(game))

soup = BeautifulSoup(r.text.encode('utf-8'), 'html.parser')
div = soup.find('div',{'id':'tab_4'}).findNext('span',{'class':'b_demoteText'}).find_next_sibling(text=True)

predicts_string = div

Nesse caso foi muito mais difícil achar o local exato no código fonte do resultado da busca do Bing do que foi no caso do tabeladobrasileirao.net. Mas com um pouco de pesquisa e muita tentativa e erro eu acabei chegando no texto de previsão do Bing usando soup.find(‘div’,{‘id’:’tab_4′}).findNext(‘span’,{‘class’:’b_demoteText’}).find_next_sibling(text=True), mais uma vez com text=true para retornar somente o texto do objeto que vem no formato “Chapecoense 14% – Empate 29% – Corinthians 57%”, por exemplo.

Com os templates do Flask eu criei um pequeno site que executa e exibe o resultado desses crawlers. Ficou assim:

Essa foi minha primeira experiência com BeautifulSoup4 e crawlers. Qualquer dúvida ou sugestão, deixe um comentário abaixo. E para ver o código fonte desse pequeno app acesse meu Github.

Categorias
Tecnologia

Usando Grunt para compilar SCSS (Sass)

O Grunt é um gerenciador de tarefas JavaScript. O conheci pela primeira vez quando utilizei o Yeoman para desenvolver um projeto em AngularJS (leia mais).

O Sass é uma linguagem de extensão de CSS, que se auto-intitula “CSS com superpoderes”.

Precisei começar um novo projeto de frontend montando apenas os HTMLs, JavaScripts e CSSs das páginas. Então resolvi conhecer melhor o Grunt e o Sass.

Percebi que dominar essas ferramentas gera dezenas de possibilidades novas para o desenvolvimento frontend. Claro que ainda não dominei, mas vou escrever um pouco sobre o que já aprendi.

Obs: Para todos os comandos abaixo estou utilizando a linha de comando (console) do Linux.

Para instalar o Sass é necessário o package manager do Ruby, o RubyGems.

sudo su -c "gem install sass"

Para instalar o grunt-cli é necessário o package manager do Node.js, o npm. Aproveito para já instalar os pacotes que vou utilizar: grunt-contrib-sass e grunt-contrib-watch.

npm install grunt grunt-contrib-sass grunt-contrib-watch

Estou usando o Bootstrap nesse projeto, então é necessário instalar o bootstrap-sass pelo npm.

npm install boostrap-saas

Minha estrutura de diretórios será a seguinte:

  • project/
    • node_modules/
    • public/
      • css/
      • img/
      • js/
      • index.html
    • sass/
      • style.scss
    • Gruntfile.js
    • package.json

No arquivo Gruntfile.js configuramos todas as tarefas que o Grunt vai executar. No caso desta publicação faremos a preprocessamento do SCSS para CSS.

Todas as tarefas do Grunt são executadas na linha de comando (console) precedidas por “grunt”, como por exemplo “grunt watch” ou “grunt build”.

[gist id=”f1c4801d3fd6f07a76ac28ec4ef3d4d6″]

Primeiro configurei o Sass para compilar para o ambiente de desenvolvimento, nesse caso o CSS não ficará comprimido, para que eu possa vê-lo em ação. No caso da configuração para ambiente de produção ele será comprimido.

Então configuro a tarefa do grunt-watch, ela acionará a tarefa “sass:dev“, que configuramos no passo anterior, a cada mudança que eu fizer aos arquivos *.scss. Isso serve para que eu não precise rodar um comando grunt manualmente a cada alteração no SCSS, o que seria contraproducente.

Obs: Para que isso aconteça é preciso deixar o comando “grunt watch” rodando na linha de comando (console).

No final do arquivo registro as tarefas com o registerTask. No caso apenas “grunt build“, que será usado para gerar o CSS final comprimido para produção, pois a tarefa que gera os arquivos não-comprimidos para ambiente de desenvolvimento já será chamada pelo grunt-watch e não precisa ser registrada novamente.

Estas tarefas registradas ao final do arquivo servem para agregar várias tarefas a um único comando. Assim quando você precisar rodar tudo de uma vez para gerar a versão de produção, você rodorá apenas “grunt build” e todas as tarefas da lista serão executadas. No nosso caso temos apenas o Sass por enquanto, mas futuramente podemos ter diversas tarefas a serem executadas para gerar os arquivos de prod.

Vou falar mais sobre Grunt e Sass no futuro. Se tiver alguma sugestão ou dúvida sobre o conteúdo desta publicação, dexe um comentário abaixo.

Categorias
Tecnologia

Sticky navbar com Bootstrap

Como fixar a barra de navegação do Bootstrap quando a barra de rolagem chegar nela? Essa funcionalidade é chamada de Sticky Navbar.

Utilizando esse código JavaScript abaixo a barra de navegação estática (com a classe .navbar-static-top) passa a ficar fixada no topo da página (classe .navbar-fixed-top) quando a barra de rolagem atinge certa parte da página.

      var num = $('.navbar').offset().top;

      $(window).bind('scroll', function () {
          if ($(window).scrollTop() > num) {
              $('.navbar').addClass('navbar-fixed-top');
              $('.navbar').removeClass('navbar-static-top');
          } else {
              $('.navbar').addClass('navbar-static-top');
              $('.navbar').removeClass('navbar-fixed-top');
          }
      });

A variável num contém a quantidade de pixels do topo até onde a barra de rolagem vai atuar para modificar o estilo da navbar. Neste caso estou utilizando o método offset() do jQuery para verificar a altura da barra de navegação automaticamente.

As classes de navbar descritas acima são originalmente do Bootstrap (testado com o Bootstrap 3.3.2), mas com algumas adaptações pode ser utilizado em qualquer outro CSS, basta alterar os nomes das classes no código.

Esse código foi encontrado nesse JSFiddle e apenas alterei para a utilização do método offset().

Categorias
Tecnologia

Deploy de sistema Flask no Webfaction

Depois de concluir um sistema simples (em Python Flask) para guardar informações em um banco de dados, eu o publiquei no meu servidor para disponibilizar para o usuário final (deploy).

Foi a primeira vez que fiz um deploy de um sistema desenvolvido em Flask (que é um micro framework web em Python que tenho estudado e utilizado ultimamente, como comentei neste texto).

Tenho uma conta no Webfaction e foi lá que fiz o deploy do sistema. O processo foi baseado em uma mistura de alguns tutoriais achados na internet e também de tentativa e erro. Entretanto todos eles eram baseados em Python 2.7 e o Webfaction já disponibilizou o WSGI com Python 3.4. Depois de algumas alterações para suprir isso e mais pesquisa eu consegui colocar o sistema para funcionar.

O deploy foi feito para rodar com virtualenv, como mostra essa referência que encontrei neste link. O que tive mesmo que mudar da referência foi a forma de executar o arquivo Python que ativa o ambiente virtual (activate_this.py). No Python 2.7 utilizamos execfile() para rodar o script que ativa o ambiente (como você pode ver no Step 6 do artigo que referenciei acima) e precisei substituir por exec() do Python 3.4.

Só consegui descobrir que o erro era esse acompanhando os logs do servidor a cada vez que fazia uma mudança no código e executava um restart no Apache. No caso do Webfaction os logs ficam em /home/<seuUsuario>/logs/user/error_<seuApp>.log.

Estou gostando muito de trabalhar com o Python e com o Flask. Desenvolver com este framework é muito ágil e dinâmico. O Flask tem diversas extensões que facilitam a vida do desenvolvedor e ainda assim, por não virem no código-fonte do framework, te deixam a escolha de usar ou esta ou aquela extensão. O que é muito bom, pois dependendo do escopo do projeto podemos precisar ou não de determinadas extensões.

Neste projeto, que comento neste texto, estou utilizando as extensões Flask-Login (controle de acesso), Flask-SQLAlchemy (ORM) e Flask-WTF (para formulários). Em textos futuros falarei um pouco mais sobre as extensões do Flask.