domingo, 10 de março de 2013

Como gerar sitemaps a partir dos modelos do Django

Antes de qualquer coisa, o que é um sitemap?

É um formato de arquivo XML, definido aqui, que possui links para as páginas do seu site. Seu objetivo é facilitar o trabalho dos crawlers (bots de sistemas de busca que varrem os sites para indexação de conteúdo). Um dos critérios principais que todo crawler usa é não dar muita relevância para páginas muito profundas do seu site, ou melhor, páginas em que o usuário deve clicar em muitos links para chegar. Para essas páginas o sitemap é especialmente importante, pois ele ajuda a dar uma relevância muito maior a essas páginas, que poderiam ser consideradas pouco significantes para o crawler.

Sitemaps também facilitam muito o trabalho dos crawlers, os quais vão poder indexar um volume grande de páginas de seu site rapidamente, pois não vão precisar ficar varrendo página por página em busca de links. Porém, para sites muito grandes é aconselhável fazer múltiplos sitemaps, divididos em seções, para que não haja muitos links em uma página.
Não há um valor exato para o limite de links por sitemap, o crawler da Google aceita sitemaps com até 50000 links ou 10MB. O que é muita coisa para web e nem todo crawler aguenta tanto. Eu não tenho nenhum site grande o bastante para ter sitemaps tão grandes, mas se tivesse os limitaria para uns 5000 links para não correr o riscos, porém a escolha é sua. Neste site dizem que um limite de 10000 links já não dá problemas.

Você pode tanto criar os seus sitemaps na "raça" quanto utilizar o sistema de views do django para isso. É muito mais simples utilizar o sistema de sitemaps, depois que aprendemos. Eu tinha feito primeiro na unha, pois as informações, como usual, estão sempre muito dispersas por aí e precisava feito logo.
Bem, aqui temos um sitemap de exemplo para só uma página:
<?xml version="1.0" encoding="UTF-8" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://www.legauss.blogspot.com.br/</loc>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
</urlset>

A única coisa diferente do usual é que você tem de mudar o mimetype retornado pela view. Ou melhor, dizer qual tipo de dado você está retornando. O padrão de retorno das views é text/plain (texto simples), mas aqui teremos que usar application/xml. Tem text/xml, mas ele não deixa mudar o charset enquanto que no outro podemos defini-lo segundo este site aqui (em inglês).
Um exemplo de como especificar o formato de arquivo retornado por uma view:
def view_exemplo(request):
    return HttpResponse( 'template_sitemap.html', mimetype='application/xml' )

Como gerar um sitemap em Django?

O sistema de sitemaps do django é bem elaborado e permite diversas funcionalidades, porém vamos nos ater ao básico neste post. As informações que precisei para conseguir gerar meus sitemaps foram pegas neste blog e na documentação oficial. Primeiro, teremos que configurar o settings.py:
INSTALLED_APPS = (
        # ...
        'django.contrib.sitemaps'  # --- adicione esta linha
    )
Precisamos especificar que o pacote de sitemaps do django vai ser usado no projeto. Ou ele não será carregado e nada do que fizermos vai funcionar.

Os sitemaps que vamos gerar serão baseados em models. Ou seja, um sitemap para cada objeto de um model. Como, por exemplo, na listagem de produtos de uma loja. Cada produto terá que ter um permalink para ser utilizado no sitemap. Pode ser simplesmente a url que o seu sistema usa por padrão, mesmo que seja algo ininteligível para seres humanos. O importante mesmo é o crawler indexá-las.
Para isso, temos que preparar nossos Models para serem utilizados para gerar sitemaps. Aqui tem um model "Exemplo" já preparado:
# -*- coding: utf-8 -*-

from django.db import models
from django.utils.encoding import smart_str

class Exemplo(models.Model):
    nome = models.CharField(max_length=30, null=False, blank=False)
    nome.verbose_name = 'Nome'
    nome.help_text = 'Título do objeto'

    data = models.DateTimeField(auto_now=True)
 
    permalink = models.CharField(max_length=255, null=True, blank=True, editable=False)
    distinguisher = models.IntegerField(default=0, editable=False, null=True, blank=True)
    
    #gera o permalink do objeto e utiliza 
    def save(self, *args, **kwargs):
        from django.template.defaultfilters import slugify
        if not self.id:
            i = 0
            temp = slugify(self.nome)
            while Exemplo.objects.filter(permalink=temp).count() > 0:
                temp = slugify(self.nome)+'-'+str(i)
                i += 1
            self.permalink = temp
            self.distinguisher = i
        else:
            try:
                aux = int(self.permalink.split('-')[-1])
                self.distinguisher = aux
            except:
                pass
  super(Exemplo, self).save(*args, **kwargs)
  return self
    #este ponto é crucial, é a alma desta forma de gerar sitemaps
    @models.permalink
    def get_absolute_url(self):
        return ( 'exemplo_exemplo', [self.permalink] )
    
    class Meta:
        ordering = ['-id', ]

Note que dentro do método save geramos um permalink com o acréscimo de uma variável para distinguir entre dos objetos com nomes iguais. Temos um campo data, que salva a data da última alteração (auto_now=True, se for só armazenar quando foi criado é auto_now_add=True). Mas o mais importante é o método get_absolute_url. Ele retorna os valores para gerar o permalink de cada objeto. Os valores são exatamente os mesmos que os utilizados pelo método reverse. No caso, teríamos urls.py configurado da seguinte forma:
...
urlpatterns = patterns('projeto.exemplo.views',
    (r'(?P<permalink>.*)/$', 'view_exemplo', {}, 'exemplo_exemplo'),
)
...

Tudo configurado e funcionando? Só falta gerar os sitemaps. Vamos editar novamente o arquivo urls.py:
...
from django.contrib.sitemaps import GenericSitemap
from projeto.exemplo.models import Exemplo, Exemplo2

exemplo_dict = {'queryset': Exemplo.objects.filter(ativo=True), 'date_field': 'data'}
exemplo2_dict = {'queryset': Exemplo2.objects.filter(ativo=True), 'date_field': 'data'}
sitemaps = {
    'exemplo': GenericSitemap(exemplo_dict),
    'exemplo2': GenericSitemap(exemplo2_dict),
} urlpatterns += patterns('django.contrib.sitemaps.views', (r'^sitemap\.xml$', 'index', {'sitemaps': sitemaps}), (r'^sitemap-(?P<section>.+)\.xml$', 'sitemap', {'sitemaps': sitemaps}),
)

É bem simples o uso e funcionamento disso. Importamos o objeto GenericSitemap, que é o mais simples para gerar os sitemaps. Veja que primeiro criamos alguns dicionários com a consulta para selecionar todos os objetos a terem suas urls inseridas no seu sitemap, depois criamos um novo dicionário instanciando os objetos GenericSitemap para serem referenciados nos padrões de url.

Como isso será exibido? Será exibido em vários arquivos conforme os padrões de url criados. O segundo padrão, /sitemap-(?P<section>.+).xml, vai listar todas as urls de cada GenericSitemap. No exemplo, temos o exemplo e exemplo2, que serão exibidos nas urls /sitemap-exemplo.xml e /sitemap-exemplo2.xml respectivamente. Enquanto que o sitemap principal, acessado por /sitemap.xml vai listar os demais sitemaps.
Tudo funcionando? É só enviar o sitemap principal para os sistemas de busca indexarem. ;)

Ah! Não se esqueça de configurar o seu arquivo robots.txt. É outro padrão para auxílio de crawlers. Este é um robots.txt que indica que tudo em seu site pode ser indexado, e é o que utilizo em meu site:
User-agent: *
Allow: /*
Sitemap: http://site-exemplo.com.br/sitemap.xml

O robots.txt indica tudo de seu site que pode ou não ser indexado. Porém, ele costuma para especificar que diretórios devem ou não ser indexados, diferente das meta tags html, que definem página por página. Embora seja possível fazer isso no robots.txt, não é recomendável.

Notou que especificamos a localização do nosso sitemap no robots.txt? É uma boa prática especificar essa localização, mesmo que ele esteja no local padrão, a raiz do site. Pois poderia não ser. Sem contar que nunca sabemos como funciona os crawlers de tantos sistemas de busca.

Bem, para deixar seu robots.txt na raiz do site, utilizando Django, coloquei meu arquivo robots na base do diretório templates do meu projeto e adicionei a seguinte url ao meu url.py:
urlpatterns += patterns('django.views.generic.simple',
    (r'^robots\.txt$', 'direct_to_template', {'template': 'robots.txt', 'mimetype': 'text/plain'}),
)

Pronto! Só esperar algum tempo e seu site estará indexado. ;)




Nenhum comentário: