لا شيء يطفّي الحماس أسرع من إنك تكتب 30 سطر Python، وتشغّل أداة استخراج البيانات من Goodreads، وبعدها تطلع لك []. قائمة فاضية. ولا شيء. وتبقى لحالك قدّام المؤشر وهو يلمع على الشاشة.
شفت هذا المشهد يتكرر عشرات المرات — في تجاربنا الداخلية في ، وفي منتديات المطورين، وحتى في مشكلات GitHub المتراكمة في مشاريع الاستخراج المهملة. وغالبًا تكون الشكوى نفسها: "قسم المراجعات العليا فاضي، ما يطلع لي إلا []"، "أي رقم صفحة أشغّله، يظل يسحب الصفحة الأولى كل مرة"، "كان الكود يشتغل السنة الماضية، والآن وقف"، وبهذا المعنى ما في شيء يزعّل أكثر من كذا. والأسوأ من هذا كله أن واجهة Goodreads API توقفت في ديسمبر 2020، فالنصيحة القديمة من نوع "استخدم الـ API فقط" ما عادت تنفع.
إذا كنت اليوم تحتاج بيانات كتب منظمة من Goodreads — مثل العناوين، المؤلفين، التقييمات، المراجعات، التصنيفات، وأرقام ISBN — فالاستخراج من الموقع هو الحل الأساسي. هذا الدليل بياخذك خطوة بخطوة لطريقة عملية وكاملة لاستخراج بيانات Goodreads باستخدام Python، مع تغطية المحتوى اللي ينحمّل عبر JavaScript، وترقيم الصفحات، وتفادي الحظر، والتصدير. وإذا Python ما يناسبك، بوريك بديل بدون كود يخلّص الشغل في تقريبًا نقرَتين.
ما هو استخراج بيانات Goodreads ولماذا نستخدم Python؟
استخراج بيانات Goodreads يعني سحب بيانات الكتب تلقائيًا — مثل العناوين، المؤلفين، التقييمات، عدد المراجعات، التصنيفات، أرقام ISBN، عدد الصفحات، وتاريخ النشر — من صفحات Goodreads باستخدام الكود بدل النسخ واللصق اليدوي.
يُعتبر Goodreads واحدًا من أكبر قواعد بيانات الكتب في العالم، مع أكثر من وحوالي . وكمان أكثر من 18 مليون كتاب يضاف إلى رفوف "Want to Read" كل شهر. هذا النوع من البيانات المنظمة والمتجددة باستمرار هو بالضبط السبب اللي يخلي الناشرين، وعلماء البيانات، وبائعي الكتب، والباحثين يرجعون له مرارًا.
Python هو الخيار المفضل لهذا النوع من الشغل — فهو يشغّل نحو من مشاريع استخراج البيانات. المكتبات ناضجة (requests وBeautifulSoup وSelenium وPlaywright وpandas)، والصياغة مناسبة للمبتدئين، والمجتمع كبير جدًا.
إذا ما سبق لك تسحب بيانات من موقع، فـ Python هو أفضل نقطة بداية.
لماذا نستخرج Goodreads باستخدام Python؟ حالات استخدام واقعية
قبل لا ندخل في الكود، من المفيد نسأل: من أصلًا يحتاج هذي البيانات، وماذا يسوي فيها؟
| حالة الاستخدام | من يستفيد | ما الذي يتم استخراجه |
|---|---|---|
| أبحاث سوق النشر | الناشرون، وكلاء الأدب | التصنيفات الرائجة، أعلى العناوين تقييمًا، المؤلفون الصاعدون، تقييمات المنافسين |
| أنظمة التوصية بالكتب | علماء البيانات، الهواة، مطورو التطبيقات | التقييمات، التصنيفات، رفوف المستخدمين، مشاعر المراجعات |
| متابعة الأسعار والمخزون | بائعو الكتب عبر التجارة الإلكترونية | العناوين الرائجة، حجم المراجعات، عدد "Want to Read" |
| البحث الأكاديمي | الباحثون، الطلاب | نصوص المراجعات، توزيع التقييمات، تصنيف التصنيفات |
| تحليلات القراءة | مدونو الكتب، المشاريع الشخصية | بيانات الرفوف الشخصية، سجل القراءة، إحصاءات نهاية السنة |
أمثلة عملية: قاعدة بيانات UCSD Book Graph — وهي من أكثر مجموعات البيانات الأكاديمية استشهادًا في أبحاث أنظمة التوصية — تضم ، وكلها جُمعت من رفوف Goodreads المتاحة للعامة. كما أن عدة مجموعات على Kaggle (مثل goodbooks-10k وBest Books Ever وغيرها) أصلها كان من استخراج بيانات Goodreads. وفي دراسة نُشرت عام 2025 في Big Data and Society جرى تجميع لتحليل أثر المراجعات الممولة على المنصة.
وعلى الجانب التجاري، تبيع Bright Data مجموعات بيانات Goodreads جاهزة بأسعار تبدأ من 0.50 دولار لكل 1,000 سجل — وهذا دليل واضح أن هذي البيانات لها قيمة سوقية فعلية.
واجهة Goodreads API انتهت — فما البديل الآن؟
إذا بحثت مؤخرًا عن "Goodreads API" فغالبًا وصلت إلى شرح قديم وغير محدّث. ففي 8 ديسمبر 2020، توقفت Goodreads بهدوء عن إصدار مفاتيح API جديدة للمطورين. ما كان فيه منشور رسمي كبير أو بريد جماعي — فقط لافتة صغيرة على صفحة التوثيق، وخلت كثير من المطورين في حيرة.

وكان الأثر سريعًا. أحد المطورين، Kyle K، بنى روبوت Discord لمشاركة توصيات الكتب — "فجأة، ومن دون مقدمات، توقف عن العمل". ومطور آخر، Matthew Jones، فقد الوصول إلى الـ API قبل أسبوع واحد من تصويت جوائز Reddit r/Fantasy Stabby، واضطر يرجع إلى Google Forms. وحتى مشروع أطروحة ماجستير لطالبة اسمها Elena Neacsu تعثّر أثناء التطوير.
فإيش المتاح الآن؟ المشهد الحالي كالتالي:
| النهج | البيانات المتاحة | سهولة الاستخدام | حدود المعدل | الحالة |
|---|---|---|---|---|
| Goodreads API | بيانات وصفية كاملة، مراجعات | سهلة (كانت كذلك) | طلب واحد/ثانية | متوقفة (ديسمبر 2020) — لا مفاتيح جديدة |
| Open Library API | عناوين، مؤلفون، ISBNs، أغلفة (~30 مليون عنوان) | سهلة | 1-3 طلبات/ثانية | نشطة، مجانية، بدون مصادقة |
| Google Books API | بيانات وصفية، معاينات | سهلة | 1,000/يوم مجانًا | نشطة (مع فجوات في ISBN غير الإنجليزية) |
| استخراج Python (requests + BS4) | أي شيء موجود في HTML الأولي | متوسط | تُدار ذاتيًا | يعمل مع المحتوى الثابت |
| استخراج Python (Selenium/Playwright) | المحتوى المُحمّل عبر JavaScript أيضًا | أصعب | تُدار ذاتيًا | مطلوب للمراجعات وبعض القوائم |
| Thunderbit (إضافة Chrome بدون كود) | أي بيانات ظاهرة على الصفحة | سهل جدًا (نقرتان) | حسب الرصيد | نشط — لا حاجة إلى Python |
يُعد Open Library بديل قوي، خاصةً للبحث عن ISBN والبيانات الوصفية الأساسية. لكن إذا كنت تحتاج التقييمات، أو المراجعات، أو وسوم التصنيف، أو عدد "Want to Read"، فأنت تحتاج استخراج Goodreads مباشرة — إما عبر Python أو عبر أداة مثل Thunderbit، التي تقدر تستخرج صفحات Goodreads (بما فيها الصفحات الفرعية الخاصة بتفاصيل الكتاب) مع حقول مقترحة بالذكاء الاصطناعي وتصدير مباشر إلى Google Sheets أو Notion أو Airtable.
لماذا تعيد لك أداة استخراج Goodreads في Python نتائج فارغة؟ وكيف تصلح ذلك
هذه هي الفقرة اللي كنت أتمنى ألاقيها عندما بدأت أتعامل مع بيانات Goodreads. مشكلة "النتائج الفارغة" هي أكثر شكوى شائعة في منتديات المطورين، ولها عدة أسباب مختلفة — ولكل سبب حل خاص.
This paragraph contains content that cannot be parsed and has been skipped.
المحتوى المحمّل عبر JavaScript: تظهر المراجعات والتقييمات فارغة
واجهة Goodreads الأمامية مبنية على React. لما تستدعي requests.get() على صفحة كتاب، تحصل على HTML الأولي فقط — لكن المراجعات، وتوزيع التقييمات، وكثير من أقسام "المزيد من المعلومات" تُحمّل بشكل غير متزامن عبر JavaScript. ببساطة، أداة الاستخراج ما تقدر تشوفها.
الحل: في أي صفحة تحتاج فيها إلى محتوى يُحمّل عبر JavaScript، انتقل إلى Selenium أو Playwright. وأنا أوصي بـ Playwright للمشاريع الجديدة — فهو بفضل بروتوكوله المعتمد على WebSocket، وكمان فيه قدرات أفضل مدمجة للتخفي والدعم غير المتزامن.

