Животные меняются при загрузке страницы
Анализ конкурентных ссылок через Ahrefs API и Python: пошаговый скрипт
Карточки, чек-листы, таблицы и примеры помогают быстро найти нужный ответ.
В 2026 году конкурентный анализ ссылок остаётся одним из ключевых инструментов для оценки позиций и построения стратегии. В этой статье показан практический скрипт, который автоматически собирает данные о ссылочном профиле конкурентов через Ahrefs API, сохраняет их в удобном формате и проводит первичный анализ качества.
1. Получить API‑ключ Ahrefs и подготовить окружение. 2. Сформировать список конкурентов и их доменов. 3. Запросить ссылочный профиль через API. 4. Сохранить данные в CSV/SQLite. 5. Оценить метрики DR/UR, типы ссылок и анкор. 6. Применить чек‑лист качества. 7. Сформировать рекомендации и план действий.
1. Зачем анализировать конкурентные ссылки?
Конкурентный анализ ссылок – это системный разбор внешних ссылок, которые привлекают трафик и авторитет к сайтам соперников. Он позволяет понять, какие страницы и домены приносят авторитет, какие типы ссылок (текстовые, медиа, баннерные) работают лучше, а также выявить потенциальные источники новых качественных ссылок.
Когда нужен такой анализ? В любой момент, когда необходимо:
- Запустить новый продукт или сервис и быстро войти в топ.
- Провести ребрендинг и убедиться, что старые ссылки не навредят новому домену.
- Отреагировать на санкции поисковых систем и восстановить потерянные позиции.
- Определить, какие конкуренты растут быстрее, и перенять их стратегии.
Цели и выгоды:
- Выявление «потенциальных» ссылочных ниш – страниц, которые конкуренты уже привлекают, но которые можно использовать и для себя.
- Определение слабых мест в ссылочном профиле конкурентов, которые можно эксплуатировать, например, низкая анкор‑диверсификация.
- Получение точных метрик (доменный рейтинг, Page Authority, количество ссылок) для сравнения и построения KPI.
- Снижение риска потери позиций: если конкуренты получают новые ссылки, можно быстро реагировать и не отставать.
- Оптимизация бюджета на контент‑маркетинг – фокус на тех типах ссылок, которые реально работают.
Таким образом, конкурентный анализ ссылок превращается из «просто» в инструмент, который позволяет принимать обоснованные решения о продвижении, экономить ресурсы и ускорять рост органического трафика.
2. Подготовка окружения и доступа к Ahrefs API
Получить ключ Ahrefs API: зайдите в личный кабинет, откройте раздел API, скопируйте токен. Храните его в безопасном месте, например, в файле .env. Установите Python 3.10+ и pip. Установите библиотеки requests и python-dotenv. После установки создайте файл .env в корне проекта и добавьте строку API_KEY=ваш_ключ. В коде импортируйте dotenv и получите переменную через os.getenv.
- API ключ в
.env - Python 3.10+ установлен
- pip,
requests,python-dotenvустановлены - Файл
.envв корне проекта - Переменная
API_KEYдоступна в коде
- pip install requests python-dotenv
- echo "API_KEY=xxxxxxxx" > .env
- В коде:
from dotenv import load_dotenv; load_dotenv(); key = os.getenv("API_KEY")
3. Сбор списка конкурентов и их доменов
- Сформируйте список ключевых запросов, которые отражают вашу нишу.
- Вызовите Ahrefs API
keywords_to_sitesдля каждого запроса, получив список сайтов, ранжирующих по ним. - Извлеките из ответа поле
domainи добавьте его в общую коллекцию. - Убедитесь, что коллекция уникальна – удалите дубликаты.
- Сохраните результат в CSV или JSON, чтобы использовать в следующих шагах.
import requests, json
API_KEY = "YOUR_AHREFS_API_KEY"
KEYWORDS = ["example keyword 1", "example keyword 2"]
domains = set()
for kw in KEYWORDS:
url = f"https://apiv2.ahrefs.com/keywords_to_sites?token={API_KEY}&keyword={kw}&limit=100"
resp = requests.get(url)
if resp.status_code != 200:
continue
data = resp.json()
for site in data.get("results", []):
domains.add(site["domain"])
with open("competitor_domains.json", "w") as f:
json.dump(list(domains), f, indent=2)
4. Запрос ссылочного профиля через Ahrefs API
Для получения ссылочного профиля Ahrefs API нужно сформировать запрос к https://apiv2.ahrefs.com с параметрами target, from и limit. Пагинация реализуется через from – смещение, а limit – размер страницы (максимум 200). В каждом ответе указано next_from, которое можно использовать для следующего шага. Аутентификация – токен в заголовке Authorization: Bearer YOUR_TOKEN или в параметре token.
import os
import requests
import time
API_URL = "https://apiv2.ahrefs.com"
TOKEN = os.getenv("AHREFS_TOKEN") # Установите в окружении
def fetch_link_profile(domain, limit=200, pause=1.0):
"""Возвращает список всех ссылок на domain."""
results = []
params = {
"token": TOKEN,
"target": domain,
"from": 0,
"limit": limit,
"mode": "backlinks",
"output": "json"
}
while True:
resp = requests.get(API_URL, params=params)
if resp.status_code != 200:
raise RuntimeError(f"Error {resp.status_code}: {resp.text}")
data = resp.json()
backlinks = data.get("backlinks", [])
results.extend(backlinks)
# Пагинация
next_from = data.get("next_from")
if not next_from:
break
params["from"] = next_from
time.sleep(pause) # соблюдаем лимиты API
return results
if __name__ == "__main__":
domain = "example.com"
links = fetch_link_profile(domain)
print(f"Получено {len(links)} ссылок для {domain}")
5. Парсинг и хранение данных в CSV/SQLite
Парсинг JSON из Ahrefs API, сохранение в CSV и импорт в SQLite – это два простых шага, которые позволяют быстро превратить сырые данные в удобный формат для анализа и дальнейшего использования в отчётах.
import json
import csv
import sqlite3
import requests
from pathlib import Path
API_KEY = 'YOUR_AHREFS_API_KEY'
BASE_URL = 'https://apiv2.ahrefs.com'
DOMAIN = 'example.com'
OUTPUT_DIR = Path('output')
OUTPUT_DIR.mkdir(exist_ok=True)
def fetch_links(domain: str) -> dict:
"""Получаем JSON с внешними ссылками."""
params = {
'token': API_KEY,
'from': 'backlinks',
'target': domain,
'mode': 'domain',
'limit': 1000,
'output': 'json'
}
resp = requests.get(BASE_URL, params=params)
resp.raise_for_status()
return resp.json()
def save_to_csv(data: dict, file_path: Path) -> None:
"""Сохраняем массив ссылок в CSV."""
backlinks = data.get('backlinks', [])
if not backlinks:
return
fieldnames = backlinks[0].keys()
with file_path.open('w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(backlinks)
def init_sqlite(db_path: Path) -> sqlite3.Connection:
"""Создаём базу и таблицу, если их нет."""
conn = sqlite3.connect(db_path)
conn.execute('''
CREATE TABLE IF NOT EXISTS backlinks (
id INTEGER PRIMARY KEY,
source_url TEXT,
target_url TEXT,
anchor TEXT,
anchor_text TEXT,
anchor_type TEXT,
ref_subdomain TEXT,
ref_domain TEXT,
ref_page TEXT,
ref_text TEXT,
ref_text_length INTEGER,
ref_text_type TEXT,
ref_text_anchor TEXT,
ref_text_anchor_type TEXT,
ref_text_anchor_text TEXT,
ref_text_anchor_type_text TEXT,
ref_text_anchor_type_text_type TEXT,
ref_text_anchor_type_text_type_text TEXT,
ref_text_anchor_type_text_type_text_type TEXT,
ref_text_anchor_type_text_type_text_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type TEXT,
ref_text_anchor_type_text_type_text_type_type_text_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type_type
);
''')
conn.commit()
return conn
def insert_backlinks(conn: sqlite3.Connection, backlinks: list[dict]) -> None:
"""Вставляем данные в SQLite."""
cur = conn.cursor()
cur.executemany('''
INSERT INTO backlinks (
source_url, target_url, anchor, anchor_text, anchor_type,
ref_subdomain, ref_domain, ref_page, ref_text, ref_text_length,
ref_text_type, ref_text_anchor, ref_text_anchor_type,
ref_text_anchor_text, ref_text_anchor_type_text,
ref_text_anchor_type_text_type, ref_text_anchor_type_text_type_type
) VALUES (
:source_url, :target_url, :anchor, :anchor_text, :anchor_type,
:ref_subdomain, :ref_domain, :ref_page, :ref_text, :ref_text_length,
:ref_text_type, :ref_text_anchor, :ref_text_anchor_type,
:ref_text_anchor_text, :ref_text_anchor_type_text,
:ref_text_anchor_type_text_type, :ref_text_anchor_type_text_type_type
)
''', backlinks)
conn.commit()
def main():
data = fetch_links(DOMAIN)
csv_path = OUTPUT_DIR / f'{DOMAIN}_backlinks.csv'
save_to_csv(data, csv_path)
db_path = OUTPUT_DIR / f'{DOMAIN}_backlinks.db'
conn = init_sqlite(db_path)
insert_backlinks(conn, data.get('backlinks', []))
conn.close()
print(f'CSV и SQLite сохранены в {OUTPUT_DIR}')
if __name__ == '__main__':
main()
6. Анализ качества ссылок: метрики и типы
При оценке внешних связей важно смотреть не только на их количество, но и на качество. Ключевыми метриками являются трафик, Domain Rating (DR) и URL Rating (UR). Трафик показывает, сколько посетителей пришло по ссылке, DR отражает авторитет домена-источника, а UR указывает на силу конкретной страницы. Чем выше эти показатели, тем более ценна ссылка.
Далее стоит различать типы ссылок. Follow передают ссылочный вес и влияют на ранжирование, тогда как nofollow не передают вес, но могут приносить прямой трафик и улучшать видимость в поиске. В большинстве случаев полезно держать баланс: большинство ссылок – follow, но не забывать о nofollow для спам-источников и контекстных рекламных площадок.
Anchor‑текст – это видимый текст ссылки. Он должен быть релевантным, разнообразным и не переоптимизированным. Рекомендуется использовать комбинацию брендовых, точных, частичных и естественных фраз. Переизбыток точных ключевых слов может вызвать подозрение у поисковых систем, тогда как полностью случайный текст выглядит спамно. Анализ anchor‑текста помогает выявить чрезмерную оптимизацию и корректировать стратегию линкбилдинга.
| Параметр | Что смотреть |
|---|---|
| Трафик | Общее количество органических посетителей, приходящих с внешнего источника. |
| Domain Rating (DR) | Авторитет домена‑источника (0–100). Чем выше, тем сильнее ссылка. |
| URL Rating (UR) | Сила конкретной страницы (0–100). Влияет на вес ссылки. |
| Тип ссылки | Follow – передаёт вес; nofollow – не передаёт, но приносит трафик. |
| Anchor‑текст | Релевантность, разнообразие, отсутствие переоптимизации. |
7. Фильтрация и чек‑лист качества ссылок
Качество обратной ссылки измеряется не только её количеством, но и набором параметров, которые влияют на SEO‑ценность. Важнейшие критерии включают: релевантность контента, авторитетность домена, тип ссылки, естественность анкор‑текста, наличие индексации и отсутствие штрафов. Фильтры, основанные на Ahrefs API, позволяют быстро исключить «плохие» ссылки и сосредоточиться на тех, которые действительно повышают рейтинг.
- Domain Rating (DR) ≥ 30 – фильтр: `dr >= 30`
- URL Rating (UR) ≥ 20 – фильтр: `ur >= 20`
- Релевантность тематики – проверка ключевых слов в `title` и `meta description`
- Анкор‑текст: не более 30% «exact match», > 70% естественный или брендовый
- Тип ссылки: nofollow только в случае партнёрских площадок, иначе follow
- Наличие индексации: `status_code == 200` и `is_indexed == true`
- Отсутствие санкций: `penalty == 0` и `spam_score
- Источники: исключить «link farms» – `referring_domain_category != "Link Farm"`
- Трафик рефералов: > 100 посетителей в месяц – фильтр: `referring_domain_traffic >= 100`
- Период обновления: ссылки актуальны ≤ 12 мес – фильтр: `last_update
- Проверка на «псевдо‑рекламные» площадки: `site_type != "Ad Network"`
- Проверка на дублирование: `unique_anchor == true`
8. Выводы и рекомендации по ссылочной стратегии
| Подход к ссылочному анализу | Преимущества | Недостатки | Оптимальные рекомендации |
|---|---|---|---|
| Классический ручной аудит | Тщательный контроль контента, точная оценка качества ссылок | Высокая трудоемкость, низкая масштабируемость, задержка в принятии решений | Использовать как проверочный этап после автоматической обработки, фокусироваться на критичных доменах |
| Автоматический анализ через Ahrefs API + Python | Быстрый сбор данных, возможность обработки тысяч доменов, точная метрика DR/UR | Зависимость от API‑лимитов, риск недостоверности данных без ручной проверки, сложность настройки | Настроить лимиты, добавить фильтры по anchor‑text и трафику, периодически сверять с ручным аудиторским чек‑листом |
| Гибридный вариант (Python + ручная валидация) | Комбинация скорости и точности, гибкость в выборе критериев | Сложность реализации, необходимость поддерживать два потока данных | Разработать модульный скрипт, который выводит только «потенциально ценные» ссылки, а остальные проверяются вручную |
- Сформировать список целевых доменов: 50–100 наиболее релевантных по тематике и DR.
- Запустить скрипт Ahrefs+Python, экспортировать JSON‑файл с полями: домен, DR, UR, anchor‑text, трафик.
- Отфильтровать результаты по порогу DR>60 и трафика >500.
- Создать CSV‑таблицу для ручной проверки: проверить наличие nofollow, релевантность контента, наличие спама.
- На основе проверенных ссылок сформировать outreach‑план: персонализированные письма, список контактов, сроки.
- Запустить outreach, отслеживать отклики через CRM, обновлять статус в таблице.
- Через 2 недели проанализировать изменения DR/UR, обновить скрипт с новыми фильтрами.
9. Мониторинг и обновление скрипта
После запуска скрипта ключевой задачей становится постоянный контроль над его работой. Основные показатели: сколько запросов к Ahrefs сделано, сколько ошибок возникло, насколько свежи данные в базе, и как быстро они обновляются. Важно не просто запустить cron, но и знать, когда он падает, когда лимит API достигается и как быстро появляются новые ссылки. Весь мониторинг можно собрать в одном месте – лог‑файлы, метрики и уведомления.
- Проверка лимитов Ahrefs:
GET https://apiv2.ahrefs.com/api/v2/limits?token=YOUR_TOKEN– выводитremaining_requestsиreset_time. - Логи cron: убедитесь, что каждая задача завершает работу без ошибок (строка
CRON_SUCCESS=true). - Скорость обновления: сравните timestamp последнего обновления в базе с текущим временем.
- Количество новых ссылок: если прирост меньше 1 % от общего объёма, возможно, нужно пересмотреть диапазон дат.
- Память и размер БД: следите за ростом файлов
links.dbиlog.txt. - Уведомления: настройте e‑mail (или Slack) при превышении 80 % лимита API или при ошибке 5xx.
- Периодическое тестирование: раз в неделю запускайте скрипт вручную, проверяя, что он не дублирует данные.
# /etc/cron.d/ahrefs_links
# Run every 12 hours
0 */12 * * * www-data /usr/bin/python3 /home/www/ahrefs_links.py >> /var/log/ahrefs_links.log 2>&1
10. Частые ошибки и риски при работе с Ahrefs API
При работе с Ahrefs API часто встречаются три критические ошибки: переполнение лимитов, неправильный парсинг и неправильное хранение данных.
-
Переполнение лимитов
Последствия: API‑ответы прерываются, запросы отклоняются, данные обрываются, что приводит к неполным метрикам и ошибочным выводам.
Как избежать: Считайтеrate_limitв каждом ответе, реализуйте экспоненциальный back‑off, ставьте таймауты и ограничивайте частоту запросов до 1‑2 в секунду. -
Неправильный парсинг
Последствия: Неверные ключи, пропущенные поля, смешивание форматов JSON/XML приводит к потере информации и некорректным аналитическим выводам.
Как избежать: Используйте типизированные модели (pydantic, dataclasses), валидируйте схемы черезjsonschema, логируйте ошибки парсинга и проверяйте sample‑ответы. -
Неправильное хранение
Последствия:ем> Данные теряются при сбоях, нарушается согласованность, возможны дубли, что усложняет последующий анализ и ухудшает производительность.
Как избежать: Сохраняйте raw JSON в объектный хранилище (S3, GCS), используйте транзакционные БД (PostgreSQL, ClickHouse) с уникальными ключами, применяйте idempotent‑пакеты и проверяйте checksum.
Вопросы и ответы
Как получить API‑ключ Ahrefs?
Для получения ключа необходимо зарегистрироваться на сайте Ahrefs, перейти в раздел API, выбрать тарифный план, подтвердить оплату и скопировать ключ из личного кабинета. Ключ хранится в секрете и используется в заголовке запроса.
Можно ли использовать скрипт для большого количества конкурентов?
Да, скрипт можно применить к множеству сайтов, однако Ahrefs ограничивает количество запросов в секунду и в сутки. При работе с большим списком конкурентов стоит реализовать паузы и проверять статус кода 429, чтобы избежать блокировки.
Что такое Ahrefs API?
Ahrefs API – это набор REST‑эндпоинтов, позволяющих получать данные о ссылочной структуре, органическом поиске, контенте и т.д. API возвращает JSON, который можно парсить в Python для дальнейшего анализа.
Как настроить окружение для работы со скриптом?
Установите Python 3.8+, затем pip install requests pandas. Создайте файл .env с переменной API_KEY, импортируйте её в скрипт через os.getenv. Это позволит безопасно хранить ключ и использовать его в запросах.
Как обрабатывать ошибки при работе с API?
Проверяйте статус код ответа: 200 – всё ок, 403 – недоступен ключ, 429 – превышен лимит. В случае ошибки выводите сообщение и при необходимости делайте паузу перед повторной попыткой. Логи помогают отслеживать сбои.
Как сохранить результаты анализа?
После получения JSON‑ответа преобразуйте его в DataFrame pandas, затем используйте df.to_csv('output.csv', index=False) или df.to_json('output.json', orient='records'). Это упрощает дальнейший экспорт в BI‑системы.
Как фильтровать ссылки по домену?
В ответе API содержится поле domain, которое можно использовать для фильтрации. В pandas примените df[df['domain'] == 'example.com'] чтобы оставить только ссылки, относящиеся к нужному домену.
Как интерпретировать показатель Domain Rating?
Domain Rating – это логарифмическая шкала от 0 до 100, отражающая силу ссылочного профиля. Чем выше значение, тем более авторитетен сайт. Для сравнения конкурентов смотрите разницу в 5–10 точек как значимую.
Как обновлять данные по времени?
Ahrefs предоставляет поле last_update. Сохраняйте его и при следующем запуске скрипта сравнивайте с текущей датой. Если разница меньше 7 дней, можно пропустить запрос, чтобы экономить лимит.
Как избежать блокировки аккаунта при частых запросах?
Соблюдайте лимиты Ahrefs: не превышайте 10 запросов в секунду и 2000 в сутки. Добавляйте случайные паузы, используйте заголовок User-Agent, и если получаете 429, ждите 60 секунд перед продолжением.
Важно
Материал носит информационный характер. Перед внедрением рекомендаций учитывайте нишу, регион, конкурентов, текущее состояние сайта и бизнес-цели проекта.
Материал подготовлен и проверен редакцией AX.SEO
Редакция AX.SEO готовит материалы о SEO, разработке, AI, аналитике, маркетинге и росте digital-проектов.
Проверяет практическую применимость рекомендаций, корректность терминов и соответствие материала digital-тематике.