Скрипт массовой проверки и накопления истории ahrefs DR
Дата публикации: 08.11.2026

Скрипт массовой проверки и накопления истории ahrefs DR

Хочу себе такие же кнопки

Вот и все.. конец мучениям, чекаем массово DR . Ключ берем в панельке ahrefs. ВСе бесплатно и без всяких левых сервисов.
.txt
 * Каждая запись — одна строка формата: d.m.Y\tDR\n
 * Запись выполняется ТОЛЬКО если:
 *   - файла ещё нет (первая проверка), ИЛИ
 *   - DR изменился по сравнению с последней записью в файле.
 *
 * Требования:
 *   - PHP 7.4+ с расширением cURL
 *   - Бесплатный API-ключ Ahrefs
 */

// ================= НАСТРОЙКИ =================
$ahrefsApiKey = 'ВАШ_API_КЛЮЧ_AHREFS';       // Замените на ваш ключ
$domainsFile  = __DIR__ . '/domains.txt';    // Путь к файлу со списком доменов
$dataDir      = __DIR__ . '/data';           // Папка для результатов
$drVersion    = 'dr3';                       // dr3 или dr4
$pauseMs      = 300;                         // Пауза между запросами (мс)
// =============================================

/**
 * Читает домены из файла, очищает и валидирует их.
 *
 * @return array{valid: string[], invalid: array}
 */
function readDomainsFromFile(string $filePath): array
{
    if (!is_readable($filePath)) {
        fwrite(STDERR, "Не удалось прочитать файл: {$filePath}\n");
        exit(1);
    }

    $valid   = [];
    $invalid = [];
    $seen    = [];
    $lineNo  = 0;

    $handle = fopen($filePath, 'r');
    if ($handle === false) {
        fwrite(STDERR, "Не удалось открыть файл: {$filePath}\n");
        exit(1);
    }

    while (($line = fgets($handle)) !== false) {
        $lineNo++;
        $line = trim($line);

        if ($line === '' || $line[0] === '#') {
            continue;
        }

        $domain = preg_replace('#^https?://#i', '', $line);
        $domain = rtrim($domain, '/');
        $domain = strtolower($domain);

        if (!preg_match('/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/', $domain)) {
            $invalid[] = ['line' => $lineNo, 'value' => $line];
            continue;
        }

        if (isset($seen[$domain])) {
            continue;
        }
        $seen[$domain] = true;

        $valid[] = $domain;
    }

    fclose($handle);

    return ['valid' => $valid, 'invalid' => $invalid];
}

/**
 * Получает DR для одного домена.
 *
 * @return array{success: bool, domain_rating: float|null, error: string|null}
 */
function getDomainRating(string $domain, string $apiKey, string $version = 'dr3'): array
{
    $url = 'https://api.ahrefs.com/v3/public/domain-rating-free?' . http_build_query([
        'target'  => $domain,
        'version' => $version,
        'output'  => 'json',
    ]);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . $apiKey,
            'Accept: application/json',
        ],
        CURLOPT_TIMEOUT        => 15,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);

    $response  = curl_exec($ch);
    $httpCode  = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlError = curl_error($ch);
    curl_close($ch);

    if ($curlError) {
        return ['success' => false, 'domain_rating' => null, 'error' => 'cURL: ' . $curlError];
    }

    $data = json_decode($response, true);

    if ($httpCode === 429) {
        return ['success' => false, 'domain_rating' => null, 'error' => 'HTTP 429 (превышен лимит запросов)'];
    }
    if ($httpCode !== 200) {
        $errorMsg = $data['error'] ?? "HTTP $httpCode";
        return ['success' => false, 'domain_rating' => null, 'error' => $errorMsg];
    }

    if (!isset($data['domain_rating']['domain_rating'])) {
        return ['success' => false, 'domain_rating' => null, 'error' => 'Некорректный ответ API'];
    }

    return [
        'success'       => true,
        'domain_rating' => (float) $data['domain_rating']['domain_rating'],
        'error'         => null,
    ];
}

/**
 * Возвращает последнее сохранённое значение DR из файла домена,
 * либо null, если файла нет или в нём нет валидной записи.
 */
function getLastStoredDr(string $filePath): ?float
{
    if (!is_file($filePath) || !is_readable($filePath)) {
        return null;
    }

    $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    if ($lines === false || empty($lines)) {
        return null;
    }

    $last = trim(end($lines));
    // Ожидаемый формат: d.m.YDR
    if (!preg_match('/^\d{2}\.\d{2}\.\d{4}\t([0-9]+(?:\.[0-9]+)?)$/', $last, $m)) {
        return null;
    }

    return (float) $m[1];
}

/**
 * Форматирует DR для записи в файл: один знак после точки, без хвостовых нулей.
 */
function formatDr(float $dr): string
{
    return rtrim(rtrim(number_format($dr, 1, '.', ''), '0'), '.');
}

/**
 * Дописывает строку результата в файл домена, только если DR изменился
 * или файла ещё нет.
 *
 * @return string 'created' | 'updated' | 'unchanged'
 */
function saveIfChanged(string $dataDir, string $domain, float $dr): string
{
    $filePath = $dataDir . DIRECTORY_SEPARATOR . $domain . '.txt';
    $lastDr   = getLastStoredDr($filePath);

    if ($lastDr !== null && abs($lastDr - $dr) < 1e-9) {
        return 'unchanged';
    }

    $line = date('d.m.Y') . "\t" . formatDr($dr) . "\n";

    if (file_put_contents($filePath, $line, FILE_APPEND | LOCK_EX) === false) {
        throw new RuntimeException("Не удалось записать в файл: {$filePath}");
    }

    return $lastDr === null ? 'created' : 'updated';
}

// ================= ВЫПОЛНЕНИЕ =================

if (!is_dir($dataDir)) {
    if (!mkdir($dataDir, 0755, true) && !is_dir($dataDir)) {
        fwrite(STDERR, "Не удалось создать папку: {$dataDir}\n");
        exit(1);
    }
}

$parsed = readDomainsFromFile($domainsFile);

if (!empty($parsed['invalid'])) {
    echo "Пропущены некорректные строки:\n";
    foreach ($parsed['invalid'] as $bad) {
        echo "  строка {$bad['line']}: \"{$bad['value']}\"\n";
    }
    echo "\n";
}

$domains = $parsed['valid'];
$total   = count($domains);

if ($total === 0) {
    echo "В файле {$domainsFile} не найдено валидных доменов.\n";
    exit(0);
}

echo "Проверка Domain Rating (версия: {$drVersion})\n";
echo "Доменов к проверке: {$total}\n";
echo "Папка результатов: {$dataDir}\n";
echo str_repeat('-', 80) . "\n";
printf("%-38s | %-8s | %s\n", 'Домен', 'DR', 'Действие');
echo str_repeat('-', 80) . "\n";

$stats = ['created' => 0, 'updated' => 0, 'unchanged' => 0, 'error' => 0];

foreach ($domains as $i => $domain) {
    $result = getDomainRating($domain, $ahrefsApiKey, $drVersion);

    if (!$result['success']) {
        $stats['error']++;
        printf("%-38s | %-8s | Ошибка: %s\n", $domain, '-', $result['error']);
    } else {
        try {
            $action = saveIfChanged($dataDir, $domain, $result['domain_rating']);
            $stats[$action]++;
            $labels = [
                'created'   => 'записано (первая проверка)',
                'updated'   => 'записано (DR изменился)',
                'unchanged' => 'пропущено (DR не изменился)',
            ];
            printf("%-38s | %-8s | %s\n", $domain, formatDr($result['domain_rating']), $labels[$action]);
        } catch (RuntimeException $e) {
            $stats['error']++;
            printf("%-38s | %-8s | Ошибка записи: %s\n", $domain, formatDr($result['domain_rating']), $e->getMessage());
        }
    }

    if ($i < $total - 1 && $pauseMs > 0) {
        usleep($pauseMs * 1000);
    }
}

echo str_repeat('-', 80) . "\n";
echo "Итоги:\n";
echo "  новых файлов создано:       {$stats['created']}\n";
echo "  обновлено (DR изменился):   {$stats['updated']}\n";
echo "  без изменений (пропущено):  {$stats['unchanged']}\n";
echo "  ошибок:                     {$stats['error']}\n";