Economizando 1 salário mínimo no Axie Infinity usando python web scraping e um grupo de telegram

Como economizar R$900 ao sacar suas criptomoedas do jogo, buscando dados do JavaScript de um site com python.

Vitor Azambuja
14 min readSep 2, 2021

Axie Infinity é um jogo que vem se tornando cada vez mais popular. Para termos noção, hoje em setembro/21, o jogo conta com uma base de mais de 250mil jogadores ativos por mês que já movimentaram mais de 90mil ETH (No momento 1 ETH = 3.775 USD), possui grandes parceiros como, Ubisoft, Samsung e Binance e tudo isso estando ainda em fase alpha.

Exemplo de time com 3 Axies, pronto para começar a jogar.

E de forma bem resumida, para começar a jogar, você precisa comprar pelo menos 3 axies para montar um time. Com o seu time pronto, você pode começar a lutar no modo PVE (Player vs Enviroment) e PVP (Player vs Player) para começar a ganhar uma das moedas do jogo, o SLP, ou Small Love Potion. E pra que serve essa moeda? Bom, o SLP é necessário para você cruzar os seus axies para que eles botem ovos e ao chocar, nasçam novos axies.

Eu não vou entrar no mérito do que alguns dizem que o jogo é uma pirâmide, se vale a pena jogar, ou “investir” um dinheiro nesse jogo pois não estou aconselhando ninguém a fazer isso. Meu propósito aqui é única e exclusivamente didático. Dito isto, vou seguir e contar como me deparei com um problema no jogo e como usei meus conhecimentos em python para resolve-lo.

Bom, quando comecei a jogar, eu me deparei com uma questão bem chata… Eu só poderia sacar meus SLPs depois de 15 dias jogando, pois o jogo tem essa limitação, não sei porque. Mas o real problema é que para sacar os SLPs do jogo, eu pago uma taxa. E essa taxa, caros amigos, não é barata para um brasileiro médio.

Através do site https://axie.live, que é um site que monitora a média das taxas cobradas para realizar transações entre o jogo e sua carteira de cripto, eu descobri que as taxas poderiam chegar a valores exorbitantes. No momento que eu estou escrevendo esse texto, eu tirei o print abaixo e o saque mais rápido de SLP estava em 84,83 USD. Absurdamente alto, né?

Transações “Rapid” serão executadas quase que instantaneamente pela rede, enquanto as transações “Fast” serão executadas em alguns segundos, Já as transações “Standard” levarão vários minutos e as transações “Slow” serão executadas, provavelmente em até algumas horas.

Eu já estava com quase 15 dias de jogo e eu já queria sacar os SLPs que eu tinha gerado com algo em torno de 30 dias, para mandar para a minha corretora de criptomoedas. E ai eu pensei: “Vou esperar as transações ficarem baixas, provavelmente de madrugada, para então eu sacar.” Mas ai eu me dei conta de que eu não sabia o que era uma transação baixa. Seria algo abaixo de 30 USD? 20 USD? 10 USD? Foi então que eu abri meu PyCharm e comecei a codar.

Raciocinando sobre como resolver o problema

Eu sabia que o site axie.live tinha as informações de valor das taxas de saque que eu precisava e ele é atualizado automaticamente a cada 30s, mais ou menos. Se eu ficasse 2 semanas olhando pro site, eu saberia qual o menor valor que a taxa de saque de SLP atingiu, qual o valor máximo, quais os dias da semana as taxas ficam mais altas e quais dias as taxas ficam mais baratas. Além disso eu poderia saber também quais horários ocorrem os maiores e menores picos e também a informação que eu mais queria: Se eu pegasse todas as taxas de transação durantes essas 2 semanas e separasse as 5% menores, qual valor eu acharia? Todas estariam abaixo de 7 USD? 10USD? Era isso que eu precisava saber. Quando eu soubesse qual era esse valor mágico, eu precisaria deixar um código rodando, olhando aquela página a todo momento e quando a taxa de transação ficasse baixa, o código teria que me notificar por email, whatsapp ou telegram.

A informação que eu quero estava dentro do site, que é criado com HTML, CSS e JavaScript. Então eu primeiro preciso saber em que parte do código eu tenho que procurar as informações que eu quero.

Eu então abri o site no meu google chrome, cliquei com o botão direito no preço que eu queria e então em inspecionar, conforme print abaixo.

