أتمتة تقارير الـ "Cannibalization" (تعارض الكلمات المفتاحية)


ماذا سنبني اليوم؟

سنتعلم اليوم كيفية أتمتة عملية اكتشاف تقارير تعارض الكلمات المفتاحية (Keyword Cannibalization) باستخدام بيانات Google Search Console (GSC) وواجهة برمجة التطبيقات (API) الخاصة بها، مع استخدام لغة Python لتجهيز البيانات وإخراج التقرير إلى جدول بيانات Google Sheets. الهدف هو توفير الوقت والجهد في تحديد الصفحات المتنافسة على نفس الكلمات المفتاحية، مما يساعد في تحسين استراتيجية SEO.

إعداد بيئة العمل

قبل البدء بكتابة الكود، نحتاج إلى تهيئة بيئة العمل:

  1. إنشاء مشروع على Google Cloud Platform (GCP):
    • اذهب إلى Google Cloud Console.
    • أنشئ مشروعاً جديداً.
    • فعّل واجهات برمجة التطبيقات التالية:
      • Google Search Console API
      • Google Sheets API
      • Google Drive API
    • من قسم "APIs & Services" -> "Credentials"، أنشئ "OAuth client ID" (نوع "Desktop app" أو "Web application" إذا كنت تستخدم خادماً). قم بتنزيل ملف credentials.json واحتفظ به في نفس مجلد ملف Python الخاص بك.
  2. تثبيت مكتبات Python اللازمة:
    pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib pandas
ملاحظة هامة: ملف credentials.json يحتوي على مفاتيح سرية لمشروعك. تعامل معه بحذر ولا تشاركه علناً. عند تشغيل السكريبت لأول مرة، سيطلب منك المصادقة عبر متصفح الويب. تأكد من أن حسابك الذي تصادق به يمتلك صلاحية الوصول إلى موقعك في Google Search Console.

جلب البيانات من Google Search Console

سنستخدم Google Search Console API لجلب بيانات الأداء (النقرات، مرات الظهور، متوسط الموضع، الصفحات) للكلمات المفتاحية على موقعك. سنطلب البيانات التي تتضمن كل من query و page لتحديد أي الصفحات تظهر لأي كلمة مفتاحية.

تحديد التعارض (Cannibalization)

تحدث ظاهرة تعارض الكلمات المفتاحية عندما تتنافس صفحتان أو أكثر من موقعك على نفس الكلمة المفتاحية في نتائج البحث. هذا يربك محركات البحث ويقلل من سلطة كل صفحة. لتحديد ذلك، سنقوم بتجميع البيانات حسب الكلمة المفتاحية (query) ثم البحث عن الكلمات المفتاحية التي تظهر مع أكثر من صفحة فريدة واحدة.

إخراج التقرير إلى Google Sheets

بعد معالجة البيانات وتحديد حالات التعارض، سنقوم بإنشاء تقرير منظم في Google Sheets لتسهيل المراجعة واتخاذ الإجراءات اللازمة. هذا يضمن إمكانية مشاركة التقرير والتعاون عليه بسهولة.

الكود العملي

