Отсюда хоть три года скачи, ни до какого государства не доскачешь (Н.В. Гоголь).

Технический SEO-аудит с помощью Curl и Bash: руководство по Linux (2026)


Технический SEO-аудит с помощью Curl и Bash: руководство по Linux (2026)

Технический SEO-аудит не обязательно начинать с дорогого краулера или сложной аналитической платформы. Многие важные параметры сайта можно проверить средствами Linux: выяснить, какие HTTP-ответы получает поисковый робот, найти лишние перенаправления, проверить директивы индексации, изучить серверные журналы и измерить время ответа.

В этой статье разберём последовательный подход к техническому аудиту сайта с помощью командной строки. Он подойдёт владельцам сайтов, администраторам серверов, веб-разработчикам и SEO-специалистам, которым важно не просто увидеть ошибку, но и понять её причину.

В качестве основных инструментов используются curl, dig, grep, awk, zcat и Python 3. Для анализа HTML в автоматизированной проверке применяется встроенный модуль html.parser: он надёжнее регулярных выражений и не требует установки сторонних библиотек.

 

Что может и чего не может технический SEO-аудит из командной строки

Командная строка позволяет проверить технические сигналы, которые сервер отдаёт при запросе страницы. Это полезно для диагностики доступности сайта, редиректов, метатегов, HTTP-заголовков, DNS и серверных ошибок.

Однако такой аудит не равнозначен полноценному сканированию сайта. Простой HTTP-клиент не выполняет JavaScript, не оценивает качество контента и не определяет позиции страниц в поисковой выдаче. Он также не измеряет пользовательские Core Web Vitals так, как это делают браузерные инструменты и системы сбора полевых данных.

Важно различать три уровня проверки:

  • Технический ответ сервера. Код HTTP, заголовки, редиректы, полученный HTML и время сетевого запроса.
  • Состояние страницы. Title, Description, H1, canonical, robots, ссылки и другие элементы исходного HTML.
  • Фактическое состояние в поисковой системе. Результат обработки страницы роботом, выбранный поисковиком canonical, наличие страницы в индексе и её видимость в поиске.

 

Эти уровни связаны, но не заменяют друг друга. Например, ответ 200 OK означает успешное выполнение HTTP-запроса, но сам по себе не подтверждает, что страница индексируется или должна индексироваться.

 

1. Подготовка Linux и базовая диагностика HTTP

1.1. Установка необходимых инструментов

В Ubuntu и Debian установите базовый набор утилит:

sudo apt update
sudo apt install curl dnsutils grep gawk sed coreutils python3

 

curl отправляет HTTP-запросы, dig и host проверяют DNS, а стандартные утилиты Linux помогают фильтровать и анализировать результаты. Python 3 понадобится для разбора HTML и формирования отчёта.

1.2. Проверка заголовков ответа

curl -I https://example.com/

 

Ключ -I отправляет запрос методом HEAD и выводит заголовки без тела страницы. В ответе полезно посмотреть:

  • строку состояния, например HTTP/2 200;
  • Content-Type — тип возвращаемого содержимого;
  • Location — адрес назначения перенаправления;
  • Cache-Control, Expires, ETag — параметры кэширования;
  • X*-Robots-Tag — директивы для поисковых роботов.

 

Учитывайте, что некоторые серверы обрабатывают HEAD и GET по-разному. Если результат выглядит подозрительно, выполните обычный GET-запрос.

1.3. Проверка обычного GET-запроса

curl -sS -D - -o /dev/null https://example.com/

 

Команда отправляет GET, выводит заголовки ответа и не сохраняет тело документа. Это более показательная проверка, когда нужно увидеть ответ на обычное обращение к странице.

1.4. Сохранение HTML для изучения

curl -sS -L --max-time 25 https://example.com/ -o page.html
grep -i '<title' page.html
grep -i 'name="description"' page.html
grep -i 'rel="canonical"' page.html

 

Так можно быстро найти основные элементы исходного HTML. Но grep не является HTML-парсером: он может пропустить тег с необычным порядком атрибутов, переносами строк или одинарными кавычками. Для автоматической проверки большого числа страниц лучше использовать парсер.

1.5. Проверка цепочки перенаправлений

curl -sSIL --max-redirs 10 --max-time 25 http://example.com/

 

В выводе будут видны последовательные HTTP-ответы и заголовки Location. Например:

HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/
HTTP/2 200

 

В этом примере запрос перенаправляется один раз и завершается успешным ответом. Если переходов несколько, стоит проверить правила веб-сервера, CDN, CMS и настройки предпочтительного адреса сайта.

1.6. Получение итогового URL и времени запроса

curl -sS -L --max-redirs 10 --max-time 25 -o /dev/null \
  -w 'HTTP: %{http_code}\nRedirects: %{num_redirects}\nFinal URL: %{url_effective}\nTotal: %{time_total}s\n' \
  https://example.com/

 

Параметр -L разрешает следовать редиректам, а --max-redirs ограничивает их количество. Время time_total показывает продолжительность выполнения запроса с учётом сети и получения ответа. Это не оценка полной скорости отображения страницы в браузере.

1.7. Что curl не видит

curl загружает ответ сервера, но не исполняет JavaScript и не строит полноценную браузерную модель страницы. Если важный текст, ссылки, canonical или метаданные добавляются скриптами после загрузки, простой запрос может показать не всю картину.

Для проверки рендеринга используйте браузерные инструменты: Chrome DevTools, Lighthouse или проверку URL в Google Search Console. При необходимости можно получить отрендеренный DOM через Chromium в headless-режиме:

chromium --headless --no-sandbox --disable-gpu \
  --dump-dom https://example.com/

 

Путь к исполняемому файлу может отличаться в зависимости от дистрибутива. Не запускайте браузер от root без понимания последствий и используйте подходящий режим изоляции.

 

2. Проверка сканирования и индексации

2.1. Анализ robots.txt

curl -sS -D - https://example.com/robots.txt

 

Проверьте код ответа и содержимое файла. Типичный пример:

User-agent: *
Disallow: /private/
Sitemap: https://example.com/sitemap.xml

 

Особое внимание уделите широким запретам, например Disallow: /. Такое правило может закрыть сканирование всего сайта для соответствующей группы роботов. Оно часто появляется на тестовых сайтах и иногда забывается при переносе проекта на основной домен.

Robots.txt управляет доступом роботов к URL, но не является надёжным способом удалить страницу из индекса. Если поисковик уже знает адрес, он в некоторых случаях может показать URL без содержимого. Для исключения страницы из индекса применяют корректные директивы индексации и другие подходящие механизмы.

2.2. Проверка sitemap.xml

curl -sS -D - https://example.com/sitemap.xml | head -n 40

 

Убедитесь, что карта сайта доступна, возвращает XML, а не HTML-страницу ошибки, и содержит актуальные URL. В карту обычно включают канонические страницы, которые должны индексироваться и возвращают успешный ответ.

Если используется индекс sitemap, он может ссылаться на несколько отдельных карт. Для надёжной обработки больших или вложенных карт лучше использовать XML-парсер, а не извлекать адреса регулярными выражениями.

2.3. Проверка Meta* Robots

grep -iE '<meta*[^>]+name=["'\"'\"']robots' page.html

 

Пример директивы:

<meta* name="robots" content="noindex,follow">

 

noindex запрещает индексировать страницу для поддерживающих эту директиву поисковых систем, если робот смог получить и обработать страницу. Проверьте, не попала ли такая директива на важные разделы сайта из-за настроек CMS, SEO-плагина или шаблона.

2.4. Проверка X*-Robots-Tag

curl -sSI https://example.com/ | grep -i '^x*-robots-tag:'

 

Директивы индексации могут передаваться не только через HTML, но и через HTTP-заголовок X*-Robots-Tag. Это особенно важно для PDF, изображений и других файлов, у которых нет HTML-метатегов.

2.5. Проверка canonical

grep -i 'canonical' page.html

 

Canonical указывает поисковой системе предпочтительный URL для группы одинаковых или близких по содержанию страниц. Это сигнал, а не безусловная команда: поисковик может выбрать другой адрес.

Проверьте, что canonical ведёт на существующий URL, использует правильный протокол и предпочтительный хост, не содержит случайных параметров и не указывает на страницу с ошибкой, редиректом или запретом индексации.

2.6. Проверка DNS

dig example.com A
dig example.com AAAA
dig www.example.com A
dig example.com NS

 

DNS-ошибки могут сделать сайт недоступным полностью или частично. Сравните ответы с настройками домена и убедитесь, что записи A, AAAA и CNAME ведут на ожидаемую инфраструктуру. Если сайт работает через CDN, учитывайте, что адреса могут принадлежать CDN, а не исходному серверу.

2.7. Проверка реакции на User-Agent

curl -sS -A 'Googlebot' -o /dev/null -w '%{http_code}\n' https://example.com/

 

Это только тест реакции сайта на строку User-Agent. Подстановка слова Googlebot не превращает запрос в настоящий запрос поискового робота. Для подтверждения подлинности Googlebot анализируют IP-адрес и выполняют обратную и прямую DNS-проверку по официальной процедуре Google.

 

3. Аудит списка URL и перенаправлений

3.1. Подготовка списка страниц

Создайте файл urls.txt, указав по одному адресу на строку:

https://example.com/
https://example.com/about/
https://example.com/catalog/
https://example.com/contacts/

 

В список можно включить URL из sitemap, важных разделов сайта, внутренней навигации и страниц, которые недавно изменялись. Для первого запуска разумно проверить небольшой набор, а затем расширить его.

3.2. Проверка кодов состояния

while IFS= read -r url || [ -n "$url" ]; do
  [ -z "$url" ] && continue
  case "$url" in \#*) continue ;; esac
  curl -sS -L --max-redirs 10 --max-time 25 -o /dev/null \
    -w '%{http_code} redirects=%{num_redirects} final=%{url_effective}\n' \
    "$url"
done < <(tr -d '\r' < urls.txt)

 

Ключ -L показывает итоговый ответ после редиректов. Поэтому в выводе будет конечный код, а количество перенаправлений укажет, сколько переходов потребовалось.

3.3. Проверка первого ответа без перехода

curl -sS -o /dev/null -w 'HTTP: %{http_code}\nRedirect: %{redirect_url}\n' \
  http://example.com/

 

Этот запрос не следует за перенаправлением и позволяет увидеть исходный статус и адрес назначения. Такой тест полезен, когда нужно отдельно проверить 301, 302, 307 или 308.

3.4. Проверка четырёх вариантов домена

Проверьте HTTP и HTTPS, а также варианты с www и без него:

for url in \
  http://example.com/ \
  http://www.example.com/ \
  https://example.com/ \
  https://www.example.com/
do
  curl -sS -L --max-redirs 10 --max-time 25 -o /dev/null \
    -w '%{http_code} hops=%{num_redirects} %{url_effective}\n' "$url"
done

 

Все варианты должны вести к одной согласованной предпочтительной версии сайта. Какой именно вариант выбрать — с www или без него — не принципиально. Важно, чтобы внутренние ссылки, canonical, sitemap и редиректы не противоречили друг другу.

3.5. Проверка несуществующего URL и soft 404

curl -sS -L -o /dev/null -w '%{http_code} %{url_effective}\n' \
  https://example.com/page-that-does-not-exist-987654

 

Для действительно отсутствующей страницы обычно ожидается 404 или 410. Если сервер возвращает 200 с шаблоном ошибки, это может быть soft 404. Перенаправление всех несуществующих URL на главную также не всегда является корректным решением: адрес должен вести на действительно подходящую замену, если она существует.

3.6. Как трактовать коды ответа

  • 200 — запрос выполнен успешно. Проверьте, что в ответе действительно нужная страница, а не заглушка.
  • 301 и 308 — постоянное перенаправление. Проверьте правильность целевого URL и отсутствие лишних переходов.
  • 302 и 307 — временное перенаправление. Убедитесь, что временный характер ответа соответствует задаче.
  • 401 и 403 — доступ требует авторизации или запрещён. Проверьте, не блокируются ли важные страницы.
  • 404 и 410 — ресурс не найден или удалён. Сверьте результат с назначением URL.
  • 429 — слишком много запросов. Уменьшите частоту сканирования и проверьте ограничения сервера.
  • 5xx — ошибка сервера или приложения. Изучите журналы и состояние инфраструктуры.
  • 000 / ошибка curl — запрос не получил корректного HTTP-ответа. Возможны проблемы DNS, TLS, сети или тайм-аут.

 

Не каждый редирект или 404 является SEO-ошибкой. Оценивать результат нужно с учётом того, должен ли URL существовать и какую задачу он выполняет.

 

4. Анализ журналов Nginx и Apache

4.1. Где искать access.log

Типичные пути в Ubuntu и Debian:

  • Nginx: /var/log/nginx/access.log;
  • Apache: /var/log/apache2/access.log.

 

На виртуальном хостинге, в ISPmanager или за прокси журнал может находиться в другом каталоге. Кроме того, формат записи настраивается администратором, поэтому перед использованием awk проверьте конфигурацию логирования.

4.2. Просмотр последних запросов

sudo tail -n 50 /var/log/nginx/access.log

 

Для поиска ошибок сервера:

sudo grep -E '" [45][0-9][0-9] ' /var/log/nginx/access.log | tail -n 50

 

Если включена ротация, старые файлы могут иметь суффиксы .1 или .gz. Для чтения обычных и сжатых журналов можно использовать:

sudo zcat -f /var/log/nginx/access.log* | tail -n 100

4.3. Подсчёт HTTP-кодов

sudo awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -nr

 

Команда подходит для стандартного формата Combined, в котором код ответа находится в девятом поле. Если формат журнала изменён, номер поля может быть другим. Не используйте этот пример без проверки структуры строки.

4.4. Поиск часто запрашиваемых URL

sudo awk '{print $7}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -nr | head -20

Это помогает увидеть наиболее часто запрашиваемые пути. При анализе учитывайте параметры URL, запросы к статическим файлам, ботов и трафик CDN.

4.5. Анализ запросов поисковых роботов

sudo grep -i 'Googlebot' /var/log/nginx/access.log | tail -n 50

 

Для подсчёта наиболее часто запрашиваемых путей:

sudo grep -i 'Googlebot' /var/log/nginx/access.log \
  | awk '{print $7}' | sort | uniq -c | sort -nr | head -20

 

Для поиска ошибок в запросах с таким User-Agent:

sudo grep -i 'Googlebot' /var/log/nginx/access.log \
  | awk '$9 ~ /^(404|410|5[0-9][0-9])$/ {print $9, $7}' \
  | sort | uniq -c | sort -nr | head -30

 

Фильтрация по User-Agent удобна для предварительного анализа, но не подтверждает подлинность робота: строку можно подделать. Для серьёзных выводов проверяйте IP-адреса по официальной процедуре поисковой системы.

4.6. Проверка подлинности Googlebot

Для проверки конкретного IP используется обратный DNS-запрос, а затем прямой запрос полученного имени. Имя должно принадлежать официальному домену, указанному Google для соответствующего типа робота, а прямое разрешение должно вернуть исходный IP. Сверяйтесь с актуальной документацией Google: набор доменов и процедуры проверки могут различаться для разных типов роботов.

Не публикуйте необработанные журналы без необходимости. В них могут содержаться IP-адреса, URL с идентификаторами, параметры запросов и другие сведения о посетителях. Ограничивайте доступ, срок хранения и состав передаваемых данных.

5. Время ответа, кэширование и сжатие

5.1. Измерение этапов HTTP-запроса

curl -sS -o /dev/null \
  -w 'DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nFirst byte: %{time_starttransfer}s\nTotal: %{time_total}s\n' \
  https://example.com/

 

Показатели помогают понять, на каком этапе возникает задержка: разрешение DNS, подключение, TLS или ожидание первого байта. На результат влияют расположение проверяющего компьютера, сеть, CDN, состояние кэша и текущая нагрузка.

Проведите несколько измерений, а не делайте вывод по одному запросу:

for i in 1 2 3 4 5; do
  curl -sS -o /dev/null -w '%{time_starttransfer}\n' https://example.com/
done

5.2. Проверка заголовков кэширования

curl -sSI https://example.com/ \
  | grep -iE '^(cache-control|expires|age|etag|last-modified):'

 

Политика кэширования зависит от типа ресурса. HTML-документ, изображение, CSS и JavaScript могут иметь разные сроки хранения. Длительное кэширование удобно для версионированных статических файлов, но опасно для ресурсов, содержимое которых меняется без изменения URL.

5.3. Проверка сжатия

curl -sS -D - -o /dev/null \
  -H 'Accept-Encoding: gzip, br' https://example.com/ \
  | grep -iE '^(content-encoding|vary):'

Если сервер поддерживает сжатие для данного типа содержимого, в ответе может присутствовать Content-Encoding: gzip или Content-Encoding: br. Отсутствие этого заголовка не всегда означает ошибку: изображения и другие уже сжатые форматы обычно не нуждаются в дополнительном сжатии.

5.4. Core Web Vitals и ограничения curl

Командная строка не измеряет пользовательские показатели Core Web Vitals. Для оценки взаимодействия с сайтом применяются:

  • LCP — время отображения крупнейшего элемента контента;
  • INP — задержка взаимодействия пользователя со страницей;
  • CLS — визуальная нестабильность макета.

 