Para analisar o HTML de um elemento de um site com o google chrome, clique nele com o botão direito e então em inspecionar.

Então eu pude perceber que um dos preços de “Withdraw SLP” que eu queria estava dentro de uma tag span, que por sua vez estava dentro de uma tag div e por fim, dentro de uma tag td (Conforme print abaixo). Para quem não conhece muito de HTML, a tag td é como se fosse uma célula de um excel, ou seja, é uma célula de uma tabela, praticamente. E realmente, os valores que nós vemos no site axie.live lembram muito uma tabela.

Beautiful Soup == Fail

Eu precisava pegar o texto contido nas tags td do site, então fui atrás de uma lib de web scraping com python para pegar os dados do preço. Eis que eu encontro a Beautiful Soup, uma das libs mais famosas sobre o assunto. Então um baixei ela e a lib requests, digitando os comandos abaixo no meu terminal.

pip install requests
pip install beautifulsoup4

Com as libs instaladas, fiz o básico, importei a lib requests e a Beautifulsoup. Crio a variável URL e a preencho com o site que eu quero. E quando eu faço requests.get(URL) eu estou na verdade fazendo o que o seu navegador faz para abir um site: uma requisição para ele. No código abaixo, se eu printar a variável r eu vou ter o seguinte resultado <Response [200]>. Você não precisa saber muito sobre isso agora, mas response 200 significa que a request foi um sucesso e nós conseguimos trazer os dados do site.

import requests
from bs4 import BeautifulSoup
URL = "https://axie.live"r = requests.get(URL)print(r)

Eu então criei mais uma linha e printei seu resultado. E então eu recebi o código HTML que eu queria! Pelo menos eu achava isso…

import requests
from bs4 import BeautifulSoup
URL = "https://axie.live"r = requests.get(URL)soup = BeautifulSoup(r.content, "html.parser")print(soup)

No código acima, eu uso a função Beautifulsoup() e uso no primeiro parâmetro r.content pra trazer o conteudo da request do site que está na variável r. E no segundo parâmetro eu defino que esse conteúdo é um código HTML, usando o “html.parser”. Após isso eu printei esse conteúdo que eu armazenei na variável soup, copio e o colo no editor de texto VsCode (Só pq eu gosto dele) e idento o código com alt+ shift + F, para que eu possa ver e entender o código de uma forma mais organizada. O resultado que eu tive foi o que mostra a imagem abaixo.

Código obtido com as libs beautifulsoup + requests, com o JavaScipt desabilitado.

Percebam que no código acima, na linha 28 eu tenho uma mensagem desanimadora: “You need to enable JavaScript to run this app”, ou seja, eu precisava habilitar o JavaScript para executar algo no site. E esse algo provavelmente era o que ia trazer as minhas tags td, que não estava nesse mini código que recebemos. Eu então, fui em busca de outra forma de resolver meu problema e voltei ao google e a inúmeros fóruns e stackoverflows da vida para tentar uma forma de ter o JavaScript habilitado antes de eu pegar o conteúdo HTML do site…

Há uma forma de fazer isso com Beautifulsoup? Pode ser que sim, mas eu resolvi meu problema de outra forma. Se você sabe como resolver com soup, posta um comentário pra ajudar a galera (A mim, inclusive).

Enfim, deu certo!

Depois de um tempo debruçado em fóruns, achei a lib requests-html que foi a a que resolveu meu problema com o JavaScript e eu a instalei no meu terminal com o comando :

pip install requests-html

OBS: para instalar, você vai usar requests-html, mas para importar no seu código, vc usa requests_html. Cuidado para não confundir os dois (pq eu já confundi isso…)! Essa lib tb da problema se vc tenta rodar ela no Jupyter, então procure executa-la no PyCharm, Spyder ou outras IDEs diferentes de Jupyter.

from requests_html import HTMLSessionsession = HTMLSession()url = "https://axie.live/"r = session.get(url)

No código acima, eu importei a lib requests-html e então armazeno na variável session a função HTMLSession(). Parece com o que fizemos no beautiful soup. E novamente salvamos na variável URL o site do axie.live e então, na variável r damos um get() para fazer uma request pro site. Novamente, se você der um print(r), você provalvelmente terá um response 200 novamente. Legal, mas a parte importante vem agora.

