DEV Community

1

O robô entusiasta de poesia

Um fantasma de mil anos habita o Twitter: o fantasma do poeta japonês Ki no Tsurayuki (紀貫之, ca.872 — ca. 945). Na verdade, não é fantasma nenhum; trata-se de um bot que a cada duas horas posta poemas aleatórios extraídos da coleção Kokin Wakashū.

A ideia é tão legal que eu resolvi programar meu próprio robô entusiasta de poesia, só que em vez de poemas japoneses, meu Charles Robaudelaire seria um robô de poesia francesa, extraída do acervo poetica.fr.

A versatilidade — diversidade de tarefas às quais a ferramenta pode servir — é um dos maiores atrativos da linguagem Python. A riqueza de bibliotecas open source é igualmente grande no Javascript, é verdade. Mas esta linguagem sempre me pareceu ligada muito fortemente à manipulação do DOM do navegador. Ser general-purpose desde o princípio, e não por expansão, me fez confiar muito mais em Python.

Construindo o scraper

O Robaudelaire precisava ter sua biblioteca, isto é, um banco de dados de poemas que serviriam de conteúdo para cada tweet. Para criá-la, havia duas opções: buscar uma API que providenciasse os dados dos poemas em formato JSON, ou extrair essa informação de sites que a contém, mas não providenciam uma API, por meio de um scraper.

Scrapers e crawlers não são exatamente a mesma coisa, mas são parecidos — como um prego e um parafuso. Deixo aqui uma definição rasa, só para esclarecer e seguir com a história:

Scrapers são scripts que visitam uma página da web e traduzem (parse) o código de forma a deixá-lo interpretável e navegável pela linguagem de programação. Desta forma, é possível buscar, listar, filtrar e manipular os elementos do html como quaisquer objetos. Crawlers são scripts que navegam as várias páginas de um site repetidamente, como um aventureiro que explora as áreas novas de um mapa. Como as duas atividades são fundamentais para o nosso programa, chamarei simplesmente de scraper.

O scraper foi desenvolvido utilizando a biblioteca Beautiful Soup 4, que faz exatamente o que está escrito na definição acima, portanto não é necessário repetir. Consiste em três funções: get_poem_list() acessa a página de um autor e retorna a lista de todos os seus poemas; get_poem_detail() acessa um poema individual e retorna o texto completo e o nome do autor devidamente formatado para o tweet (get_poem_detail() não será usada para construir a biblioteca); scrape_site() acessa o acervo e, utilizando get_poem_list() em cada um dos autores, retorna a lista total de poemas, com nome do autor, título e link.