ترقيم صفحات لا يعيد إلا الصفحة الأولى
هذه مشكلة خبيثة. تكتب حلقة، وتزيد ?page=N، ومع ذلك تحصل على نفس النتائج في كل مرة. في Goodreads، صفحات الرفوف قد ترجع بصمت محتوى الصفحة الأولى مهما غيّرت معامل ?page= إذا لم تكن مسجّل الدخول. لا يظهر خطأ ولا تحويل — فقط نفس الصفحة الأولى تتكرر.
الحل: أضف Cookie جلسة مصادقة (وبالتحديد _session_id2). وسأشرح هذا بشكل أوضح في قسم ترقيم الصفحات تحت.
الكود الذي كان يعمل العام الماضي توقف الآن
Goodreads تغيّر أحيانًا أسماء أصناف HTML وبنية الصفحات. المستودع الشهير maria-antoniak/goodreads-scraper على GitHub عليه الآن تنبيه دائم: "هذا المشروع غير مُصان ولم يعد يعمل." الحل هو استخدام محددات أكثر ثباتًا — مثل البيانات المنظمة JSON-LD (التي تتبع معيار schema.org ونادرًا ما تتغير) أو سمات data-testid بدلًا من أسماء الأصناف الهشّة.
أخطاء 403 أو التعرض للحظر
مكتبة requests في Python تملك بصمة TLS مختلفة عن Chrome. وحتى لو ضبطت User-Agent يشبه Chrome، أنظمة كشف الروبوتات مثل AWS WAF (التي تستخدمها Goodreads باعتبارها شركة تابعة لـ Amazon) تقدر تلاحظ الفرق. الحل: أضف رؤوس متصفح واقعية، وطبّق تأخيرات time.sleep() من 3 إلى 8 ثوانٍ بين الطلبات، وفكّر باستخدام curl_cffi لمطابقة بصمة TLS إذا كنت تعمل على نطاق كبير.
جدران تسجيل الدخول في الرفوف وصفحات القوائم
بعض صفحات الرفوف والقوائم في Goodreads تتطلب المصادقة للوصول إلى المحتوى الكامل، خصوصًا بعد الصفحة 5. استخدم requests.Session() مع الكوكيز المصدَّرة من متصفحك، أو استخدم Selenium/Playwright مع ملف شخصي مسجّل الدخول. Thunderbit يتعامل مع هذا بشكل طبيعي لأنه يعمل داخل متصفح Chrome الخاص بك وأنت مسجّل الدخول.
قبل أن تبدأ
- مستوى الصعوبة: متوسط (يفترض معرفة أساسية بـ Python)
- الوقت المطلوب: حوالي 20-30 دقيقة للشرح الكامل
- ما ستحتاج إليه:
- Python 3.8 أو أحدث
- متصفح Chrome (لفحص DevTools ولـ Selenium/Playwright)
- المكتبات:
requestsوbeautifulsoup4وseleniumأوplaywrightوpandas - (اختياري)
gspreadلتصدير Google Sheets - (اختياري) كبديل بدون كود