A função r.html.render() abaixo faz com que eu renderize o JavaScript daquele site. E os parâmetros sleep=5 me da 5 segundos para eu esperar ele ser renderizado. Já o parâmetro keep_page=True meio que mantém a página renderizada até o meu código finalizar. E pessoal, até agora eu ainda não peguei o HTML. A gente apenas renderizou o JavaScript. Então agora é a hora de pegar o bendito código!

from requests_html import HTMLSessionsession = HTMLSession()url = "https://axie.live/"r = session.get(url)r.html.render(sleep=5, keep_page=True)html_code = r.html.html
print(html_code)

Nas penúltima linha do código acima, eu crio uma variável html_code e coloco nela o conteúdo html do site com r.html.html que dessa vez está com o Js renderizado. E quando eu printo a var hmtl_code eu recebo um código html. Mas dessa vez ele é beeem maior (554 linhas) do que aquele que nós tinhamos recebido qnd o Js não estava renderizado (36 linhas). Abaixo, segue uma imagem desse código aberto no meu VsCode.

No código acima, a gnt ve algumas tags td e o texto que está dentro delas é o preço. Especificamente na de cima, a gente vê 4 “Withdraw SLP” e 4 preços. Esses 4 são os preços do Withdraw SLP Rapid, Fast Standard e Slow. Vou poupar sua vida e te dizer que os nomes das colunas estão em tags th nesse código html.

Então, acrescentando as linhas de código abaixo, eu procurei todas as tags td no html e coloquei elas dentro da td_tags. E fiz o mesmo com as tags th, incluindo-as na variável th_tags.

# Pega todos os dados das tags td e th do arquivo HTML
td_tags = r.html.find("td")
th_tags = r.html.find("th")

print(td_tags)
print(td_tags)

O resultado dos prints da td_tags e th_tags são os seguintes:

1ª linha do log traz o print da variável td_tags e a 2ª linha traz o print da th_tags

Agora, nas últimas 2 linhas do código abaixo, eu trago cada texto que está armazenado nas tags da td_tags e coloco na variável dados_td. E faço o mesmo com o dados_th, mas dessa vez com as tags_th.

from requests_html import HTMLSession


# Abre o site axie.live e espera renderizar o JavaScript
session = HTMLSession()

url = "https://axie.live/"

r = session.get(url)

r.html.render(sleep=5, keep_page=True)

# Pega todos os dados das tags td e th do arquivo HTML
td_tags = r.html.find("td")
th_tags = r.html.find("th")


# Pre processa as listas
dados_td = [tag.text for tag in td_tags]
dados_th = [tag.text for tag in th_tags]

Se eu printar a lista dados_td eu verei o seguinte no log:

[‘Ronin Transactions’, ‘$0’, ‘$0’, ‘$0’, ‘$0’, ‘Deposit ETH’, ‘$67.84’, ‘$57.97’, ‘$57.36’, ‘$57.36’, ‘Withdraw ETH’, ‘$85.75’, ‘$73.28’, ‘$72.5’, ‘$72.5’, ‘Deposit AXS’, ‘$60.2’, ‘$51.45’, ‘$50.9’, ‘$50.9’, ‘Withdraw AXS’, ‘$76.23’, ‘$65.14’, ‘$64.45’, ‘$64.45’, ‘Deposit SLP’, ‘$60.19’, ‘$51.43’, ‘$50.89’, ‘$50.89’, ‘Withdraw SLP’, ‘$79.72’, ‘$68.13’, ‘$67.4’, ‘$67.4’, ‘Ethereum: Send ETH’, ‘$8.7’, ‘$7.43’, ‘$7.36’, ‘$7.36’, ‘Ethereum: Send AXS’, ‘$14.73’, ‘$12.59’, ‘$12.46’, ‘$12.46’, ‘Ethereum: Send SLP’, ‘$15.37’, ‘$13.13’, ‘$12.99’, ‘$12.99’]

E printando a dados_th eu terei:

[‘ETH/USD\xa0$3766.31’, ‘110\nRapid’, ‘94\nFast’, ‘93\nStandard’, ‘93\nSlow’]

Pronto. Agora os dados estão bem mais inteligíveis, certo? A partir daí, eu fiz alguns tratamentos nesses dados até eles ficarem da forma que eu queria, um DataFrame de 1 linha apenas. Eu não vou entrar no detalhe de como fiz esses tratamentos, pois aqui eu quero explicar mais sobre o web scraping e não tanto da manipulação de dados em si. Mas abaixo está o código após a manipulação.

