مشروع بايثون: أداة SEO Audit لتحليل ملف الزحف

60 دقيقة تقريبًا

ما تحتاج أن تعرفه قبل هذا الدرس
  • Variable
  • String
  • Integer
  • Float
  • Boolean
  • List
  • Dictionary
  • print
  • type
  • key
  • index
  • append
  • len
  • if
  • else
  • elif
  • Comparison Operators
  • and
  • or
  • not
  • for
  • while
  • break
  • continue
  • Function
  • def
  • Parameter
  • Argument
  • return
  • open
  • with
  • read
  • write
  • strip
  • startswith
  • endswith
  • import
  • urlparse
  • csv
  • reader
  • DictReader
  • writer
  • DictWriter
  • fieldnames
  • pandas
  • DataFrame
  • read_csv
  • head
  • columns
  • shape
  • value_counts
  • isna
  • notna
  • sort_values
  • contains
  • to_csv
  • set
  • lower
  • rstrip
  • replace
  • split
  • duplicated
  • drop_duplicates
  • rename
  • merge
  • groupby
  • try
  • except
ما يضيفه هذا الدرس

    يحسب العدّاد في الأعلى تمارين الدرس، وعددها 5، أما القراءة وتشغيل الأمثلة فلا تُحتسب.

    إحنا وصلنا لنهاية المرحلة الأولى.

    ولحد دلوقتي اتعلمنا حاجات كتير:

    لكن لو وقفنا هنا، ممكن تفضل شايفهم كدروس منفصلة.

    عارف تعمل Loop. عارف تكتب Function. عارف تقرأ CSV. وعارف تعمل Filter باستخدام Pandas.

    لكن الهدف من البداية مش إنك تحفظ بايثون. الهدف إنك تستخدم بايثون في شغل SEO.

    علشان كده الدرس ده مفيهوش مكتبة جديدة تقريبًا، ومش هنحاول نحشر Commands جديدة.

    هنعمل برنامج كامل من البداية للنهاية.

    البرنامج هيأخذ Crawl من موقع، يحلل البيانات، يطلع مجموعة Reports، وبعدها يحفظ كل Report في ملف منفصل.

    هنسميه SEO Audit Analyzer.

    لكن قبل ما نكتب أي كود، هنطبق اللي اتعلمناه في الدرس السابق.

    القاعدة
    مش هنبدأ من الكود. هنبدأ من المشكلة.

    البرنامج المفروض يعمل إيه؟

    عندنا ملف Crawl اسمه crawl.csv وجواه معلومات عن صفحات الموقع.

    عايزين البرنامج يطلع لنا:

    وبعدها يحفظ التقارير المهمة في ملفات CSV منفصلة.

    إحنا هنا مش بنقول إن كل نتيجة من دول مشكلة SEO لازم تتصلح. إحنا بنبني Analyzer يساعدنا نكتشف الصفحات اللي تحتاج مراجعة.

    بعدها ييجي دور متخصص SEO في الحكم على كل حالة.

    ملف المشروع

    اعمل ملف اسمه crawl.csv وحط جواه البيانات دي:

    وقبل ما نحلل الملف، خلينا نبص عليه بعقل متخصص SEO.

    عندنا 12 URLs، منهم صفحات 200 و301 و404. وفيه صفحات Indexable وصفحات Non-Indexable. وفيه Titles. وفيه عدد كلمات. وفيه URL واحد على الأقل فيه Query Parameter: ?page=2.

    يعني عندنا Data كفاية نطبق عليها أغلب اللي اتعلمناه.

    الأول هنكتب Pseudocode

    قبل بايثون، هنكتب البرنامج بلغتنا.

    دي الخطة. دلوقتي فقط نبدأ نترجمها إلى بايثون.

    الخطوة الأولى: قراءة الملف

    هنستخدم Pandas.

    مثال
    الناتج المتوقع
                                      URL  Status      Title   Indexability  Words
    0                https://example.com/     200       Home      Indexable    900
    1          https://example.com/about/     200   About Us      Indexable    650
    2       https://example.com/blog/seo/     200  SEO Guide      Indexable   2200
    3    https://example.com/blog/python/     200        NaN      Indexable   1800
    4  https://example.com/product/phone/     200      Phone  Non-Indexable    320

    أول حاجة اتأكدنا منها إن الملف اتقرأ. وكمان لاحظنا إن الـ Title الفاضي تحول إلى NaN. وده شيء إحنا عارفينه من درس Pandas.

    نفحص البيانات قبل ما نحللها

    متقفزش على الفلاتر فورًا. نعمل:

    مثال
    الناتج المتوقع
    Index(['URL', 'Status', 'Title', 'Indexability', 'Words'], dtype='str')

    يعني عندنا الأعمدة اللي متوقعينها فعلًا: URL وStatus وTitle وIndexability وWords. بعدها:

    مثال
    الناتج المتوقع
    (12, 5)

    يعني 12 Rows و5 Columns. وده نفس عدد URLs اللي متوقعينه. بعدها:

    مثال
    الناتج المتوقع
    URL               str
    Status          int64
    Title             str
    Indexability      str
    Words           int64
    dtype: object

    المهم هنا إن Status رقم، وWords رقم. إذن لما نعمل df["Status"] == 404 هنستخدم 404 كرقم، مش "404".

    نعرف حجم البيانات

    مثال
    الناتج المتوقع
    Total URLs: 12

    دي قيمة بسيطة، لكن وجودها مهم. دلوقتي إحنا عارفين إن الـ Input الأساسي فيه 12 URLs، وأي تحليل بعد كده لازم يكون منطقي بالنسبة للعدد ده.

    نبدأ من Status Codes

    قبل ما نطلع 404 أو 301، خلينا نفهم توزيع الـ Status Codes كلها.

    مثال
    الناتج المتوقع
    Status
    200    9
    404    2
    301    1
    Name: count, dtype: int64

    إذن عندنا 9 URLs بـ 200، 2 URLs بـ 404، URL واحد بـ 301. ولو جمعناهم 9 + 2 + 1 = 12، وده يطابق العدد الكلي.

    دي أول Sanity Check.

    التقرير الأول: صفحات 404

    المشكلة واضحة. عايزين Status = 404. الكود:

    مثال
    الناتج المتوقع
                                 URL  Status     Title   Indexability  Words
    7   https://example.com/missing/     404       NaN  Non-Indexable      0
    8  https://example.com/blog/old/     404  Old Blog  Non-Indexable      0
    404 Pages: 2

    إذن عندنا صفحتين، وده نفس الرقم اللي ظهر في value_counts(). يبقى التحليل منطقي.

    هل كل 404 لازم تعمل لها Redirect؟

    لا. وده مهم جدًا.

    البرنامج هنا بيقول: عندك صفحتين Status بتاعهم 404. لكنه مش بيقول: اعمل Redirect للصفحتين.

    ممكن URL تحتاج Redirect. وممكن تكون 404 طبيعية. وممكن يكون عندك Internal Link غلط محتاج يتصلح. وممكن تكون صفحة اتحذفت ومفيش بديل ليها.

    إذن تقرير 404 Pages هو Detection، مش Automatic Fix.

    التقرير الثاني: صفحات 301

    نفس الفكرة. الشرط Status = 301. نكتب:

    مثال
    الناتج المتوقع
                                    URL  Status        Title   Indexability  Words
    6  https://example.com/old-product/     301  Old Product  Non-Indexable     20
    301 Pages: 1

    عندنا Redirect واحدة. لكن مرة تانية، وجود 301 مش معناه تلقائيًا إن عندنا مشكلة.

    إحنا نحتاج بعد كده نعرف مثلًا:

    • الـ Redirect رايح فين؟
    • هل فيه Redirect Chain؟
    • هل Internal Links لسه بتشير للـ Redirect؟
    • هل الـ Redirect مقصود؟

    لكن البيانات الحالية مش فيها المعلومات دي. فمنخترعش تحليل مش موجود في الـ Data.

    التقرير الثالث: صفحات 200 غير القابلة للفهرسة

    هنا عندنا شرطين. الأول Status = 200، والثاني Indexability = Non-Indexable. إذن:

    مثال
    الناتج المتوقع
                                       URL  Status  ...   Indexability Words
    4   https://example.com/product/phone/     200  ...  Non-Indexable   320
    11        https://example.com/private/     200  ...  Non-Indexable   150
    
    [2 rows x 5 columns]
    200 Non-Indexable: 2
    ملحوظة
    لاحظ إن Pandas خبّت عمود Title وحطت ... مكانه — الجدول أعرض من عرض الشاشة الافتراضي اللي بتحسبه، فبتختصر العرض تلقائيًا. ده مش خطأ، وهنحله بعد شوية لما نختار الأعمدة اللي محتاجينها بس قبل ما نطبع.

    لكن هل الاتنين مشاكل؟ مقدرش أقول. مثلًا:

    /private/ ممكن تكون صفحة المفروض أصلًا متدخلش جوجل.

    فمرة تانية البرنامج بيطلع Pages to review، مش Pages that must be indexed.

    التقرير الرابع: صفحات 200 بدون Title

    عندنا Status = 200 وفي نفس الوقت Title = Missing. إذن:

    مثال
    الناتج المتوقع
                                    URL  Status Title Indexability  Words
    3  https://example.com/blog/python/     200   NaN    Indexable   1800
    Missing Titles: 1

    دلوقتي نراجع النتيجة نفسها. عندنا https://example.com/blog/python/. Status: 200. Title: NaN. إذن الفلتر عمل اللي إحنا طلبناه فعلًا.

    التقرير الخامس: صفحات 200 أقل من 100 كلمة

    عايزين Status = 200 وWords < 100. نكتب:

    مثال
    الناتج المتوقع
                                        URL  Status    Title Indexability  Words
    5  https://example.com/category/phones/     200   Phones    Indexable     80
    9          https://example.com/contact/     200  Contact    Indexable     70
    Pages under 100 words: 2

    هل الصفحتين عندهم Thin Content؟ مش بالضرورة. صفحة /contact/ طبيعي جدًا تكون قصيرة.

    إذن اسم التقرير الأفضل بالنسبة لنا Low Word Count Pages، مش Thin Content Errors. فرق التسمية هنا مهم لأنه بيعكس طريقة تفكير صح.

    التقرير السادس: صفحات Blog

    هنا إحنا مش بنبحث عن Issue. إحنا بنقسم الموقع حسب نوع URL. عايزين URLs اللي فيها /blog/. نستخدم:

    مثال
    الناتج المتوقع
                                    URL  Status      Title   Indexability  Words
    2     https://example.com/blog/seo/     200  SEO Guide      Indexable   2200
    3  https://example.com/blog/python/     200        NaN      Indexable   1800
    8     https://example.com/blog/old/     404   Old Blog  Non-Indexable      0

    عندنا ثلاث Blog URLs. لكن لاحظ حاجة مهمة. واحدة منهم 404.

    لو سألنا: كام Blog URL موجود في الـ Crawl؟ الإجابة: 3.

    لكن لو السؤال: كام Blog Page شغالة بـ Status 200؟ لازم نزود شرط.

    مثال
    الناتج المتوقع
                                    URL  Status      Title Indexability  Words
    2     https://example.com/blog/seo/     200  SEO Guide    Indexable   2200
    3  https://example.com/blog/python/     200        NaN    Indexable   1800

    النتيجة بقت صفحتين. وده مثال ممتاز على إن السؤال نفسه لازم يكون واضح.

    من Blog Pages، مين عنده Missing Title؟

    إحنا نقدر نكمل التحليل على DataFrame أصغر. عندنا blog_200_pages وفيها فقط Blog Pages اللي Status بتاعها 200. نعمل:

    مثال
    الناتج المتوقع
                                    URL  Status Title Indexability  Words
    3  https://example.com/blog/python/     200   NaN    Indexable   1800

    هنا بنشوف قوة إننا نبني تحليل فوق تحليل.

    بدأنا بـ
    1. 1كل الصفحات.
    2. 2Blog Pages.
    3. 3Blog 200 Pages.
    4. 4Blog 200 Pages with Missing Titles.

    دلوقتي نراجع URL Parameters

    في الدرس السابع اتعلمنا urlparse(). وإحنا عندنا في البيانات https://example.com/shop/?page=2. عايزين نعرف URLs اللي فيها Query Parameters.

    ممكن نستخدم اللي اتعلمناه بالفعل. نستورد:

    وبعدين نعمل List فاضية:

    ونمر على عمود URL:

    مثال
    الناتج المتوقع
    ['https://example.com/shop/?page=2']

    خلينا نفهم اللي حصل.

    عندنا for url in df["URL"]: يعني: مر على كل URL في عمود URL. بعدها parsed_url = urlparse(url) فككنا الرابط. وبعدين if parsed_url.query: لو فيه Query، أضف الرابط إلى parameter_urls.

    وده مثال مهم لأننا استخدمنا Pandas وLoop وList وCondition وurlparse كلهم في جزء واحد.

    هل وجود Parameter معناه مشكلة؟

    لا. وجود ?page=2 لوحده مش كفاية تقول Crawl Trap أو Duplicate Content. محتاج تعرف:

    • كام URL بيتولد؟
    • هل جوجل بتزحفها؟
    • هل Canonical مظبوط؟
    • هل Parameters ممكن تولد عدد غير محدود؟
    • هل الصفحات مفيدة؟
    • هل فيه Filters؟
    • هل Sitemap بتحتوي عليها؟

    فالبرنامج هنا بيقول: عندك URLs فيها Parameters، مش: عندك Crawl Trap.

    خلينا نطبع Summary للـ Audit

    بدل ما نفضل نشوف النتائج متفرقة، نعمل Summary. نكتب:

    مثال
    الناتج المتوقع
    
    SEO Audit Summary
    Total URLs: 12
    404 Pages: 2
    301 Pages: 1
    200 Non-Indexable: 2
    Missing Titles: 1
    Low Word Count: 2
    Blog URLs: 3
    URLs with Parameters: 1

    دلوقتي البرنامج بدأ يبقى Analyzer فعلًا.

    قبل الحفظ، اختار الأعمدة المفيدة

    ممكن نحفظ broken_pages بكل الأعمدة. لكن هل ده أفضل دائمًا؟ حسب التقرير.

    مثلًا في تقرير 404 ممكن نحتاج URL وStatus وTitle. فنكتب:

    مثال
    الناتج المتوقع
                                 URL  Status     Title
    7   https://example.com/missing/     404       NaN
    8  https://example.com/blog/old/     404  Old Blog

    بالنسبة لتقرير Missing Titles ممكن نحتاج الأعمدة كلها. يعني ممكن نحتفظ بكل الأعمدة. الفكرة إن الـ Output نفسه جزء من تصميم البرنامج.

    نحفظ التقارير

    نبدأ بصفحات 404:

    صفحات 301:

    صفحات 200 غير القابلة للفهرسة:

    Missing Titles:

    Low Word Count:

    Blog Pages:

    بعد تشغيل البرنامج هيبقى عندنا:

    يعني أخدنا ملف واحد وحولناه لمجموعة تقارير.

    هل نحفظ Parameter URLs؟

    عندنا حاليًا parameter_urls، ودي List. مش DataFrame. ممكن نحفظها بالطريقة اللي اتعلمناها في درس Files.

    مثال
    الناتج المتوقع
    https://example.com/shop/?page=2

    شايف هنا إحنا رجعنا استخدمنا درس Files مرة تانية.

    أول نسخة كاملة من البرنامج

    دلوقتي نجمع كل الأجزاء.

    وفي المجلد هتلاقي كل ملفات الـ Crawl والتقارير وملف الـ Parameters.

    دي أول نسخة كاملة من الـ Analyzer.

    هل البرنامج خلص؟

    من ناحية إنه بيعمل المطلوب، آه. لكن نقدر نحسن تنظيمه. وهنا نرجع للـ Functions.

    بس خلي بالك من ترتيبنا. إحنا بنيناه الأول وخليناه يشتغل. بعد كده هنبدأ ننظمه.

    مش بدأنا من أول دقيقة نعمل عشر Functions وإحنا لسه مش عارفين البرنامج هيعمل إيه.

    أول Function: فحص البيانات

    إحنا عندنا الجزء ده:

    ممكن نحتاجه كل مرة نحلل ملف. نعمله Function:

    مثال
    الناتج المتوقع
    First Rows:
                                      URL  Status      Title   Indexability  Words
    0                https://example.com/     200       Home      Indexable    900
    1          https://example.com/about/     200   About Us      Indexable    650
    2       https://example.com/blog/seo/     200  SEO Guide      Indexable   2200
    3    https://example.com/blog/python/     200        NaN      Indexable   1800
    4  https://example.com/product/phone/     200      Phone  Non-Indexable    320
    
    Columns:
    Index(['URL', 'Status', 'Title', 'Indexability', 'Words'], dtype='str')
    
    Shape:
    (12, 5)
    
    Data Types:
    URL               str
    Status          int64
    Title             str
    Indexability      str
    Words           int64
    dtype: object

    ميزة الـ Function إننا جمعنا مهمة واحدة واضحة Inspect Data في مكان واحد.

    Function لصفحات 404

    مثال
    الناتج المتوقع
                                 URL  Status     Title   Indexability  Words
    7   https://example.com/missing/     404       NaN  Non-Indexable      0
    8  https://example.com/blog/old/     404  Old Blog  Non-Indexable      0

    الـ Function هنا اسمها واضح. get_broken_pages(df) يعني: هات Broken Pages من الـ DataFrame دي.

    Function للـ Redirects

    مثال
    الناتج المتوقع
                                    URL  Status        Title   Indexability  Words
    6  https://example.com/old-product/     301  Old Product  Non-Indexable     20

    Function للـ Missing Titles

    مثال
    الناتج المتوقع
                                    URL  Status Title Indexability  Words
    3  https://example.com/blog/python/     200   NaN    Indexable   1800

    Function للصفحات قليلة الكلمات

    هنا فيه حاجة ممكن نحسنها. إحنا حاليًا عاملين Words < 100. لكن ماذا لو المرة الجاية عايز Words < 200؟

    بدل ما نغير الكود جوه الـ Function، ممكن نخلي الرقم Parameter. نكتب:

    مثال
    الناتج المتوقع
                                        URL  Status    Title Indexability  Words
    5  https://example.com/category/phones/     200   Phones    Indexable     80
    9          https://example.com/contact/     200  Contact    Indexable     70

    لو عايز أقل من 500:

    جرّب بنفسك — غيّر الرقم وشغّل

    النتيجة هتتغير لأننا غيرنا limit، من غير ما نعدل الـ Function نفسها. هنا بدأنا نشوف ليه Functions مفيدة فعلًا.

    Function لحفظ التقرير

    إحنا مكررين:

    أكتر من مرة. ممكن نعمل:

    ونستخدم:

    وكده عندنا Function واحدة مسؤولة عن حفظ تقارير CSV.

    هل كل حاجة لازم تبقى Function؟

    برضه لا. ممكن تعمل Function لكل سطر في البرنامج وتوصل في الآخر لكود أصعب من الأول.

    إحنا نستخدم Function لما:

    • العملية لها معنى واضح.
    • العملية ممكن تتكرر.
    • تجميعها يخلي البرنامج أسهل في القراءة.
    • نحتاج نمرر لها Values مختلفة.

    مش لمجرد إننا اتعلمنا Functions.

    نسخة منظمة من المشروع

    دلوقتي نقدر نكتب البرنامج بالشكل ده:

    مثال
    الناتج المتوقع
    First Rows:
                                      URL  Status      Title   Indexability  Words
    0                https://example.com/     200       Home      Indexable    900
    1          https://example.com/about/     200   About Us      Indexable    650
    2       https://example.com/blog/seo/     200  SEO Guide      Indexable   2200
    3    https://example.com/blog/python/     200        NaN      Indexable   1800
    4  https://example.com/product/phone/     200      Phone  Non-Indexable    320
    
    Columns:
    Index(['URL', 'Status', 'Title', 'Indexability', 'Words'], dtype='str')
    
    Shape:
    (12, 5)
    
    Data Types:
    URL               str
    Status          int64
    Title             str
    Indexability      str
    Words           int64
    dtype: object
    
    Status Codes:
    Status
    200    9
    404    2
    301    1
    Name: count, dtype: int64
    
    SEO Audit Summary
    Total URLs: 12
    404 Pages: 2
    301 Pages: 1
    200 Non-Indexable: 2
    Missing Titles: 1
    Low Word Count: 2
    URLs with Parameters: 1

    لكن البرنامج دلوقتي بقى منظم أكتر.

    بص إحنا استخدمنا إيه فعلًا

    المشروع ده تقريبًا جمع المرحلة الأولى كلها.

    • Variable: df = pd.read_csv("crawl.csv")
    • Lists: parameter_urls = []
    • Conditions: if parsed_url.query:
    • Loops: for url in df["URL"]:
    • Functions: def get_broken_pages(df):
    • Files: with open(...)
    • URLs: urlparse(url)
    • CSV: crawl.csv
    • Pandas: pd.read_csv()
    • Filtering: df[df["Status"] == 404]
    • Missing Values: df["Title"].isna()
    • Export: to_csv()

    يعني الأدوات اللي كانت منفصلة في بداية المرحلة، بدأت تبقى أجزاء من Workflow واحد.

    أهم جزء في المشروع مش الكود

    ممكن تاخد الكود النهائي Copy وPaste وتشغله. لكن لو ده كل اللي عملته، إنت مستفدتش أهم حاجة من المشروع.

    المهم إنك تكون قادر تجاوب:

    ملحوظة
    ليه استخدمنا Pandas هنا؟

    لأن البيانات جدولية.

    ملحوظة
    ليه استخدمنا Loop مع Parameter URLs؟

    لأننا بنمر على URLs ونفك كل URL باستخدام urlparse().

    ملحوظة
    ليه استخدمنا &؟

    لأن عندنا أكتر من شرط Pandas لازم يتحقق.

    ملحوظة
    ليه استخدمنا isna()؟

    لأن الـ Title المفقود تحول إلى Missing Value.

    ملحوظة
    ليه استخدمنا Function في Low Word Pages؟

    علشان نخلي Threshold قابل للتغيير.

    ملحوظة
    ليه بنطبع Counts؟

    علشان نراجع إن النتيجة منطقية.

    لو فاهم الإجابات دي، إنت استفدت من المرحلة.

    لو هتشغّله على Export حقيقي

    الـ Analyzer اللي بنيناه بيستخدم أعمدة اسمها URL وStatus وTitle وIndexability وWords. لكن زي ما شفنا في درس Pandas، الـ Export الحقيقي من Screaming Frog أعمدته أسماؤها مختلفة.

    مش محتاج تغيّر أي Function. كل اللي محتاجه سطر واحد بعد قراءة الملف:

    مثال
    الناتج المتوقع
    ['https://example.com/old-page/']

    وده بالظبط الفرق بين إنك تبني الكود على أسماء واضحة، وإنك تربطه بشكل ملف معين. الـ Functions نفسها مش محتاجة تعرف الملف جاي منين.

    ولو عايز تطوّر الـ Analyzer خطوة كمان: ادمج معاه ملف Search Console بـ merge()، وطلّع صفحات 404 اللي لسه بتجيب Clicks. ده تقرير هتلاقيه مفيد جدًا في أي Audit حقيقي.

    المشروع الحالي Analyzer مش Crawler

    دي نقطة مهمة جدًا قبل ما نبدأ المرحلة التالية.

    برنامجنا الحالي بياخد crawl.csv. يعني البيانات موجودة أصلًا. هو مش بيدخل الموقع ويجمع البيانات بنفسه.

    المسار الحالي:

    المسار الحالي
    1. 1Crawler / SEO Tool
    2. 2crawl.csv
    3. 3Python Analyzer
    4. 4Reports

    لكن في المرحلة التالية هنبدأ نبني الجزء اللي قبل كده.

    يعني بدل ما نقول لبايثون: خد البيانات دي وحللها.

    هنبدأ نقول: روح للـ URL، ابعت Request، شوف السيرفر رجع إيه، اقرأ HTML، واستخرج البيانات بنفسك.

    ودي نقلة كبيرة جدًا في الكورس.

    قبل ما ننهي المرحلة، عندك Challenge

    مش هديك كود الجزء ده كامل. لأن المطلوب هنا إنك تبدأ تبني بنفسك. استخدم نفس crawl.csv.

    وعايزك تطور الـ Analyzer بحيث يطلع تقرير جديد اسمه blog-review.csv.

    التقرير يحتوي فقط على Blog Pages اللي:

    وفي نفس الوقت عندها واحدة على الأقل من المشكلتين:

    أو:

    خلي بالك من المنطق. إحنا عايزين:

    قبل ما تكتب الكود، اكتبه بالعربي أو Pseudocode.

    تحدّي SEO

    تقرير blog-review.csv

    من نفس crawl.csv، اطلّع Blog Pages اللي Status = 200 وIndexability = Indexable، وعندها Title مفقود أو Words أقل من 100 (شرط أو، جوه شرط و)، واحفظها في blog-review.csv بدون Index.

    Challenge الثاني

    اعمل Function اسمها get_blog_pages() تستقبل df وترجع Blog Pages اللي Status بتاعها 200 فقط.

    المفروض النتيجة تكون https://example.com/blog/seo/ وhttps://example.com/blog/python/، مش https://example.com/blog/old/ لأن دي 404.

    تحدّي SEO

    Function: get_blog_pages()

    اكتب Function اسمها get_blog_pages تستقبل df وترجّع DataFrame فيه Blog Pages اللي Status بتاعها 200 فقط.

    Challenge الثالث

    اعمل Function اسمها print_summary() تستقبل النتائج اللي طلعتها وتطبع Summary بالشكل:

    فكر الأول: إيه الـ Parameters اللي الـ Function محتاجاها؟ مش لازم يكون فيه حل واحد فقط.

    جاوب بأسلوبك

    اكتب توقيع Function اسمها print_summary (إيه الـ Parameters اللي هتستقبلها؟) وجسمها، اللي تطبع الـ Summary بالشكل المطلوب. تفتكر إيه أفضل طريقة تستقبل بيها الأرقام: قيم منفصلة، ولا Dictionary واحد؟

    اكتب إجابتك الأول. الفايدة في إنك تحاول قبل ما تشوف.

    Challenge الرابع

    غيّر Low Word Count من 100 إلى 200، من غير ما تعدل الكود داخل get_low_word_pages(). المفروض تغير فقط القيمة اللي بتبعتها للـ Function. شوف النتيجة هتتغير لإيه.

    جرّب بنفسك — غيّر الرقم من 100 لـ 200

    Challenge الخامس

    بعد ما البرنامج يشتغل، حاول تعمل تحليل غلط عمدًا. مثلًا بدل:

    اكتب:

    وشوف الفرق في النتائج.

    الهدف هنا إنك تشوف بنفسك إن الكود يشتغل مش معناها الكود يجاوب السؤال الصح.

    جاوب بأسلوبك

    شغّل الكود بالشرطين (== 200 مرة، و != 404 مرة) على نفس crawl.csv، وقارن عدد وأسماء الصفحات في كل مرة. إيه الفرق، وليه حصل؟

    اكتب إجابتك الأول. الفايدة في إنك تحاول قبل ما تشوف.

    الاختبار النهائي للمرحلة الأولى

    عندك ملف جديد اسمه final-audit.csv ومحتواه:

    المطلوب منك تبني Analyzer بنفسك. قبل الكود، اكتب Pseudocode.

    بعدها البرنامج لازم يعمل الآتي:

    1. يقرأ الملف.
    2. يطبع أول 5 Rows.
    3. يطبع أسماء الأعمدة.
    4. يطبع عدد الصفوف والأعمدة.
    5. يطبع عدد كل Status Code.
    6. يطلع صفحات 404.
    7. يطلع صفحات 301.
    8. يطلع صفحات 200 غير القابلة للفهرسة.
    9. يطلع صفحات 200 اللي Title بتاعها مفقود.
    10. يطلع صفحات 200 الأقل من 100 كلمة.
    11. يطلع Blog Pages اللي Status بتاعها 200.
    12. يطلع URLs اللي فيها Query Parameters.
    13. يستخدم urlparse() لمعرفة الـ Hostname لكل URL.
    14. يقسم URLs إلى Internal وExternal باعتبار إن الموقع الأساسي site.com.
    15. يطبع Summary بالأعداد.
    16. يحفظ التقارير في ملفات منفصلة.
    17. يستخدم Function واحدة على الأقل فيها Parameter قابل للتغيير.
    18. يستخدم Function لحفظ تقارير CSV بدل تكرار نفس الكود.
    19. يراجع عينة من كل Report قبل الحفظ.
    20. يتأكد إن مجموع الـ Status Codes يساوي العدد الكلي للـ URLs.

    مش الهدف إن البرنامج يبقى أقصر برنامج ممكن. ومش الهدف إنك تكتب كل حاجة في سطر واحد.

    الهدف إنك تقدر تبني الحل، تفهم كل خطوة، وتعرف ترجع لأي خطوة لو النتيجة طلعت غلط.

    ملحوظة
    الاختبار بيتصحح على الأجزاء اللي ليها نتيجة محددة بالظبط: 404، 301، صفحات 200 غير القابلة للفهرسة، وMissing Titles، وLow Word، وBlog Pages، وParameters، وInternal/External. أما تنظيم الكود بـ Functions والمراجعة والملفات المحفوظة فده أسلوبك الشخصي، ومفيش شكل واحد صح ليه.
    تحدّي SEO

    SEO Audit Analyzer — final-audit.csv

    ابنِ Analyzer كامل على final-audit.csv: صفحات 404، صفحات 301، صفحات 200 غير القابلة للفهرسة، صفحات 200 بـ Title مفقود، صفحات 200 أقل من 100 كلمة، Blog Pages بـ Status 200، URLs فيها Parameters، وتقسيم Internal/External باعتبار site.com هو الموقع الأساسي.

    لو قدرت تعمل المشروع ده بنفسك، حتى لو رجعت للدروس السابقة أثناء العمل، فأنت حققت الهدف الحقيقي من المرحلة الأولى.

    إنت مش مطلوب منك تحفظ بايثون. إنت بقيت تعرف تبدأ من مشكلة SEO، تحدد البيانات، تحول المشكلة لخطوات، تختار الأداة المناسبة، تكتب الكود، تراجع النتيجة، وتحفظ Output تقدر تستخدمه في شغلك.

    وده بالضبط الأساس اللي محتاجينه قبل ما ندخل المرحلة الثانية ونخلي بايثون تبدأ تفحص الموقع بنفسها بدل ما تستقبل Crawl جاهز.

    بقيت تمارين لم تحلّها

    0/5

    التمارين المتبقية: 5 من 5. يُحتسب الدرس مكتملًا حين تحلّها كلها، ويمكنك العودة إليها متى شئت.

    كل الدروس

    أحسنت، وصلت إلى نهاية الدرس.