هذا الكود يقوم بالخطوات المذكورة أعلاه. تأكد من استبدال YOUR_WEBSITE_URL برابط موقعك في GSC (مثل sc-domain:example.com أو https://www.example.com/).

import os
import pandas as pd
from datetime import datetime, timedelta
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build

# --- Configuration ---
SCOPES = ['https://www.googleapis.com/auth/webmasters.readonly',
          'https://www.googleapis.com/auth/spreadsheets',
          'https://www.googleapis.com/auth/drive']
CREDENTIALS_FILE = 'credentials.json' # Make sure this file is in the same directory
TOKEN_FILE = 'token.json' # Token will be stored here after first auth
WEBSITE_URL = 'sc-domain:yourdomain.com' # Replace with your GSC property URL (e.g., 'https://www.example.com/' or 'sc-domain:example.com')
SHEET_NAME = f'Cannibalization Report - {datetime.now().strftime("%Y-%m-%d")}'
# Date range for data (last 90 days)
END_DATE = datetime.now()
START_DATE = END_DATE - timedelta(days=90)

def authenticate_google_api():
    """Authenticates with Google APIs and returns credentials."""
    creds = None
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(TOKEN_FILE, 'w') as token:
            token.write(creds.to_json())
    return creds

def get_search_console_data(service, start_date, end_date, website_url):
    """Fetches data from Google Search Console API."""
    request_body = {
        'startDate': start_date.strftime('%Y-%m-%d'),
        'endDate': end_date.strftime('%Y-%m-%d'),
        'dimensions': ['query', 'page'],
        'rowLimit': 25000, # Max rows per request, adjust if needed
        # 'startRow': 0 # For pagination if needed for extremely large datasets
    }
    print(f"Fetching data from GSC for {website_url} from {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}...")
    response = service.searchanalytics().query(siteUrl=website_url, body=request_body).execute()
    return response.get('rows', [])

def identify_cannibalization(data):
    """Processes GSC data to identify potential keyword cannibalization."""
    if not data:
        print("No data received from Google Search Console.")
        return pd.DataFrame()

    df = pd.DataFrame(data)
    df['query'] = df['keys'].apply(lambda x: x[0])
    df['page'] = df['keys'].apply(lambda x: x[1])
    df = df.drop(columns=['keys'])

    # Calculate total clicks and impressions for each query-page combination
    df['clicks'] = pd.to_numeric(df['clicks'])
    df['impressions'] = pd.to_numeric(df['impressions'])
    df['position'] = pd.to_numeric(df['position'])

    # Group by query and count unique pages
    query_page_counts = df.groupby('query')['page'].nunique()
    cannibalized_queries = query_page_counts[query_page_counts > 1].index

    if cannibalized_queries.empty:
        print("No keyword cannibalization detected for the given period.")
        return pd.DataFrame()

    # Filter the original DataFrame to include only cannibalized queries
    cannibalization_df = df[df['query'].isin(cannibalized_queries)].copy()

    # Sort by query and then by impressions (or position) to see dominant pages
    cannibalization_df.sort_values(by=['query', 'impressions'], ascending=[True, False], inplace=True)

    return cannibalization_df

def create_google_sheet(sheets_service, drive_service, sheet_name):
    """Creates a new Google Sheet and returns its ID."""
    spreadsheet = {
        'properties': {
            'title': sheet_name
        }
    }
    spreadsheet = sheets_service.spreadsheets().create(body=spreadsheet, fields='spreadsheetId').execute()
    print(f"Spreadsheet ID: {spreadsheet.get('spreadsheetId')}")
    
    # Optionally, share the spreadsheet with specific email or make it public (use with caution)
    # drive_service.permissions().create(
    #     fileId=spreadsheet.get('spreadsheetId'),
    #     body={'type': 'user', 'role': 'writer', 'emailAddress': 'your_email@example.com'},
    #     fields='id'
    # ).execute()

    return spreadsheet.get('spreadsheetId')

def export_to_google_sheet(sheets_service, spreadsheet_id, df, sheet_tab_name='Cannibalization Report'):
    """Exports a Pandas DataFrame to a specified Google Sheet."""
    if df.empty:
        print("No data to export to Google Sheet.")
        return

    # Prepare data for Google Sheets API
    # Convert all columns to string to avoid type issues with Sheets API
    df_str = df.astype(str)
    values = [df_str.columns.tolist()] + df_str.values.tolist()

    body = {
        'values': values
    }
    range_name = f'{sheet_tab_name}!A1' # Assuming first tab

    # Update sheet name (optional, if you want to rename the first default tab)
    sheets_service.spreadsheets().batchUpdate(
        spreadsheetId=spreadsheet_id,
        body={
            "requests": [
                {
                    "updateSheetProperties": {
                        "properties": {
                            "sheetId": 0, # Default first sheet ID
                            "title": sheet_tab_name
                        },
                        "fields": "title"
                    }
                }
            ]
        }
    ).execute()

    sheets_service.spreadsheets().values().update(
        spreadsheetId=spreadsheet_id,
        range=range_name,
        valueInputOption='RAW',
        body=body
    ).execute()
    print(f"Data successfully exported to Google Sheet: https://docs.google.com/spreadsheets/d/{spreadsheet_id}")

def main():
    creds = authenticate_google_api()
    gsc_service = build('webmasters', 'v3', credentials=creds)
    sheets_service = build('sheets', 'v4', credentials=creds)
    drive_service = build('drive', 'v3', credentials=creds)

    gsc_rows = get_search_console_data(gsc_service, START_DATE, END_DATE, WEBSITE_URL)
    cannibalization_df = identify_cannibalization(gsc_rows)

    if not cannibalization_df.empty:
        spreadsheet_id = create_google_sheet(sheets_service, drive_service, SHEET_NAME)
        export_to_google_sheet(sheets_service, spreadsheet_id, cannibalization_df)
    else:
        print("No cannibalization report to generate.")

if __name__ == '__main__':
    main()

النتيجة النهائية المتوقعة

بعد تشغيل الكود بنجاح، ستجد جدول بيانات جديداً في حسابك على Google Drive بعنوان "Cannibalization Report - YYYY-MM-DD" (حيث YYYY-MM-DD هو تاريخ التشغيل). سيحتوي هذا الجدول على ورقة عمل واحدة تعرض جميع الكلمات المفتاحية التي تم تحديدها على أنها تعاني من التعارض، مع تفاصيل الصفحات المتنافسة، عدد النقرات، مرات الظهور، ومتوسط الموضع لكل صفحة. هذا التقرير سيمكنك من:

  • تحديد الكلمات المفتاحية التي تعاني من التعارض بشكل واضح.
  • رؤية الصفحات المتنافسة لكل كلمة مفتاحية.
  • تقييم تأثير التعارض بناءً على النقرات ومرات الظهور.
  • اتخاذ قرارات مستنيرة لتحسين استراتيجية المحتوى والروابط الداخلية (مثل دمج الصفحات، أو إعادة توجيه 301، أو تحسين المحتوى لتمييز الصفحات).

ستحصل على رابط مباشر لجدول البيانات في مخرجات الكونسول بعد انتهاء التشغيل.