from bs4 import BeautifulSoup as soup
import requests
BASE_URL = "BASE URL" # USE CORRECT URL FOR SOURCE
flatten = lambda l: [item for sublist in l for item in sublist] #FLATTEN NESTED LISTS
# we will get all authors and links and zip them in a single list
def get_authors(lis):
jackie = map(lambda x: x.string, lis)
return list(jackie)
def get_urls(lis):
jackie = map(lambda x: x.a['href'], lis)
return list(jackie)
# we access a single author's url and get data for all available poems
# get each poem as {'author':name,'title':title,'link':url}
def get_poem_list(url):
author_html = requests.get(url).text
author_soup = soup(author_html, "html.parser")
author_articles = author_soup.findAll("div",{'class':'ELEMENT CLASS') #USE CORRECT ELEMENT CLASS
author_poems = map(lambda x: {"title": x.header.a.text, "link": x.header.a['href']} ,author_articles)
return list(author_poems)
def get_poem_detail(url):
poem_html = requests.get(url).text
poem_soup = soup(poem_html, "html.parser")
text = poem_soup.find("div",{'class':'entry-content'}).text.replace("\xa0", "").replace('\n',' ')
header = poem_soup.find('header',{'class':'entry-header'})
return {'author': header.div.text,'title':header.h1.text,'text':text}
#get a list of objects as: {'name': name, 'poems': get_poem_list(idx)}
def scrape_site():
base_html = requests.get(BASE_URL).text
base_soup = soup(base_html, "html.parser")
author_menu = base_soup.find("ul",{"id":"MENU SELECTOR"}) #USE CORRECT SELECTOR
author_list = author_menu.findAll('li')
authors = get_authors(author_list)
urls = get_urls(author_list)
library = list(zip(authors,urls))[:-1]
all_authors = list(map(lambda x: {'author':x[0],'poems': get_poem_list(x[1])},library))
return flatten(all_authors)
view raw scrapers.py hosted with ❤ by GitHub

A função get_poem_list() opera como um procedimento interno da função principal scrape_site(), que além fazer o scrape da página inicial para montar a lista “library” de autores e urls, funciona como um crawler que navega a página de cada autor, por meio da função map(…).
Rodando o scraper, preenchemos a biblioteca com 3.000 poemas de 150 autores. Restava apenas salvá-la no banco de dados como o arquivo .csv a ser consultado para criar os tweets. Em caso de dúvida sobre como salvar o objeto em .csv, segue o snippet.

import csv
def read_csv(file):
csv_file = open(file)
reader = csv.reader(csv_file)
alpha = list(reader)
csv_file.close()
return alpha
def write_csv(file,method,content):
csv_file = open(file,method,newline='')
fieldnames = content[0]
writer = csv.DictWriter(csv_file,fieldnames=fieldnames)
writer.writeheader()
writer.writerows(content)
print('created csv file')
csv_file.close()
view raw csv_manage.py hosted with ❤ by GitHub

Construindo o objeto Poem

Tendo a biblioteca em mãos, era hora de fazer os tweets — primeiro em texto, antes de postar no Twitter. Após algumas tentativas, decidi que a melhor abordagem seria criar instâncias/objetos de uma classe Poem, que teria em si não apenas os atributos obtidos da biblioteca, mas também dois métodos importantes: get_poem() e get_tweet().

Você deve ter percebido que a biblioteca que salvamos no banco de dados não contém os texto dos poemas. É claro que o Robaudelaire precisa desses textos para citar nos tweets. Para isso, cada instância do objeto Poem possui o método get_poem(), que chama a função get_poem_details() dos scrapers e retorna o poema completo.

from scrapers import get_poem_detail
class Poem:
def __init__(self,author,title,link):
self.author = author
self.title = title
self.link = link
def get_poem(self):
text = get_poem_detail(self.link)
return text
def get_tweet(self):
return f"\"{self.get_poem()['text'][1:121]}...\" -- \"{self.get_poem()['title']}\", {self.get_poem()['author'].upper()}: {self.link}"
view raw poem.py hosted with ❤ by GitHub

Postando no twitter por meio da API

O Robaudelaire já tinha uma biblioteca cheia e o método para criar seus tweets. O último passo era postá-los no Twitter.
Para isso, era necessário criar uma conta na plataforma para developers do Twitter, vinculá-la à conta, registrar o app (i.e., o robô) e adquirir as quatro chaves para autenticação, api key, api secret key, access token, access token secret. (Esta parte é um desafio que vou deixar você enfrentar sozinho, porque a explicação seria longa, fácil e desinteressante).
Com as quatro chaves, é possível autenticar na API e postar o tweet com o método api.update_status(). Utilizei a biblioteca pandas para ler o arquivo .csv e extrair um poema aleatório com o método data.sample().

import tweepy
from secrets import secrets
# Authenticate to Twitter
auth = tweepy.OAuthHandler(API_KEY, SECRET_KEY)
auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
# Create API object
api = tweepy.API(auth)
view raw tweeter.py hosted with ❤ by GitHub
import pandas as pd
from csv_manage import read_csv, write_csv
from poem import Poem
from tweeter import api as tweeter
df = pd.read_csv('poem_list.csv')
(author,title,link) = tuple(df.sample(1).iloc[0])
poem = Poem(author,title,link)
tweet = poem.get_tweet()
tweeter.update_status(tweet)
view raw main.py hosted with ❤ by GitHub

Com o arquivo main.py rodando, o loop infinito while True: posta um tweet a cada intervalo configurado no método time.sleep(). Na hora de configurar o intervalo de tweets, lembre-se que a API impõe um limite de acessos para evitar spam.

Conclusão

O Robaudelaire foi um programa fácil de se criar, mas um bom exercício para aprender a desenvolver scrapers e crawlers, ler e escrever arquivos, desenhar classes, interagir com APIs.
Por enquanto, ele só posta citações com links para o site que generosamente disponibiliza o acervo de poemas (espero que não se incomodem por eu pegá-lo emprestado). Mas há outras funcionalidades que podem ser desenvolvidas no futuro; por exemplo: retweetar conteúdos relevantes, responder automaticamente a mensagens e citações, entre outras.

Mas essa é outra história. Agradeço ao site poetica.fr por fornecer gratuitamente na internet um acervo de poesia tão impressionante. Se você gostou deste projeto e quer tirar dúvidas ou conversar, me mande um alô pelo Instagram ou me adicione no Github.

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more