Ориентиры Google для хорошего пользовательского опыта: LCP не более 2,5 секунды, INP не более 200 миллисекунд, CLS не более 0,1. Эти значения оцениваются по 75-му процентилю полевых данных. Один запрос curl не позволяет подтвердить соответствие сайта этим порогам.

Для лабораторных измерений используйте Lighthouse и PageSpeed Insights, а для реальной картины — полевые данные Chrome UX Report и доступные отчёты поисковых систем.

 

6. Автоматизация технического SEO-аудита

6.1. Почему для HTML лучше использовать Python

Bash отлично подходит для последовательного запуска команд и обработки файлов, но регулярные выражения неудобны для разбора HTML. Порядок атрибутов, кавычки, переносы строк и вложенные элементы могут привести к пропущенным данным.

Ниже используется Python 3 и встроенный HTML-парсер. Скрипт не требует BeautifulSoup, Requests или других сторонних пакетов: HTTP-запросы выполняются через стандартный модуль urllib, а HTML разбирается с помощью html.parser.

6.2. Создание файла seo-audit.py

Создайте файл seo-audit.py и вставьте в него следующий код. Комментарии в скрипте поясняют назначение основных операций.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Базовый технический SEO-аудит списка URL.
Использует только стандартную библиотеку Python 3.

Запуск:
  python3 seo-audit.py urls.txt seo-report.csv

Дополнительные параметры:
  DELAY=1       пауза между запросами в секундах
  TIMEOUT=25    тайм-аут одного запроса
  MAX_BYTES=5000000  максимальный объём загружаемого HTML
"""

import csv
import os
import sys
import time
import urllib.request
import urllib.error
from html.parser import HTMLParser
from urllib.parse import urlparse


# ---------- Настройки ----------

DELAY = float(os.getenv("DELAY", "1"))
TIMEOUT = float(os.getenv("TIMEOUT", "25"))
MAX_BYTES = int(os.getenv("MAX_BYTES", "5000000"))

USER_AGENT = "TechSEOAudit/2.0 (+https://example.com/seo-audit-info)"


# ---------- Разбор HTML ----------

class PageParser(HTMLParser):
    """Извлекает основные SEO-элементы из исходного HTML."""

    def __init__(self):
        super().__init__(convert_charrefs=True)
        self.title = ""
        self.in_title = False
        self.description = ""
        self.robots = ""
        self.canonical = ""
        self.h1 = []
        self.in_h1 = False
        self.current_h1 = []

    def handle_starttag(self, tag, attrs):
        attrs = dict(attrs)
        tag = tag.lower()

        if tag == "title":
            self.in_title = True

        elif tag == "h1":
            self.in_h1 = True
            self.current_h1 = []

        elif tag == "meta*":
            name = attrs.get("name", "").strip().lower()
            content = attrs.get("content", "").strip()

            if name == "description":
                self.description = content
            elif name == "robots":
                self.robots = content

        elif tag == "link":
            rel = attrs.get("rel", "").lower().split()
            if "canonical" in rel:
                self.canonical = attrs.get("href", "").strip()

    def handle_endtag(self, tag):
        tag = tag.lower()

        if tag == "title":
            self.in_title = False

        elif tag == "h1" and self.in_h1:
            value = " ".join("".join(self.current_h1).split())
            if value:
                self.h1.append(value)
            self.in_h1 = False
            self.current_h1 = []

    def handle_data(self, data):
        if self.in_title:
            self.title += data

        if self.in_h1:
            self.current_h1.append(data)


# ---------- Проверка одного URL ----------

def audit_url(url):
    """Запрашивает страницу и возвращает словарь с результатами."""

    result = {
        "input_url": url,
        "http_status": "",
        "redirects": "",
        "final_url": "",
        "seconds": "",
        "content_type": "",
        "html_bytes": "",
        "title": "",
        "title_length": "",
        "description": "",
        "description_length": "",
        "h1_count": "",
        "h1": "",
        "canonical": "",
        "meta_robots": "",
        "x_robots_tag": "",
        "issues": "",
        "error": "",
    }

    request = urllib.request.Request(
        url,
        headers={"User-Agent": USER_AGENT},
        method="GET"
    )

    started = time.monotonic()

    try:
        with urllib.request.urlopen(request, timeout=TIMEOUT) as response:
            status = response.status
            final_url = response.geturl()
            headers = response.headers

            # Читаем ограниченный объём данных, чтобы не загружать
            # огромные документы и случайные бинарные файлы целиком.
            body = response.read(MAX_BYTES + 1)

            if len(body) > MAX_BYTES:
                body = body[:MAX_BYTES]
                result["issues"] = "HTML_TRUNCATED"

            result["http_status"] = status
            result["final_url"] = final_url
            result["content_type"] = headers.get("Content-Type", "")
            result["x_robots_tag"] = headers.get("X*-Robots-Tag", "")
            result["html_bytes"] = len(body)

            # urllib автоматически следует за обычными редиректами,
            # но не предоставляет здесь надёжное число переходов.
            # Поэтому число редиректов намеренно не подменяется догадкой.
            result["redirects"] = "FOLLOWED"

            content_type = result["content_type"].lower()

            if "text/html" in content_type or not content_type:
                charset = headers.get_content_charset() or "utf-8"
                html = body.decode(charset, errors="replace")

                parser = PageParser()
                parser.feed(html)

                title = " ".join(parser.title.split())
                description = parser.description
                h1_values = parser.h1

                result["title"] = title
                result["title_length"] = len(title)
                result["description"] = description
                result["description_length"] = len(description)
                result["h1_count"] = len(h1_values)
                result["h1"] = " | ".join(h1_values)
                result["canonical"] = parser.canonical
                result["meta_robots"] = parser.robots

                issues = []
                if not title:
                    issues.append("TITLE_MISSING")
                if not description:
                    issues.append("DESCRIPTION_MISSING")
                if not h1_values:
                    issues.append("H1_MISSING")
                elif len(h1_values) > 1:
                    issues.append("MULTIPLE_H1")
                if "noindex" in parser.robots.lower():
                    issues.append("META_NOINDEX")
                if "noindex" in result["x_robots_tag"].lower():
                    issues.append("HEADER_NOINDEX")
                if not parser.canonical:
                    issues.append("CANONICAL_MISSING")

                if result["issues"]:
                    issues.insert(0, result["issues"])
                result["issues"] = ";".join(issues)

    except urllib.error.HTTPError as error:
        result["http_status"] = error.code
        result["final_url"] = error.geturl()
        result["x_robots_tag"] = error.headers.get("X*-Robots-Tag", "")
        result["error"] = str(error)

    except Exception as error:
        result["error"] = f"{type(error).__name__}: {error}"

    result["seconds"] = round(time.monotonic() - started, 3)
    return result


# ---------- Запуск аудита и экспорт CSV ----------

def main():
    input_file = sys.argv[1] if len(sys.argv) > 1 else "urls.txt"
    output_file = sys.argv[2] if len(sys.argv) > 2 else "seo-report.csv"

    if not os.path.isfile(input_file):
        print(f"Не найден входной файл: {input_file}", file=sys.stderr)
        return 1

    # Убираем пустые строки и комментарии, сохраняя порядок URL.
    with open(input_file, "r", encoding="utf-8-sig") as source:
        urls = [
            line.strip()
            for line in source
            if line.strip() and not line.lstrip().startswith("#")
        ]

    fields = [
        "input_url", "http_status", "redirects", "final_url", "seconds",
        "content_type", "html_bytes", "title", "title_length",
        "description", "description_length", "h1_count", "h1",
        "canonical", "meta_robots", "x_robots_tag", "issues", "error"
    ]

    checked = 0
    errors = 0

    # utf-8-sig добавляет BOM, чтобы Excel корректно распознавал UTF-8.
    with open(output_file, "w", newline="", encoding="utf-8-sig") as report:
        writer = csv.DictWriter(report, fieldnames=fields)
        writer.writeheader()

        for url in urls:
            parsed = urlparse(url)

            if parsed.scheme not in ("http", "https") or not parsed.netloc:
                row = {field: "" for field in fields}
                row["input_url"] = url
                row["error"] = "Некорректный URL: требуется http:// или https://"
            else:
                print(f"[{checked + 1}/{len(urls)}] Проверка: {url}")
                row = audit_url(url)

            writer.writerow(row)
            checked += 1

            if row.get("error"):
                errors += 1

            time.sleep(max(0, DELAY))

    print()
    print("Аудит завершён.")
    print(f"Проверено URL: {checked}")
    print(f"Ошибок запросов или URL: {errors}")
    print(f"Отчёт: {output_file}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

6.3. Запуск скрипта

Создайте список URL в файле urls.txt, затем выполните:

python3 seo-audit.py urls.txt seo-report.csv

 

Чтобы уменьшить частоту запросов и увеличить тайм-аут:

DELAY=2 TIMEOUT=40 python3 seo-audit.py urls.txt seo-report.csv

 

Скрипт создаёт CSV в UTF-8 с BOM, поэтому русские значения должны корректно открываться в современных версиях Excel. В отчёт попадают исходный и конечный URL, статус, тип содержимого, размер полученного HTML, Title, Description, H1, Canonical, директивы robots и обнаруженные предупреждения.

6.4. Важные ограничения автоматического скрипта

Это облегчённый проверяющий инструмент, а не полноценный краулер. В частности:

  • Он проверяет только URL, заранее записанные в файл, и не обходит сайт по внутренним ссылкам.
  • Он не исполняет JavaScript и анализирует исходный HTML.
  • Для экономии ресурсов размер загружаемого тела ограничен переменной MAX_BYTES.
  • В этой версии число редиректов обозначается как FOLLOWED, а не вычисляется приблизительно. Для точного подсчёта цепочки нужен отдельный обработчик переходов.
  • Дубли Title и Description можно определить после выгрузки отчёта, сгруппировав строки по соответствующим столбцам.
  • Наличие canonical и robots оценивается по HTML и заголовкам, но окончательное решение об индексации принимает поисковая система.

 

Не запускайте массовый аудит без ограничений частоты. Для крупного сайта увеличьте задержку, проверяйте сайт частями и не обходите страницы, на которые у вас нет разрешения.

7. Интерпретация результатов и исправление ошибок

7.1. Как читать отчёт

В первую очередь отфильтруйте строки с ошибками запроса и неожиданными HTTP-статусами. Затем проверьте страницы с отсутствующими Title, Description, H1 или Canonical. Отдельно изучите директивы noindex и несоответствие canonical конечному URL.

Наличие предупреждения не всегда означает ошибку. Например, отсутствие meta* description не блокирует индексацию, а несколько H1 сами по себе не являются автоматическим основанием для санкций. Эти сигналы помогают найти страницы, которые требуют ручной оценки.

7.2. Типовые симптомы и направления проверки

СимптомЧто проверитьВозможное действие
Лишняя цепочка редиректовПравила Nginx, Apache, CDN и CMSСвести переход к конечному URL и обновить внутренние ссылки
Важная страница возвращает 404URL, маршрутизацию, удаление или переименование страницыВосстановить страницу либо настроить релевантный редирект
Soft 404Шаблон страницы ошибки и HTTP-статусВозвращать настоящий 404 или 410 для отсутствующего ресурса
Неожиданный noindexНастройки CMS, SEO-плагин, шаблон, X*-Robots-TagУдалять директиву только если страница должна индексироваться
Canonical ведёт на другой URLНастройки канонизации и генерацию ссылокИсправить источник canonical и согласовать его с sitemap
Ошибки 5xxЛоги приложения, PHP, Nginx или Apache, ресурсы сервераУстранить причину сбоя, затем повторить запрос
Долгое время первого байтаБэкенд, кэш, базу данных, CDN и сетевой маршрутПрофилировать приложение и устранять узкое место
Нет сжатияContent-Encoding и настройки сервераВключить gzip или Brotli для подходящих типов файлов

7.3. Пример проверки конфигурации Nginx

Перед изменением конфигурации сделайте резервную копию. После редактирования сначала проверьте синтаксис:

sudo nginx -t

 

Если проверка завершилась успешно, конфигурацию можно перечитать без полного перезапуска сервера:

sudo systemctl reload nginx

 

Не копируйте универсальные правила редиректов в рабочий конфиг без проверки существующих server-блоков. На сайте за CDN или обратным прокси схема определения HTTPS может отличаться. Дублирующиеся правила способны вызвать циклические перенаправления или сделать сайт недоступным.

7.4. Пример проверки Apache

Для Apache проверка синтаксиса выполняется командой:

sudo apache2ctl configtest

 

Правила в .htaccess зависят от включённых модулей, разрешений AllowOverride и конфигурации виртуального хоста. Если сайт работает за прокси, учитывайте передачу исходной схемы запроса. После изменения повторно проверьте все варианты HTTP/HTTPS и www/без www.

7.5. Проверка robots.txt и noindex

Если важная страница закрыта от индексации, сначала определите источник директивы. Она может находиться в robots.txt, HTML-шаблоне, настройках CMS, SEO-плагине или HTTP-заголовке. Не удаляйте запрет автоматически: для служебных, личных и технических страниц он может быть намеренным.

Помните о различии между сканированием и индексацией. Если URL закрыт в robots.txt, поисковый робот может не получить страницу и не увидеть размещённый внутри неё meta* noindex. Поэтому директивы должны быть согласованы между собой.

7.6. Цикл повторной проверки

Рабочий процесс должен быть одинаковым до и после исправления:

  1. Сохраните исходный отчёт и зафиксируйте обнаруженную проблему.
  2. Определите её источник, не ограничиваясь внешним симптомом.
  3. Внесите одно контролируемое изменение.
  4. Повторите ту же команду или аудит для тех же URL.
  5. Сравните результаты и проверьте, не появились ли побочные ошибки.
  6. При необходимости используйте инструменты веб-мастера для проверки обработки страницы поисковой системой.

 

Изменение HTTP-ответа можно увидеть сразу, но повторное сканирование и обновление поискового индекса требуют времени. Успешный тест curl не означает, что поисковая система уже обновила данные о странице.

 

Итоговый план технического SEO-аудита

Для регулярной проверки сайта можно использовать следующий порядок:

  1. Собрать список важных URL из sitemap и структуры сайта.
  2. Проверить DNS, HTTPS и доступность основных адресов.
  3. Проверить robots.txt, sitemap.xml, canonical, Meta* Robots и X*-Robots-Tag.
  4. Найти неожиданные статусы, лишние редиректы и soft 404.
  5. Проанализировать серверные журналы и ошибки поисковых роботов.
  6. Измерить сетевое время ответа, проверить кэширование и сжатие.
  7. Сформировать отчёт, отделив подтверждённые технические ошибки от предупреждений.
  8. Исправить приоритетные проблемы и повторить проверки.

 

Заключение

Linux предоставляет достаточно инструментов для первичной диагностики технического состояния сайта. С помощью curl можно проверить HTTP-ответы, редиректы и заголовки, DNS-утилит — разрешение домена, системных команд — журналы сервера, а Python — автоматизировать проверку списка URL и собрать результаты в удобный CSV-отчёт.

Главное преимущество такого подхода — прозрачность. Каждая проверка выполняется конкретной командой, результат можно сохранить, повторить и сравнить после внесения изменений. Это особенно полезно при поиске причин ошибок после обновления CMS, переноса сайта, изменения серверной конфигурации или настройки редиректов.

При этом командный аудит следует считать частью технического SEO, а не полной заменой краулера, браузерного тестирования и инструментов поисковых систем. Надёжный вывод появляется тогда, когда данные HTTP, HTML, серверных логов и поисковой аналитики рассматриваются вместе.

 

Часто задаваемые вопросы

Можно выполнить ряд важных технических проверок: HTTP-статусы, заголовки, редиректы, время ответа и получение HTML. Но curl не исполняет JavaScript, не обходит автоматически все внутренние ссылки и не измеряет пользовательские Core Web Vitals.

Нет. Пример использует стандартные модули Python 3, поэтому достаточно установленного интерпретатора. Это упрощает запуск на обычном Linux-сервере.

Начните с доступности важных страниц, неожиданных 4xx и 5xx, цепочек редиректов, случайных noindex и корректности canonical. Затем переходите к метаданным, логам и производительности.

Код 200 подтверждает успешный HTTP-ответ, но не гарантирует индексацию. Причиной могут быть noindex, canonical на другой URL, дублирование, низкая ценность страницы, проблемы рендеринга или решение самой поисковой системы.

Частота зависит от размера и активности сайта. Небольшой ресурс можно проверять после крупных обновлений и регулярно по расписанию. Для часто обновляемого проекта полезен автоматический мониторинг ключевых URL и серверных ошибок.

Да, если проверять только разрешённые URL, ограничить частоту запросов, установить тайм-ауты и не создавать чрезмерную нагрузку. Для крупных сайтов начинайте с небольшого списка и постепенно увеличивайте объём проверки.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Редактор: AndreyEx

Если статья понравилась, поделитесь ей в социальных сетях

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Это может быть вам интересно


Спасибо!

Теперь редакторы в курсе.

Прокрутить страницу до начала