چه خطاهایی را پیش از انتشار می‌توان پیدا کرد؟

سئوی فنی از چند برچسب جدا تشکیل نشده است؛ نشانی صفحه، محتوای قابل خواندن، نسخهٔ زبانی و وضعیت قابل نمایه‌شدن باید با هم سازگار باشند. یک صفحهٔ خوب ممکن است با برچسب اشتباه از نمایه بیرون بماند یا نشانی مرجع نامناسبی معرفی کند. در این مقاله یک ممیزی کوچک محلی می‌سازیم تا چند خطای پرهزینه پیش از انتشار دیده شوند، بدون اینکه ابزار وعدهٔ رتبه یا بازدید تضمینی بدهد.

نشانی مرجع راهنمایی برای انتخاب نسخهٔ اصلی محتوای مشابه است، نه فرمان قطعی برای موتور جست‌وجو. در سایت دو زبانه، هر صفحه باید نشانی مرجع مناسب خودش و پیوند همتای زبانی درست داشته باشد. همهٔ صفحه‌های انگلیسی را به نسخهٔ فارسی ارجاع مرجع ندهید. محتوای ترجمه‌شده، مسیر پایدار و پیوندهای متقابل باید از دید کاربر و ربات جست‌وجو یک داستان منسجم بیان کنند.

نشانی مرجع و نسخه‌های زبانی هماهنگ

ابزار نمونه وجود یک عنوان اصلی، یک توضیح غیرخالی، نشانی مرجع مطلق و سه پیوند زبانی را بررسی می‌کند. همچنین برچسب جلوگیری از نمایه‌شدن را گزارش می‌دهد تا اشتباه محیط آزمایش به انتشار اصلی منتقل نشود. این قرارداد برای مثال آموزشی حاضر است؛ تعداد عنوان‌های صفحه به‌تنهایی عامل رتبه‌بندی قطعی نیست و هر یافته باید در زمینهٔ ساختار و هدف همان صفحه تفسیر شود.

چهار فایل را کنار هم ذخیره و python -m unittest test_audit.py را اجرا کنید. سپس python run_audit.py demo.html را فراخوانی کنید؛ خروجی نمونه باید فهرست خطای خالی داشته باشد. نشانی‌های example.test فقط دادهٔ آزمایشی‌اند و صفحه‌ای واقعی در آن دامنه منتشر نشده است. برای پروژهٔ خودتان، فایل‌های خروجی واقعی را به ابزار بدهید و نشانی‌های مرجع را با دامنه و مسیر نهایی تطبیق دهید.

این ممیزی هیچ صفحه‌ای را از اینترنت نمی‌گیرد، بنابراین نمی‌تواند پاسخ سرور، زنجیرهٔ تغییرمسیر، کد وضعیت یا وجود همتای زبانی در سرور را ثابت کند. دسترسی خزنده و وضعیت نقشهٔ سایت نیز به بررسی جداگانه نیاز دارد. بعد از انتشارِ مجاز، نشانی نهایی و ابزارهای معتبر مدیریت جست‌وجو را بررسی کنید. نبود خطا در فایل محلی با نمایه‌شدن قطعی یا انتخاب همان نشانی مرجع توسط موتور جست‌وجو برابر نیست.

فایل‌های کامل و قابل اجرا

چهار فایل زیر کامل‌اند؛ پیش‌نیاز، ترتیب اجرا و روش بررسی در متن آمده است. کدها خلاصه یا پنهان نشده‌اند.

1. فایل کامل audit_seo.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

from html.parser import HTMLParser
from urllib.parse import urlparse


class Audit(HTMLParser):
    def __init__(self):
        super().__init__()
        self.h1 = 0; self.canonicals = []; self.descriptions = []; self.languages = set(); self.robots = []

    def handle_starttag(self, tag, attrs):
        attrs = dict(attrs)
        if tag == 'h1': self.h1 += 1
        if tag == 'link' and attrs.get('rel') == 'canonical': self.canonicals.append(attrs.get('href', ''))
        if tag == 'link' and attrs.get('rel') == 'alternate' and attrs.get('href'): self.languages.add(attrs.get('hreflang'))
        if tag == 'meta' and attrs.get('name') == 'description': self.descriptions.append(attrs.get('content', ''))
        if tag == 'meta' and attrs.get('name') == 'robots': self.robots.append(attrs.get('content', '').lower())


def audit(source):
    parser = Audit(); parser.feed(source)
    errors = []
    if parser.h1 != 1: errors.append('expected-one-h1')
    if len(parser.descriptions) != 1 or not parser.descriptions[0].strip(): errors.append('missing-or-duplicate-description')
    if len(parser.canonicals) != 1: errors.append('expected-one-canonical')
    elif urlparse(parser.canonicals[0]).scheme != 'https' or not urlparse(parser.canonicals[0]).netloc: errors.append('canonical-must-be-absolute-https')
    if not {'fa', 'en', 'x-default'} <= parser.languages: errors.append('missing-language-alternates')
    if any('noindex' in value for value in parser.robots): errors.append('page-is-noindex')
    return errors

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

2. فایل کامل demo.html

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

<!doctype html>
<html lang="fa" dir="rtl"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>نمونهٔ ممیزی سئوی فنی</title>
<meta name="description" content="صفحهٔ آزمایشی برای بررسی سئوی فنی؛ نشانی‌ها فقط مثال هستند.">
<meta name="robots" content="index,follow">
<link rel="canonical" href="https://example.test/article.html">
<link rel="alternate" hreflang="fa" href="https://example.test/article.html">
<link rel="alternate" hreflang="en" href="https://example.test/article-en.html">
<link rel="alternate" hreflang="x-default" href="https://example.test/article.html">
</head><body><main><h1>یک عنوان اصلی روشن</h1><p>این صفحه روی دامنهٔ نمونه منتشر نشده است.</p></main></body></html>

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

3. فایل کامل run_audit.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

import json
from pathlib import Path
import sys
from audit_seo import audit

if __name__ == '__main__':
    paths = [Path(name) for name in sys.argv[1:]] or [Path(__file__).with_name('demo.html')]
    failed = False
    for path in paths:
        errors = audit(path.read_text(encoding='utf-8'))
        print(json.dumps({'file': str(path), 'errors': errors}))
        failed |= bool(errors)
    raise SystemExit(1 if failed else 0)

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

4. فایل کامل test_audit.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

from pathlib import Path
import unittest
from audit_seo import audit


class AuditTests(unittest.TestCase):
    def setUp(self):
        self.page = Path(__file__).with_name('demo.html').read_text(encoding='utf-8')

    def test_valid_and_duplicate_titles(self):
        self.assertEqual(audit(self.page), [])
        self.assertIn('expected-one-h1', audit(self.page.replace('</main>', '<h1>Duplicate</h1></main>')))

    def test_accidental_noindex_and_relative_canonical(self):
        self.assertIn('page-is-noindex', audit(self.page.replace('index,follow', 'noindex,follow')))
        broken = self.page.replace('rel="canonical" href="https://example.test/article.html"', 'rel="canonical" href="article.html"')
        self.assertIn('canonical-must-be-absolute-https', audit(broken))


if __name__ == '__main__': unittest.main()

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

اجرای ممیزی و مرز نتیجهٔ خودکار

برای نتیجهٔ ماندگار، عنوان و توضیح را دقیق و متناسب با نیاز خواننده بنویسید و پیوند داخلی به مطالب مرتبط بدهید. پاراگراف‌های تکراری، تیتر اغراق‌آمیز و پنهان‌کردن محتوای اصلی پشت بارگذاری سنگین، مشکل را با افزودن کلمهٔ کلیدی حل نمی‌کنند. گزارش ممیزی را بخشی از کنترل انتشار قرار دهید و تغییرات را با شاخص‌های واقعی مشاهده‌شده بسنجید؛ هدف، صفحهٔ قابل فهم و قابل دسترسی با نشانی‌های درست است.

چک‌لیست اجرای عملی

  • نشانی مرجع هر زبان به صفحهٔ درست اشاره کند.
  • برچسب محیط آزمایش وارد انتشار اصلی نشود.
  • خطای محلی از وضعیت واقعی سرور تفکیک شود.
  • عنوان و توضیح برای انسان دقیق و یکتا باشند.

منابع

تدوین و راهنمای عملی: دانشنامه لیان.