from requests_html import HTMLSession
import pandas as pd
from datetime import datetime
import time


# Open axie.live website and wait it to render JavaScript
session = HTMLSession()

url = "https://axie.live/"

r = session.get(url)

r.html.render(sleep=1, keep_page=True, timeout=60)

#html_code = r.html.html
#print(html_code)

# Get all table data and table headers in the HTML file
td_tags = r.html.find("td")
th_tags = r.html.find("th")

#print(td_tags)

#Pre process lists
dados_td = [tag.text for tag in td_tags]
dados_th = [tag.text for tag in th_tags]

# Get real datetime and turn it into string
now = datetime.now()
now_str=now.strftime("%d/%m/%Y %H:%M:%S")
#print(now_str)

# Bring datetime to the data list
dados_td.append(now_str)

# Pre-process the header list
new_dados_th = []
for i in dados_th:
split_result = i.split('\n')
new_dados_th.append(split_result[-1])

# print(new_dados_th)
# print(dados_td)

# Create the final list with the columns names
h = 1
j = 0
new_header = []
for n in range(40):
new_header.append(new_dados_th[h] + ' ' + dados_td[j])
h += 1
if h > 4:
h = 1
j += 5

new_header.append('DT_HR')
# print(new_header)

# Create the final list with the data
new_dados = []
for n in range(1, 50, 1):
if n % 5 != 0 :
new_dados.append(dados_td[n])

new_dados.append(dados_td[-1])
# print(new_dados)

# print(len(new_header))
# print(len(new_dados))

# Create the dataframe
df = pd.DataFrame(new_dados).transpose()
df.columns = new_header

Eu sei que da pra fazer melhor e mais simples do que a forma como eu manipulei os dados, mas eu fiz assim só pra dar uma treinadinha no meu raciocínio lógico e na minha habilidade de codar.

Bom, mas se você rodou esse código até o final, você terá um DataFrame chamado df que vai se parecer com o print da imagem abaixo. Eu coloquei toda aquela tabela em 1 linha só. E na última coluna desse df ainda coloquei uma variável com a data, hora, min e segundo em que rodamos o nosso código.

Agora que eu tenho esse df, eu quero exporta-lo com o nome “export.csv” para a pasta do meu pc onde eu criei e estou rodando o programa.

Mas eu quero também deixar o meu programa rodando em loop para ser executado a cada minuto e se NÃO EXISTIR um arquivo chamado “export.csv” na pasta, o meu programa deve então exportar o df atual para lá com esse nome. Mas se JÁ EXISTIR esse arquivo na pasta, então o meu programa deve importar esse arquivo csv existente e adicionar a nova linha do df que foi executada naquele momento, para então exporta-lo novamente. Com isso, a cada vez que o programa for executado, uma nova linha será adicionada ao arquivo export.csv.

from requests_html import HTMLSession
import pandas as pd
from datetime import datetime
import time
import os.path

contador = 1

while True:
# Open axie.live website and wait it to render JavaScript
session = HTMLSession()

url = "https://axie.live/"

r = session.get(url)

r.html.render(sleep=10, keep_page=True, timeout=60)

#html_code = r.html.html
#print(html_code)

# Get all table data and table headers in the HTML file
td_tags = r.html.find("td")
th_tags = r.html.find("th")

#print(td_tags)

#Pre process lists
dados_td = [tag.text for tag in td_tags]
dados_th = [tag.text for tag in th_tags]

# Get real datetime and turn it into string
now = datetime.now()
now_str=now.strftime("%d/%m/%Y %H:%M:%S")
#print(now_str)

# Bring datetime to the data list
dados_td.append(now_str)

# Pre-process the header list
new_dados_th = []
for i in dados_th:
split_result = i.split('\n')
new_dados_th.append(split_result[-1])

# print(new_dados_th)
# print(dados_td)

# Create the final list with the columns names
h = 1
j = 0
new_header = []
for n in range(40):
new_header.append(new_dados_th[h] + ' ' + dados_td[j])
h += 1
if h > 4:
h = 1
j += 5

new_header.append('DT_HR')
# print(new_header)

