Создание большой XML-карты сайта для Django

Предположим, что у вас так много страниц (тысячи), что вы не можете просто создать один файл /sitemap.xml, в котором перечислены все URL-адреса (он же <loc>). Поэтому вам нужно создать /sitemaps.xml, который указывает на другие файлы карты сайта. А если адресов в каждом тысячи, то нужно сжать эти файлы.

В статье показано, как создать файл карты сайта, который указывает на 63 файла sitemap-{M}-{N}.xml.gz, которые охватывают около 1 000 000 URL-адресов. Контекст здесь - это Python, а получение данных происходит из Django. Python - ключ к успеху, но если у вас есть что-то отличное от Django, вы можете присмотрется к самой идее и мысленно заменить его на свой собственный преобразователь данных.

Генерация .xml.gz файлов

Вот сама суть работы. Функция генератора, принимающая экземпляр Django QuerySet (который упорядочен и фильтруется!), начинает генерировать деревья etree и выгружает их на диск с помощью gzip.

import gzip

from lxml import etree


outfile = "sitemap-{start}-{end}.xml"
batchsize = 40_000


def generate(self, qs, base_url, outfile, batchsize):
    # Use `.values` to make the query much faster
    qs = qs.values("name", "id", "artist_id", "language")

    def start():
        return etree.Element(
            "urlset", xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        )

    def close(root, filename):
        with gzip.open(filename, "wb") as f:
            f.write(b'<?xml version="1.0" encoding="utf-8"?>\n')
            f.write(etree.tostring(root, pretty_print=True))

    root = filename = None

    count = 0
    for song in qs.iterator():
        if not count % batchsize:
            if filename:  # not the very first loop
                close(root, filename)
                yield filename
            filename = outfile.format(start=count, end=count + batchsize)
            root = start()
        loc = "{}{}".format(base_url, make_song_url(song))
        etree.SubElement(etree.SubElement(root, "url"), "loc").text = loc
        count += 1
    close(root, filename)
    yield filename

Наиболее важные строки с точки зрения lxml.etree и sitemaps:

root = etree.Element("urlset", xmlns="http://www.sitemaps.org/schemas/sitemap/0.9")
...         
etree.SubElement(etree.SubElement(root, "url"), "loc").text = loc

Еще одна важная вещь - примечание об использовании .values(). Если вы этого не сделаете, Django создаст экземпляр модели для каждой строки, возвращаемой итератором. Это дорого.

Еще одна важная вещь - использовать итератор Django ORM, поскольку это гораздо эффективнее, чем возиться с ограничениями и смещениями.

Генерация карты карт

Создание карты карт не нуждается в сжатии, так как она будет крошечной.

def generate_map_of_maps(base_url, outfile):
    root = etree.Element(
        "sitemapindex", xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
    )

    with open(outfile, "wb") as f:
        f.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
        files_created = sorted(glob("sitemap-*.xml.gz"))
        for file_created in files_created:
            sitemap = etree.SubElement(root, "sitemap")
            uri = "{}/{}".format(base_url, os.path.basename(file_created))
            etree.SubElement(sitemap, "loc").text = uri
            lastmod = datetime.datetime.fromtimestamp(
                os.stat(file_created).st_mtime
            ).strftime("%Y-%m-%d")
            etree.SubElement(sitemap, "lastmod").text = lastmod
        f.write(etree.tostring(root, pretty_print=True))
Вернуться на верх