الخطوة 1: إعداد بيئة Python
ثبّت المكتبات المطلوبة. افتح الطرفية وشغّل:
1pip install requests beautifulsoup4 selenium pandas lxml
إذا كنت تفضّل Playwright (وهو الخيار الموصى به للمشاريع الجديدة):
1pip install playwright
2playwright install chromium
لتصدير Google Sheets (اختياري):
1pip install gspread oauth2client
تأكد أنك تستخدم Python 3.8 أو أعلى. يمكنك التحقق بالأمر python --version.
بعد التثبيت، لازم تقدر تستورد كل المكتبات بدون أخطاء. جرّب python -c "import requests, bs4, pandas; print('Ready')" للتأكد.
الخطوة 2: إرسال أول طلب مع الرؤوس الصحيحة
افتح صفحة تصنيف أو قائمة في Goodreads داخل المتصفح — مثل https://www.goodreads.com/list/show/1.Best_Books_Ever. والآن خلّنا نجلب الصفحة باستخدام Python.
1import requests
2from bs4 import BeautifulSoup
3> This paragraph contains content that cannot be parsed and has been skipped.
4url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
5response = requests.get(url, headers=headers, timeout=15)
6print(f"Status: {response.status_code}")
المفروض تشوف Status: 200. إذا طلع لك 403، راجع الرؤوس — لأن AWS WAF في Goodreads يتحقق من User-Agent واقعي ويرفض الطلبات البسيطة جدًا. الرؤوس أعلاه تحاكي جلسة Chrome حقيقية.
الخطوة 3: فحص الصفحة وتحديد المحددات المناسبة
افتح Chrome DevTools (F12) في صفحة قائمة Goodreads. انقر بزر الفأرة الأيمن على عنوان كتاب واختر "Inspect". راح تشوف بنية DOM الخاصة بكل عنصر كتاب.
في صفحات القوائم، كل كتاب عادة يكون داخل عنصر <tr> مع itemtype="http://schema.org/Book". وداخل هذا العنصر ستجد:
- العنوان:
a.bookTitle(نص الرابط هو العنوان، وhrefيعطيك رابط الكتاب) - المؤلف:
a.authorName - التقييم:
span.minirating(يحتوي على متوسط التقييم وعدد التقييمات) - صورة الغلاف:
imgداخل صف الكتاب
أما في صفحات تفاصيل الكتاب الفردية، فبدل ما تعتمد على محددات CSS المباشرة، انتقل إلى JSON-LD. Goodreads يضمّن بيانات منظمة داخل وسم <script type="application/ld+json"> الذي يتبع صيغة schema.org الخاصة بالكتب. هذا أثبت بكثير من أسماء الأصناف اللي Goodreads يغيّرها أحيانًا من دون تنبيه.
الخطوة 4: استخراج بيانات الكتب من صفحة قائمة واحدة
خلّنا نحلّل صفحة القائمة ونستخرج المعلومات الأساسية لكل كتاب:
1import requests
2from bs4 import BeautifulSoup
3> This paragraph contains content that cannot be parsed and has been skipped.
4url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
5response = requests.get(url, headers=headers, timeout=15)
6soup = BeautifulSoup(response.text, "lxml")
7books = []
8rows = soup.select('tr[itemtype="http://schema.org/Book"]')
9for row in rows:
10 title_tag = row.select_one("a.bookTitle")
11 author_tag = row.select_one("a.authorName")
12 rating_tag = row.select_one("span.minirating")
13 title = title_tag.get_text(strip=True) if title_tag else ""
14 book_url = "https://www.goodreads.com" + title_tag["href"] if title_tag else ""
15 author = author_tag.get_text(strip=True) if author_tag else ""
16 rating_text = rating_tag.get_text(strip=True) if rating_tag else ""
17> This paragraph contains content that cannot be parsed and has been skipped.
18print(f"Found {len(books)} books on page 1")
19for b in books[:3]:
20 print(b)
المفروض تشوف حوالي 100 كتاب في كل صفحة قائمة. كل إدخال بيكون فيه عنوان، ومؤلف، وسلسلة تقييم مثل "4.28 avg rating — 9,031,257 ratings"، ورابط لصفحة تفاصيل الكتاب.
الخطوة 5: استخراج الصفحات الفرعية للحصول على معلومات تفصيلية عن الكتاب
صفحة القائمة تعطيك الأساسيات، لكن البيانات المهمة فعلًا — مثل ISBN، والوصف الكامل، ووسوم التصنيف، وعدد الصفحات، وتاريخ النشر — موجودة في صفحة كل كتاب على حدة. وهنا يجي دور JSON-LD.
1import json
2import time
3def scrape_book_detail(book_url, headers):
4 """زيارة صفحة كتاب واحدة واستخراج البيانات الوصفية التفصيلية عبر JSON-LD."""
5 resp = requests.get(book_url, headers=headers, timeout=15)
6 if resp.status_code != 200:
7 return {}
8> This paragraph contains content that cannot be parsed and has been skipped.
9 data = json.loads(script.string)
10 agg = data.get("aggregateRating", {})
11 # وسوم التصنيف ليست داخل JSON-LD؛ لذلك نستخدم HTML كخيار احتياطي
12 genres = [g.get_text(strip=True) for g in soup.select('span.BookPageMetadataSection__genreButton a span')]
13> This paragraph contains content that cannot be parsed and has been skipped.
14# مثال: إثراء أول 3 كتب
15for book in books[:3]:
16 details = scrape_book_detail(book["book_url"], headers)
17 book.update(details)
18 print(f"Scraped: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
19 time.sleep(4) # احترام حدود المعدل
أضف time.sleep() لمدة 3 إلى 8 ثوانٍ بين الطلبات. Goodreads يبدأ يفعل تحديد المعدل عند حوالي 20 إلى 30 طلبًا في الدقيقة من نفس عنوان IP، وبتبدأ تشوف أخطاء 403 أو CAPTCHA إذا استعجلت أكثر من اللازم.
هذا الأسلوب ذو المرحلتين — أولًا جمع روابط الكتب من صفحات القائمة، ثم زيارة كل صفحة تفاصيل على حدة — أكثر موثوقية وأسهل في الاستئناف إذا انقطع التنفيذ. وهو الاستراتيجية اللي تعتمدها أغلب أدوات استخراج Goodreads الناجحة.
ملاحظة جانبية: يقدر يسوي هذا تلقائيًا عبر استخراج الصفحات الفرعية. يزور الذكاء الاصطناعي صفحة تفاصيل كل كتاب ويثري جدولك بـ ISBN والوصف والتصنيفات والمزيد — بدون كود، بدون حلقات، وبدون مؤقتات انتظار.
الخطوة 6: التعامل مع المحتوى المحمّل عبر JavaScript باستخدام Selenium
بالنسبة للصفحات التي يُحمّل فيها المحتوى المطلوب عبر JavaScript — مثل المراجعات، وتفاصيل التقييم، وأقسام "المزيد من التفاصيل" — تحتاج أداة أتمتة للمتصفح. هذا مثال باستخدام Selenium:
1from selenium import webdriver
2from selenium.webdriver.chrome.options import Options
3from selenium.webdriver.common.by import By
4from selenium.webdriver.support.ui import WebDriverWait
5from selenium.webdriver.support import expected_conditions as EC
6options = Options()
7options.add_argument("--headless=new")
8options.add_argument("--disable-blink-features=AutomationControlled")
9options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
10 "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
11driver = webdriver.Chrome(options=options)
12driver.get("https://www.goodreads.com/book/show/5907.The_Hobbit")
13# انتظار تحميل المراجعات
14try:
15 WebDriverWait(driver, 10).until(
16 EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
17 )
18except:
19 print("لم تُحمّل المراجعات — قد تتطلب الصفحة تسجيل دخول أو انتهت المهلة")
20# الآن حلّل الصفحة بعد اكتمال تحميلها
21page_source = driver.page_source
22soup = BeautifulSoup(page_source, "lxml")
23> This paragraph contains content that cannot be parsed and has been skipped.
24driver.quit()
متى تستخدم Selenium ومتى تستخدم requests:
- استخدم
requests+ BeautifulSoup لبيانات الكتب الوصفية (JSON-LD)، وصفحات القوائم، وصفحات الرفوف (الصفحة 1)، وبيانات Choice Awards - استخدم Selenium أو Playwright للمراجعات، وتفاصيل توزيع التقييمات، وأي محتوى ما يظهر في HTML الخام
Playwright غالبًا هو الخيار الأفضل للمشاريع الجديدة — أسرع، أقل استهلاكًا للذاكرة، وإعدادات التخفي فيه أفضل. لكن Selenium عنده مجتمع أكبر ومزيد من الأمثلة الجاهزة الخاصة بـ Goodreads.
ترقيم الصفحات بشكل يعمل فعلًا: استخراج قوائم Goodreads كاملة
ترقيم الصفحات هو أكثر نقطة فشل شائعة في أدوات استخراج Goodreads، وما لقيت شرح منافس واحد يغطيه بشكل صحيح. هذه هي الطريقة السليمة.
كيف تعمل روابط ترقيم الصفحات في Goodreads
يستخدم Goodreads معاملًا بسيطًا ?page=N في أغلب الصفحات المرقمة:
- القوائم:
https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2 - الرفوف:
https://www.goodreads.com/shelf/show/thriller?page=2 - البحث:
https://www.goodreads.com/search?q=fantasy&page=2
كل صفحة قائمة تعرض عادةً 100 كتاب، بينما تعرض الرفوف 50 كتابًا لكل صفحة.
كتابة حلقة ترقيم صفحات تعرف متى تتوقف
1import time
2all_books = []
3base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
4for page_num in range(1, 50): # حد أمان: 50 صفحة
5 url = f"{base_url}?page={page_num}"
6 resp = requests.get(url, headers=headers, timeout=15)
7 if resp.status_code != 200:
8 print(f"Page {page_num}: got status {resp.status_code}, stopping.")
9 break
10 soup = BeautifulSoup(resp.text, "lxml")
11 rows = soup.select('tr[itemtype="http://schema.org/Book"]')
12 if not rows:
13 print(f"Page {page_num}: no books found, reached the end.")
14 break
15> This paragraph contains content that cannot be parsed and has been skipped.
16 print(f"Page {page_num}: scraped {len(rows)} books (total: {len(all_books)})")
17 time.sleep(5) # تأخير 5 ثوانٍ بين الصفحات
18print(f"\nDone. Total books collected: {len(all_books)}")
تكتشف الصفحة الأخيرة إما بالتحقق من أن قائمة النتائج فارغة (ما فيه عناصر tr[itemtype="http://schema.org/Book"]) أو عبر غياب رابط "التالي" (a.next_page).
حالة خاصة: مطلوب تسجيل دخول بعد الصفحة 5
هذا هو الفخ اللي يقع فيه أغلب الناس: بعض صفحات الرفوف والقوائم في Goodreads ترجع بصمت محتوى الصفحة الأولى عندما تطلب الصفحة 6 أو ما بعدها من دون مصادقة. لا يظهر خطأ ولا تحويل — فقط نفس البيانات تتكرر.
لإصلاح هذا، صدّر Cookie _session_id2 من متصفحك (باستخدام إضافة لتصدير الكوكيز أو من Chrome DevTools > Application > Cookies) وأضفه إلى طلباتك:
1session = requests.Session()
2session.headers.update(headers)
3session.cookies.set("_session_id2", "YOUR_SESSION_COOKIE_VALUE_HERE", domain=".goodreads.com")
4# استخدم session.get() بدل requests.get()
5resp = session.get(f"{base_url}?page=6", timeout=15)
Thunderbit يتعامل مع التصفح القائم على النقر أو التمرير اللانهائي بشكل طبيعي، من دون أي كود ومن دون إدارة للكوكيز. إذا كانت منطقية الترقيم عندك تتعطل باستمرار، ففعلاً يستحق التجربة.
السكربت الكامل الجاهز للنسخ واللصق
إليك السكربت الكامل الموحّد. يتعامل مع الرؤوس، وترقيم الصفحات، واستخراج الصفحات الفرعية عبر JSON-LD، وتحديد المعدل، وتصدير CSV. اختبرناه على صفحات Goodreads الحية حتى منتصف 2025.
1"""
2goodreads_scraper.py — استخراج قائمة Goodreads مع ترقيم الصفحات وإثراء تفاصيل الكتب.
3الاستخدام: python goodreads_scraper.py
4المخرجات: goodreads_books.csv
5"""
6> This paragraph contains content that cannot be parsed and has been skipped.
7> This paragraph contains content that cannot be parsed and has been skipped.
8> This paragraph contains content that cannot be parsed and has been skipped.
9> This paragraph contains content that cannot be parsed and has been skipped.
10def main():
11 all_books = []
12 # --- المرحلة 1: جمع روابط الكتب من صفحات القوائم ---
13 for page in range(1, MAX_PAGES + 1):
14 url = f"{BASE_URL}?page={page}"
15 page_books = scrape_listing_page(url)
16 if not page_books:
17 print(f"Page {page}: empty — stopping pagination.")
18 break
19 all_books.extend(page_books)
20 print(f"Page {page}: {len(page_books)} books (total: {len(all_books)})")
21 time.sleep(DELAY_LISTING)
22 # --- المرحلة 2: إثراء كل كتاب ببيانات صفحة التفاصيل ---
23 for i, book in enumerate(all_books):
24 details = scrape_book_detail(book["book_url"])
25 book.update(details)
26 print(f"[{i+1}/{len(all_books)}] {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
27 time.sleep(DELAY_DETAIL)
28 # --- التصدير إلى CSV ---
29 if all_books:
30 fieldnames = list(all_books[0].keys())
31 with open(OUTPUT_FILE, "w", newline="", encoding="utf-8") as f:
32 writer = csv.DictWriter(f, fieldnames=fieldnames)
33 writer.writeheader()
34 writer.writerows(all_books)
35 print(f"\nSaved {len(all_books)} books to {OUTPUT_FILE}")
36 else:
37 print("No books scraped.")
38if __name__ == "__main__":
39 main()
مع MAX_PAGES = 3، يجمع هذا السكربت حوالي 300 كتاب من قائمة "Best Books Ever"، ثم يزور صفحة تفاصيل كل كتاب، ويكتب كل شيء إلى ملف CSV. على جهازي يستغرق تقريبًا 25 دقيقة (وأغلب الوقت بسبب تأخير 4 ثوانٍ بين طلبات صفحات التفاصيل). ملف CSV الناتج بيحتوي على أعمدة مثل title وauthor وbook_url وisbn وpages وavg_rating وrating_count وreview_count وdescription وgenres وlanguage وformat وpublished.
التصدير بما هو أبعد من CSV: Google Sheets باستخدام gspread
إذا كنت تريد البيانات داخل Google Sheets بدلًا من CSV أو بالإضافة إليه، أضف هذا بعد التصدير إلى CSV:
1import gspread
2from oauth2client.service_account import ServiceAccountCredentials
3scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
4creds = ServiceAccountCredentials.from_json_keyfile_name("credentials.json", scope)
5client = gspread.authorize(creds)
6sheet = client.open("Goodreads Scrape").sheet1
7header = list(all_books[0].keys())
8sheet.append_row(header)
9for book in all_books:
10 sheet.append_row([str(book.get(k, "")) for k in header])
11print("Data pushed to Google Sheets.")
بتحتاج حساب خدمة في Google Cloud مع تفعيل Sheets API وDrive API. يشرح الإعداد خلال تقريبًا 5 دقائق. واستخدم عمليات الدُفعات (append_rows() مع قائمة من القوائم) إذا كنت تدفع أكثر من بضع مئات من الصفوف — لأن Google يفرض حدًا أقصى 300 طلب لكل 60 ثانية لكل مشروع.
وطبعًا إذا حسيت أن الإعداد كله أكبر من اللازم، فإن Thunderbit يصدّر إلى Google Sheets وAirtable وNotion وExcel وCSV وJSON بنقرة واحدة — من دون إعداد مكتبات، ومن دون ملف اعتماد، ومن دون حصص API.
البديل بدون كود: استخراج Goodreads باستخدام Thunderbit
مو كل الناس تبي تصون سكربت Python. يمكن أنت ناشر تحتاج تحليلًا سريعًا لمرة واحدة، أو مدونة كتب تبي بس جدولًا بأفضل الكتب مبيعًا هذا العام. لهذا السبب صممنا Thunderbit.
كيفية استخراج Goodreads باستخدام Thunderbit
- ثبّت إضافة Thunderbit Chrome من وروح إلى صفحة قائمة أو رف أو نتائج بحث في Goodreads.
- انقر على "AI Suggest Fields" في الشريط الجانبي لـ Thunderbit. الذكاء الاصطناعي يقرأ الصفحة ويقترح الأعمدة — عادةً العنوان، والمؤلف، والتقييم، ورابط صورة الغلاف، ورابط الكتاب.
- انقر على "Scrape" — يتم استخراج البيانات إلى جدول منظم خلال ثوانٍ.
- صدّر إلى Google Sheets أو Excel أو Airtable أو Notion أو CSV أو JSON.
وللحصول على بيانات تفصيلية عن الكتب (مثل ISBN، والوصف، والتصنيفات، وعدد الصفحات)، فإن ميزة استخراج الصفحات الفرعية في Thunderbit تزور صفحة تفاصيل كل كتاب وتثري الجدول تلقائيًا — بلا حلقات، بلا مؤقتات، بلا تصحيح أخطاء.
كما أن Thunderbit يتعامل مع القوائم المرقمة تلقائيًا. فقط تقول له ينقر "Next" أو يمرر الصفحة، وهو يجمع البيانات عبر جميع الصفحات بدون أي كود.
المعادلة بسيطة: سكربت Python يعطيك تحكم كامل وهو شبه مجاني (غير وقتك)، بينما Thunderbit يتنازل قليلًا عن المرونة مقابل توفير كبير في الوقت ومن دون صيانة. بالنسبة لقائمة من 300 كتاب، يستغرق سكربت Python نحو 25 دقيقة تشغيلًا، بالإضافة إلى الوقت اللي قضيته في الكتابة والتصحيح. أما Thunderbit فيجيب نفس البيانات في حوالي 3 دقائق، وبنقرتين فقط.
استخراج Goodreads بمسؤولية: robots.txt، وشروط الاستخدام، والأخلاقيات
هذه نقطة تستحق جوابًا واضحًا، مو مجرد فقرة تحذيرية عابرة.
ماذا تقول robots.txt الخاصة بـ Goodreads فعلًا
ملف robots.txt المباشر في Goodreads واضح بشكل لافت. صفحات تفاصيل الكتب (/book/show/)، والقوائم العامة (/list/show/)، والرفوف العامة (/shelf/show/)، وصفحات المؤلفين (/author/show/) غير محجوبة. أما المحجوب فهو: /api، و/book/reviews/، و/review/list، و/review/show، و/search، ومسارات أخرى عدة. كما أن GPTBot وCCBot (Common Crawl) محجوبان بالكامل عبر Disallow: /. ويوجد توجيه Crawl-delay: 5 لـ bingbot، لكن ما فيه تأخير عالمي مفروض على الجميع.
شروط استخدام Goodreads ببساطة
شروط الاستخدام (تمت مراجعتها آخر مرة في 28 أبريل 2021) تمنع "أي استخدام لتقنيات التنقيب عن البيانات، أو الروبوتات، أو أدوات جمع واستخراج البيانات المشابهة". هذا نص واسع، ولازم يؤخذ بجدية — لكن المحاكم اعتبرت باستمرار أن مخالفة شروط الاستخدام وحدها لا تُعد "وصولًا غير مصرح به" جنائيًا. فقد قضى حكم بأن "تجريم انتهاكات شروط الاستخدام قد يحوّل كل موقع إلى ولاية جنائية مستقلة".
أفضل الممارسات
- أبطئ الطلبات: 3-8 ثوانٍ بين الطلبات (كما تقترح Goodreads نفسها 5 ثوانٍ للبرمجيات الزاحفة)
- ابقَ تحت 5,000 طلب يوميًا من عنوان IP واحد
- استخراج الصفحات العامة فقط — وتجنب استخراج بيانات لا تظهر إلا بعد تسجيل الدخول بشكل جماعي
- لا تعيد توزيع نصوص المراجعات الخام تجاريًا — فالمراجعات أعمال إبداعية محمية بحقوق النشر
- احتفظ فقط بما تحتاجه وضع جدولًا زمنيًا للاحتفاظ بالبيانات
- البحث الشخصي مقابل الاستخدام التجاري: استخراج البيانات العامة للتحليل الشخصي أو البحث الأكاديمي مقبول عمومًا. أما إعادة التوزيع التجاري فهي اللي ترفع مستوى المخاطر القانونية.
استخدام أداة مثل Thunderbit (التي تعمل من خلال جلسة متصفحك) يجعل التفاعل مرئيًا ومشابهًا تمامًا للتصفح العادي، لكن المبادئ الأخلاقية نفسها تنطبق مهما كانت أداتك. وإذا أردت تتعمق أكثر في ، فقد تناولنا هذا الموضوع بشكل منفصل.
نصائح ومزالق شائعة
نصيحة: ابدأ دائمًا بـ JSON-LD. قبل ما تكتب محددات CSS معقدة، تحقق إذا كانت البيانات اللي تحتاجها موجودة داخل وسم <script type="application/ld+json">. فهو أكثر ثباتًا، وأسهل في التحليل، وأقل عرضة للكسر عندما Goodreads يحدّث الواجهة الأمامية.
نصيحة: استخدم استراتيجية المرحلتين. اجمع أولًا كل روابط الكتب من صفحات القوائم، ثم زر كل صفحة تفاصيل. هذا يجعل الأداة أسهل للاستئناف إذا تعطلت في منتصف الطريق، وتقدر تحفظ قائمة الروابط على القرص كنقطة استعادة.
مأزق: نسيان معالجة الحقول المفقودة. مو كل صفحة كتاب فيها ISBN أو وسوم تصنيف أو وصف. استخدم دائمًا .get() مع قيمة افتراضية، أو لفّ المحددات داخل شروط if. خطأ واحد من نوع NoneType ممكن يوقف تشغيل استخراج يستمر ثلاث ساعات.
مأزق: السرعة الزائدة. أعرف أنه مغري تضبط time.sleep(0.5) وتنطلق بسرعة. لكن Goodreads سيبدأ يرجع 403 بعد تقريبًا 20-30 طلبًا متتاليًا بسرعة، وبعد ما تُصنَّف كمشتبه به قد تضطر تنتظر ساعات أو تغيّر عنوان IP. التأخير بين 4 و5 ثوانٍ هو النقطة الأفضل غالبًا.
مأزق: الثقة في الشروحات القديمة. إذا كان الدليل يذكر Goodreads API، أو يستخدم أسماء أصناف مثل .field.value أو #bookTitle, فهو غالبًا قديم. تحقق دائمًا من المحددات على الصفحة الحية قبل بناء الأداة.
لمزيد من الإرشادات حول اختيار الأدوات والأطر المناسبة للاستخراج، راجع أدلتنا حول و.
الخلاصة وأهم النقاط
استخراج بيانات Goodreads باستخدام Python ممكن تمامًا — بس لازم تعرف وين الفخاخ. الخلاصة السريعة:
- واجهة Goodreads API انتهت (منذ ديسمبر 2020). الاستخراج من الموقع هو الطريقة الأساسية للحصول على بيانات الكتب المنظمة من المنصة.
- النتائج الفارغة سببها غالبًا محتوى يُحمّل عبر JavaScript، أو محددات قديمة، أو غياب الرؤوس، أو مشاكل المصادقة أثناء الترقيم — وليس بالضرورة أن الكود خاطئ.
- JSON-LD هو أفضل صديق لك لبيانات الكتب الوصفية. فهو ثابت ومنظم ونادرًا ما يتغير.
- ترقيم الصفحات يتطلب مصادقة في كثير من صفحات الرفوف والقوائم بعد الصفحة 5. أضف Cookie
_session_id2. - تحديد المعدل حقيقي. استخدم تأخيرًا من 3 إلى 8 ثوانٍ وابقَ تحت 5,000 طلب يوميًا.
- استراتيجية المرحلتين (جمع الروابط أولًا، ثم استخراج صفحات التفاصيل) أكثر موثوقية ويمكن استئنافها.
- لغير المبرمجين (أو لأي شخص يقدّر وقته)، يتولى كل هذا — عرض JavaScript، وترقيم الصفحات، وإثراء الصفحات الفرعية، والتصدير — خلال تقريبًا نقرَتين.
استخرج بمسؤولية، واحترم robots.txt، ونتمنى أن ترجع بيانات كتبك دائمًا بأكثر من [].
الأسئلة الشائعة
هل لا يزال يمكن استخدام Goodreads API؟
لا. Goodreads أوقفت الـ API العامة في ديسمبر 2020 ولم تعد تصدر مفاتيح مطورين جديدة. كما تم تعطيل المفاتيح غير النشطة لمدة 30 يومًا تلقائيًا. حاليًا، استخراج البيانات من الويب أو استخدام APIs بديلة مثل Open Library أو Google Books هو الخيار المتاح للوصول إلى بيانات الكتب برمجيًا.
لماذا تعيد أداة استخراج Goodreads نتائج فارغة؟
السبب الأكثر شيوعًا هو المحتوى المحمّل عبر JavaScript. Goodreads تُحمّل المراجعات، وتوزيعات التقييم، وكثير من أقسام التفاصيل عبر React/JavaScript، وهو ما لا تراه استدعاءات requests.get() البسيطة. استخدم Selenium أو Playwright لهذه الصفحات. من الأسباب الأخرى: محددات CSS قديمة (لأن Goodreads غيّرت HTML)، أو غياب User-Agent (ما يسبب حظر 403)، أو الطلبات غير الموثقة على صفحات الرفوف المرقمة.
هل من القانوني استخراج بيانات Goodreads؟
استخراج البيانات العامة المتاحة للجمهور لأغراض شخصية أو بحثية يُعد مقبولًا عمومًا وفق السوابق القانونية الحالية (hiQ v. LinkedIn، وMeta v. Bright Data). لكن شروط استخدام Goodreads تمنع جمع البيانات آليًا، ولازم دائمًا تراجع robots.txt. تجنب إعادة توزيع نصوص المراجعات المحمية تجاريًا، وقلّل حجم الطلبات حتى لا ترهق موارد الموقع.
كيف أستخرج عدة صفحات على Goodreads؟
أضف ?page=N إلى رابط الرف أو القائمة وكرر عبر أرقام الصفحات. تحقّق من فراغ النتائج أو من غياب رابط "next" لاكتشاف الصفحة الأخيرة. مهم: بعض صفحات الرفوف تتطلب مصادقة (Cookie _session_id2) لإرجاع النتائج بعد الصفحة 5 — وبدونها ستتكرر لك بيانات الصفحة الأولى بصمت.
هل يمكنني استخراج Goodreads دون كتابة كود؟
نعم. هو إضافة Chrome تتيح لك استخراج Goodreads خلال نقرَتين — يقترح الذكاء الاصطناعي الحقول، ثم تنقر "Scrape" وتصدّر مباشرة إلى Google Sheets أو Excel أو Airtable أو Notion. وهو يتعامل تلقائيًا مع المحتوى المحمّل عبر JavaScript، وترقيم الصفحات، وإثراء الصفحات الفرعية، بدون الحاجة إلى Python أو أي برمجة.
اعرف المزيد