Follow Us

Como monitorar links quebrados em suas páginas com Python

Como monitorar links quebrados em suas páginas com Python

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:

  1. Primeiro e mais óbvio, em uma solução própria temos total domínio do código;
  2. Uma solução própria não depende de limites em ferramentas ou APIs;
  3. Podemos monitorar todos os links dentro de uma determinada página (e não só links de uma lista);
  4. Possibilita a integração com qualquer tipo de notificação (neste caso escolhi uma notificação no Slack); e
  5. 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 com http) 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.

Criando um novo webhook no Slack
Criando um novo webhook no Slack

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.

Mensagem de notificação no Slack
Mensagem de notificação no Slack

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

Referências

  1. Documentação do requests.
  2. Documentação do BeautifulSoup.
  3. Documentação do Slack.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *