image

Bootcamps ilimitados e +750 cursos pra sempre

70
%OFF

AK

Ayumu Kitabayashi24/10/2026 23:52
Compartilhe

Por que meu sitemap listava 23 URLs duas vezes: um regex que esperava exatamente dois espaços

    Meu nome é Ayumu Kitabayashi e eu desenvolvo o minulo, um app para iPhone. O site do app, minulo.app, é um site estático em 13 idiomas hospedado no GitHub Pages. Toda vez que eu adiciono a versão de uma página em outro idioma, um pequeno script em Python, o tools/sync_langs.py, reescreve as tags hreflang e as entradas do sitemap dessa página.

    Em 2026-10-25 eu descobri que esse script tinha deixado 23 URLs listadas duas vezes no sitemap.xml. A causa eram dois espaços fixos no começo de um regex.

    O que o script faz

    Você passa um ou mais slugs, por exemplo minulo-vs-stillfeed.html. Para cada slug, o script:

    1. verifica quais pastas de idioma têm esse arquivo
    2. reescreve as tags <link rel="alternate" hreflang> e os links de idioma do rodapé em cada uma dessas páginas
    3. apaga as entradas <url> dessa página no sitemap.xml e acrescenta entradas novas no fim, com os alternates em <xhtml:link>

    O problema estava no passo 3. Antes da correção, a remoção era assim:

    for lc in locs:
      sm=re.sub(r'  <url><loc>'+re.escape(lc)+r'</loc>.*?</url>\n','',sm,flags=re.S)
    

    As entradas que ele não conseguia apagar

    Esse padrão só casa com um <url> que começa com exatamente dois espaços. O próprio script grava as entradas com dois espaços de indentação, então ele sempre conseguia apagar o que ele mesmo tinha escrito.

    Só que o sitemap.xml também tinha entradas antigas, que eu escrevi antes de o script existir, e essas não tinham indentação nenhuma. Na versão anterior à correção, en/minulo-vs-stillfeed.html aparecia na linha 2023 e de novo na linha 9603:

    <url><loc>https://minulo.app/en/minulo-vs-stillfeed.html</loc><lastmod>2026-10-20</lastmod></url>
    <url><loc>https://minulo.app/en/minulo-vs-stillfeed.html</loc><lastmod>2026-10-24</lastmod>
      <xhtml:link rel="alternate" hreflang="ja" href="https://minulo.app/minulo-vs-stillfeed.html"/>
      <xhtml:link rel="alternate" hreflang="en" href="https://minulo.app/en/minulo-vs-stillfeed.html"/>
      ...
    

    A primeira é a entrada antiga: sem indentação, sem alternates, lastmod 2026-10-20. A segunda é a que o script acrescentou, com alternates. A antiga nunca casava com o regex, então ficava lá, e cada execução acrescentava uma entrada nova no fim.

    Por que a duplicação importa

    Para o mesmo <loc>, o sitemap estava dando duas respostas diferentes:

    • dois valores de lastmod, 2026-10-20 e 2026-10-24
    • uma entrada com alternates de hreflang e outra sem

    Este site declara as versões de idioma com <xhtml:link> no sitemap, além do head da página. Qual das duas entradas um buscador usa é decisão do buscador. Se ele pegar a que não tem alternates, o conjunto de idiomas daquela URL pode não chegar até ele.

    Como eu encontrei

    Extraí todos os <loc> e pedi ao uniq -d tudo o que aparecesse mais de uma vez:

    grep -o '<loc>[^<]*</loc>' sitemap.xml | sort | uniq -d
    

    Eu vinha anotando o número de URLs do sitemap toda vez que adicionava páginas. Esse número não mostra duplicatas: ele só sobe, do mesmo jeito que sobe quando entram páginas novas.

    A correção

    1. Aceitar qualquer espaço no começo

    Os dois espaços do começo viraram [ \t]*, ou seja, zero ou mais espaços ou tabs. Agora o padrão casa com as entradas antigas sem indentação e com as novas indentadas. Ler o arquivo com uma biblioteca de XML e reconstruir tudo também funcionaria. Eu mantive o regex e só troquei o prefixo.

    2. Usar a data da execução no lastmod

    Lendo o diff, também percebi que as entradas acrescentadas tinham 2026-10-24 escrito direto na f-string. Rodar o script em qualquer outro dia continuaria gravando 2026-10-24. Agora ele usa datetime.date.today().

    -import os,re,sys
    +import os,re,sys,datetime
    +TODAY=datetime.date.today().isoformat()
    @@
    -        sm=re.sub(r'  <url><loc>'+re.escape(lc)+r'</loc>.*?</url>\n','',sm,flags=re.S)
    +        sm=re.sub(r'[ \t]*<url><loc>'+re.escape(lc)+r'</loc>.*?</url>\n','',sm,flags=re.S)
    @@
    -    add=''.join(f'  <url><loc>{url(d,s)}</loc><lastmod>2026-10-24</lastmod>\n{alts}  </url>\n' for _,d,_ in have)
    +    add=''.join(f'  <url><loc>{url(d,s)}</loc><lastmod>{TODAY}</lastmod>\n{alts}  </url>\n' for _,d,_ in have)
    

    3. Remover de uma vez as duplicatas que já estavam no arquivo

    Corrigir o regex só limpa uma URL na próxima vez que o slug dela for processado. Para limpar o arquivo atual, rodei um script avulso que mantém o último <url> de cada <loc>. O script de sincronização sempre acrescenta no fim, então a última ocorrência é a entrada mais nova.

    import re, sys
    p = sys.argv[1]
    sm = open(p).read()
    pat = re.compile(r'[ \t]*<url><loc>(.*?)</loc>.*?</url>\n', re.S)
    blocks = list(pat.finditer(sm))
    last = {m.group(1): i for i, m in enumerate(blocks)}
    drop = [m for i, m in enumerate(blocks) if last[m.group(1)] != i]
    for m in reversed(drop):
      sm = sm[:m.start()] + sm[m.end():]
    open(p, 'w').write(sm)
    print("removed", len(drop))
    

    Conferindo o resultado

    $ grep -c '<loc>' sitemap.xml
    899
    $ grep -o '<loc>[^<]*</loc>' sitemap.xml | sort | uniq -d | wc -l
         0
    $ python3 -c "import xml.etree.ElementTree as ET; print(len(ET.parse('sitemap.xml').getroot()))"
    899
    

    899 URLs, 0 duplicatas, e o arquivo é lido como XML sem erro.

    O que fica

    • Antes de apagar linhas com regex, confira se o arquivo tem linhas escritas num formato mais antigo.
    • Depois de reescrever o arquivo, procure valores de <loc> duplicados, e não só o total.
    • Não deixe datas fixas no código que gera arquivos.

    O minulo é um app para iPhone que oculta os Reels do Instagram e os Shorts do YouTube: https://minulo.app/pt/?ref=dio-pt-sitemap-1025d

    Compartilhe
    Recomendados para você
    Neo4j - Certification
    DXC - IA do Zero ao Primeiro Agente
    Reclame AQUI - Dados e IA na Prática
    Comentários (0)