Alguma vez já precisou monitorar links em algum de seus sites ou blogs?
Se você respondeu sim, ótimo, está no lugar certo. Mas se respondeu não, vamos ver se consigo te mostrar porque você deveria se preocupar com isso.
Muitas vezes utilizamos em nossos sites links de páginas que não nos pertencem. Essas páginas podem se tornar indisponíveis a qualquer momento por uma série de motivos: o recurso pode mudar de url, o site pode ser descontinuado, pode passar por instabilidades, etc. Quanto mais rápido um erro como esse for descoberto, mais rápido podemos resolvê-lo.
Claro que existem diversas ferramentas que fazem esse tipo de monitoramento mas alguns motivos me levaram a querer criar uma ferramenta própria:
- Primeiro e mais óbvio, em uma solução própria temos total domínio do código;
- Uma solução própria não depende de limites em ferramentas ou APIs;
- Podemos monitorar todos os links dentro de uma determinada página (e não só links de uma lista);
- Possibilita a integração com qualquer tipo de notificação (neste caso escolhi uma notificação no Slack); e
- Caso você já tenha um servidor, tem custo ZERO!
No momento que precisei fazer isso para este blog percebi que poderia ser uma necessidade de outras pessoas e então resolvi compartilhar aqui minha solução.
Obtendo as Urls
Vamos criar projeto Python com um arquivo com as urls que devem ser monitoradas e um link para o canal do Slack em que a notificação irá aparecer.
urls_to_check = [
{
'channel': 'https://hooks.slack.com/first-hook',
'urls': [
'https://example.com/',
'https://example.com/url-one/',
'https://example.com/url-two/'
]
},
{
'channel': 'https://hooks.slack.com/second-hook',
'urls': [
'https://other.com/',
'https://other.com/url-one/'
]
},
# ...
]
Vamos criar uma classe Url
para manipular os links monitorados, com dois métodos muito importantes:
response_status(self)
: utiliza a biblioteca requests para verificar o status da url quando uma requisição GET é realizada.get_children(self)
: utiliza a biblioteca BeautifulSoup para retornar uma lista de urls da página. Um detalhe é que ele diferencia os links absolutos (que começam comhttp
) dos links relativos (que começam com/
)
import requests
from bs4 import BeautifulSoup
class Url:
def __init__(self, raw, domain_url=None):
self.raw = raw
if '://' in raw: # absolute url
raw_split = raw.split('://')
self.method = raw_split[0]
if '/' in raw_split[1]:
self.domain = raw_split[1].split('/')[0]
else:
self.domain = raw_split[1]
elif domain_url: # relative url
self.method = domain_url.method
self.domain = domain_url.domain
else:
raise Exception('Url must have HTTP method or provide a domain_url.')
self.path = raw.replace('://', '').replace(self.method, '').replace(self.domain, '')
@property
def domain_url(self):
return Url(f'{self.method}://{self.domain}')
def __str__(self):
return f'{self.method}://{self.domain}{self.path}'
def response_status(self):
response = requests.get(self.__str__())
return response.status_code
def get_children(self):
response = requests.get(self.__str__())
soup = BeautifulSoup(response.text, features='html.parser')
urls = set()
for a in soup.find_all('a'):
if a.has_attr('href'):
if a['href'][:4] == 'http':
urls.add(Url(a['href']))
elif a['href'][0] == '/':
urls.add(Url(a['href'], domain_url=self.domain_url))
return list(urls)
Agora que preparamos nossa classe Url
, podemos criar um loop que vai percorrer todos os links e verificar se o código de status de retorno é 200. Para cada link quebrado, queremos enviar uma notificação de erro. Vamos ver como.
Integrando com o Slack
Para ser avisado sobre um link quebrado em nossa aplicação, precisamos integrar com algum sistema de notificação. Você pode integrar diversos serviços como Telegram ou Github. Para esse projeto, escolhi o Slack por ser bem simples e por ter um aplicativo móvel.
Primeiramente, crie um novo canal no Slack ou utilize um que você já participa. No menu lateral, clique em Apps e busque por webhook.
Após adicionar o webhook ao seu canal, você pode configurar a aparência da notificação como a imagem ou o nome. Não esqueça de salvar suas alterações.
Vamos fazer um pequeno teste apenas para ver se nossa integração está funcionando:
import json
import requests
url = 'https://hooks.slack.com/services/sua_url'
data = {
'text': 'Esta é uma linha de texto no canal.\nE esta é outra linha de texto.'
}
response = requests.post(url, json.dumps(data))
print(response)
Se você recebeu uma mensagem como esta então tudo está configurado corretamente.
Monitor
Agora que ja sabemos identificar um link quebrado e sabemos como criar uma notificação, nosso main.py
fica:
import json
import requests
from data import *
from url import Url
for url_to_check in urls_to_check:
for url in url_to_check['urls']:
for child in Url(url).get_children():
print(f'Checking {child}')
if child.response_status() != 200:
body = 'Link quebrado na pagina {url}\n' + \
'- Link: {child}\n'
response = requests.post(
url_to_check['channel'],
json.dumps({ 'text': body })
)
Agendando a tarefa
Agendar a tarefa não é o foco deste artigo, mas vou deixar aqui o caminho de como fazer isso. Deixe um comentário se você acha que seria interessante um post só sobre isso.
Nota: Lembre-se de usar o executável do Python contido no seu virtualenv.
Linux
No Linux, podemos adicionar nosso script ao crontab, para que seja executado todos os dias as 6 horas da manhã. Basta executar crontab -e
e adicionar a linha abaixo ao final do arquivo.
# m h dom mon dow command
0 6 * * * /path/to/virtualenv/bin/python main.py
Windows
No Windows, podemos fazer pela interface gráfica com o Agendador de Tarefas no Painel de Controle.
Código
O código completo deste artigo está disponível no github
broken-links-monitor (this link opens in a new window) by umcodigo (this link opens in a new window)
Monitor broken links and open github issues
Referências
- Documentação do requests.
- Documentação do BeautifulSoup.
- Documentação do Slack.