# Create the final list with the data
new_dados = []
for n in range(1, 50, 1):
if n % 5 != 0 :
new_dados.append(dados_td[n])

new_dados.append(dados_td[-1])
# print(new_dados)

# print(len(new_header))
# print(len(new_dados))

# Create the dataframe
df = pd.DataFrame(new_dados).transpose()
df.columns = new_header

# Verify if exist csv file. If true append new df, else just export the actual df
if os.path.isfile('export.csv') == True:

df_import = pd.read_csv(r'C:\Users\vitor\Desktop\Python Programs\Projeto_AxieLive\export.csv')

df2 = df_import.append(df, ignore_index = True)

df2.to_csv(r'C:\Users\vitor\Desktop\Python Programs\Projeto_AxieLive\export.csv', index = False)

else:
df.to_csv(r'C:\Users\vitor\Desktop\Python Programs\Projeto_AxieLive\export.csv', index=False)

print('Feito ', contador, ':', datetime.now())
time.sleep(10)
contador += 1

Com isso, o meu programa final é o que está acima .

E antes de você ir, sobre as últimas alterações que fiz no programa:

Eu criei um loop infinito (até eu parar ou o programa crashar) adicionando um “While True:” no inicio do código, mas eu criei também uma variável contador = 1, antes do loop. Mas ao final do loop eu somo 1 a ela para eu ter uma noção do que aparece no meu log e quantas vezes o loop já foi rodado, como no exemplo abaixo.

E sobre a condicional de exportação, importação e append do meu arquivo “export.csv”, eu fiz o seguinte, conforme o código abaixo. Primeiro eu importei no inicio do código a lib os.path. Com ela eu pude usar o método isfile(‘export.csv’) que me retornaria True caso existisse esse arquivo na pasta onde eu estou rodando o programa e se não existisse o arquivo ele me retornaria False. Caso fosse a primeira vez que eu tivesse executando o código, não existiria arquivo “export.csv” na minha pasta e então eu iria apenas exportar o df com o método do pandas to_csv(). Quando o loop fosse executado 1 min depois e o meu código voltasse para executar essa parte novamente, dessa vez (e das próximas) já existiria um arquivo “export.csv” na pasta de destino. Então eu apenas importaria esse arquivo com o método read_csv() do pandas para o df chamado df_import. Após isso eu crio um df2 onde eu apendo o df_import com o df que eu executei no código. Então é só exportar o df2 e deixar o loop fazer a mágica dele a cada 1 min mais ou menos.

...if os.path.isfile('export.csv') == True:

df_import = pd.read_csv(r'C:\Users\vitor\Desktop\Python Programs\Projeto_AxieLive\export.csv')

df2 = df_import.append(df, ignore_index = True)

df2.to_csv(r'C:\Users\vitor\Desktop\Python Programs\Projeto_AxieLive\export.csv', index = False)

else:
df.to_csv(r'C:\Users\vitor\Desktop\Python Programs\Projeto_AxieLive\export.csv', index=False)
...

Com os dados do csv, eu consegui montar um gráfico. Abaixo está o exemplo de um período do dia 24/ago/2021 para as transações Fast e Rapid. Da pra ver que o mínimo é quase 10% do valor máximo. Se a pessoa que pagou 322 USD de taxa pudessse esperar por uma taxa mais baixa depois das 16h da tarde, ela poderia pagar algo ali em torno de 50 USD de taxa. economizando quase 170 USD ou seja, R$918 se considerarmos o dolar turismo hoje a R$5,40.

E para esse post não ficar muito longo, a parte em que eu criei um bot do telegram para mandar mensagens num grupo quando o preço estivesse baixo, eu vou postar numa 2ª parte deste artigo.

Antes de ir, eu queria agradecer a minha esposa Enza e a minha filha Nina (que hoje está do tamanho de uma manga e se preparando para nascer daqui a 4 meses) pois elas são as maiores forças hoje que fazem com que eu sinta a necessidade de me mover para frente e para cima. Queria agradecer ao Rodrigo Dutcosky que me ajudou com esse programa e que me inspirou a começar a escrever, depois de ler os posts dele. E aos meus pais, meus avós e meus amigos que sempre me incentivaram nos estudos (ao Vinicius principalmente que foi quem me pediu para ensina-lo como fazer web scraping com python, o que gerou esse artigo e os próximos que estão por vir).

Te vejo na parte 2!

--

--