מעט מאוד דברים מאכזבים יותר מלכתוב 30 שורות Python, להריץ את ה‑Goodreads scraper שלך, ולראות שהוא מחזיר []. רשימה ריקה. כלום. רק אתה והסמן המהבהב.
ראיתי את זה קורה עשרות פעמים — בניסויים הפנימיים שלנו ב-, בפורומים של מפתחים, ובבעיות GitHub שמצטברות במאגרי scraper נטושים. התלונות כמעט תמיד זהות: "קטע הביקורות המובילות ריק, זה רק מראה לי []", "לא משנה איזה מספר עמוד אני מריץ, זה תמיד מגרד את העמוד הראשון", "הקוד עבד לי בשנה שעברה, ועכשיו הוא שבור." ולרע המצב, Goodreads API הוצא משימוש בדצמבר 2020, כך שהעצה של "פשוט תשתמשו ב‑API" שתמצאו במדריכים ישנים היא מבוי סתום.
אם אתם רוצים היום נתונים מובנים על ספרים מ‑Goodreads — כותרות, מחברים, דירוגים, ביקורות, ז'אנרים, ISBN — גרידה היא הדרך המרכזית. המדריך הזה יוביל אתכם דרך גישה עובדת ומלאה לגרידת Goodreads עם Python, כולל תוכן שמועלה ב‑JS, עימוד, מנגנוני מניעה מחסימה וייצוא. ואם 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 רשומות — הוכחה שלנתונים האלה יש ערך שוק ממשי.
ה‑API של Goodreads כבר לא כאן — הנה מה שמחליף אותו
אם חיפשתם לאחרונה "Goodreads API", כנראה הגעתם למדריך שכבר לא מעודכן. ב־8 בדצמבר 2020 Goodreads הפסיקה בשקט להנפיק מפתחות API חדשים למפתחים. לא הייתה הודעה בבלוג, לא אימייל המוני — רק באנר קטן בעמוד התיעוד והרבה מפתחים מבולבלים.

ההשפעה הייתה מיידית. מפתח אחד, Kyle K, בנה בוט Discord לשיתוף המלצות ספרים — "פתאום פוף, זה פשוט הפסיק לעבוד." אחר, Matthew Jones, איבד גישה ל‑API שבוע לפני ההצבעה ל‑Reddit r/Fantasy Stabby Awards, ונאלץ לחזור ל‑Google Forms. סטודנטית לתואר שני בשם Elena Neacsu מצאה את פרויקט התזה שלה נתקע באמצע הפיתוח.
אז מה נשאר? כך נראית המפה כיום:
| גישה | נתונים זמינים | קלות שימוש | מגבלות קצב | מצב |
|---|---|---|---|---|
| Goodreads API | מטא-דאטה מלא, ביקורות | קל (בעבר) | בקשה אחת לשנייה | הוצא משימוש (דצמ' 2020) — אין מפתחות חדשים |
| Open Library API | כותרות, מחברים, ISBN, עטיפות (~30 מיליון כותרים) | קל | 1-3 בקשות לשנייה | פעיל, חינמי, בלי אימות |
| Google Books API | מטא-דאטה, תצוגות מקדימות | קל | 1,000 ביום בחינם | פעיל (עם חוסרים ב‑ISBN לשפות שאינן אנגלית) |
| גרידה ב‑Python (requests + BS4) | כל מה שנמצא ב‑HTML הראשוני | בינוני | מנוהל על ידכם | עובד לתוכן סטטי |
| גרידה ב‑Python (Selenium/Playwright) | גם תוכן שמועלה ב‑JS | קשה יותר | מנוהל על ידכם | נדרש לביקורות ולחלק מהרשימות |
| Thunderbit (תוסף Chrome ללא קוד) | כל נתון גלוי בדף | קל מאוד (2 הקלקות) | מבוסס קרדיטים | פעיל — בלי צורך ב‑Python |
Open Library היא השלמה חזקה, במיוחד לחיפושי ISBN ומטא-דאטה בסיסי. אבל אם אתם צריכים דירוגים, ביקורות, תגיות ז'אנר או ספירות "Want to Read", אתם מגרדים את Goodreads ישירות — או עם Python, או עם כלי כמו Thunderbit, שיכול לגרד דפי Goodreads (כולל תתי-עמודים של פרטי ספר) עם שדות מוצעים על ידי AI וייצוא ישיר ל‑Google Sheets, Notion או Airtable.
למה ה‑Python scraper שלכם ב‑Goodreads מחזיר תוצאות ריקות, ואיך מתקנים את זה
זה הסעיף שהייתי שמח שהיה קיים כשהתחלתי לעבוד עם נתוני Goodreads. בעיית ה"תוצאות הריקות" היא התלונה הכי נפוצה בפורומי מפתחים, ויש לה כמה סיבות שונות — ולכל אחת פתרון משלה.
| תסמין | סיבה שורשית | תיקון |
|---|---|---|
ביקורות/דירוגים מחזירים [] | תוכן שמועלה ב‑JS (React / טעינה עצלה) | להשתמש ב‑Selenium או Playwright במקום ב‑requests |
| תמיד מגרד רק את עמוד 1 | פרמטרי עימוד לא נקלטו או שהכול מונע ב‑JS | להעביר נכון את פרמטר ?page=N; להשתמש באוטומציה בדפדפן ל‑infinite scroll |
| הקוד עבד בשנה שעברה, ועכשיו נכשל | Goodreads שינתה שמות מחלקות ב‑HTML | להשתמש בבוררים עמידים יותר (JSON-LD, מאפייני data-testid) |
| שגיאות 403 / חסימה אחרי כמה בקשות | חסרים headers / בקשות מהירות מדי | להוסיף User-Agent, time.sleep(), לסובב פרוקסים |
| קיר התחברות בדפי מדפים ורשימות | נדרש cookie / session | להשתמש ב‑requests.Session() עם cookies או בגרידה דרך דפדפן |
תוכן שמועלה ב‑JS: ביקורות ודירוגים מופיעים כריק
Goodreads מפעילה frontend מבוסס React. כשאתם עושים requests.get() לדף ספר, אתם מקבלים את ה‑HTML הראשוני — אבל ביקורות, התפלגות דירוגים והרבה אזורי "מידע נוסף" נטענים אסינכרונית דרך JavaScript. בפועל, הסקרייפר שלכם פשוט לא רואה אותם.
הפתרון: בכל דף שבו צריך תוכן שמועלה ב‑JS, עברו ל‑Selenium או Playwright. ההמלצה שלי לפרויקטים חדשים היא Playwright — הוא בזכות פרוטוקול מבוסס WebSocket, ויש לו תמיכה טובה יותר ב‑stealth וב‑async.

עימוד שמחזיר רק את עמוד 1
זה טריקי. כותבים לולאה, מעלים את ?page=N, ועדיין מקבלים את אותן תוצאות בכל פעם. ב‑Goodreads, דפי מדפים מחזירים בשקט תוכן של עמוד 1 בלי קשר ל‑?page= אם אינכם מחוברים. בלי שגיאה, בלי הפניה — פשוט אותו עמוד ראשון שוב ושוב.
הפתרון: לכלול cookie של session מאומת (במיוחד _session_id2). עוד על כך בסעיף העימוד בהמשך.
קוד שעבד בשנה שעברה עכשיו נכשל
Goodreads משנה מדי פעם שמות מחלקות ומבנה דפים. למאגר הפופולרי maria-antoniak/goodreads-scraper ב‑GitHub יש עכשיו הודעה קבועה: "This project is unmaintained and no longer functioning." הפתרון הוא להשתמש בבוררים עמידים יותר — נתונים מובנים מסוג JSON-LD (שעומדים בסטנדרט schema.org וכמעט לא משתנים) או מאפייני data-testid במקום שמות מחלקות שבירים.
שגיאות 403 או חסימות
לספריית requests של Python יש טביעת TLS שונה מזו של Chrome. גם עם מחרוזת User-Agent של Chrome, מערכות זיהוי בוטים כמו AWS WAF (ש‑Goodreads משתמשת בה, בהיותה חברה בת של Amazon) יכולות לזהות את אי-ההתאמה. הפתרון: להוסיף headers ריאליסטיים של דפדפן, להכניס השהיות של 3-8 שניות בין בקשות, ואם אתם מגרדים בהיקף גדול — לשקול curl_cffi להתאמת טביעת TLS.
קירות התחברות בדפי מדפים ורשימות
חלק מדפי המדפים והרשימות של Goodreads דורשים אימות כדי לגשת לתוכן מלא, במיוחד מעבר לעמוד 5. השתמשו ב‑requests.Session() עם cookies שיוצאו מהדפדפן, או ב‑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: שליחת הבקשה הראשונה עם headers נכונים
פתחו בדפדפן דף ז'אנר או רשימה ב‑Goodreads — למשל, https://www.goodreads.com/list/show/1.Best_Books_Ever. עכשיו נביא את הדף הזה עם Python.
1import requests
2from bs4 import BeautifulSoup
3headers = {
4 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
5 "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
6 "Accept-Language": "en-US,en;q=0.9",
7 "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
8}
9url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
10response = requests.get(url, headers=headers, timeout=15)
11print(f"Status: {response.status_code}")
אתם אמורים לראות Status: 200. אם מתקבל 403, בדקו שוב את ה‑headers — AWS WAF של Goodreads בודק User-Agent ריאלי וידחה בקשות בסיסיות מדי. ה‑headers למעלה מחקים סשן אמיתי של 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"> שעומדת בפורמט Book של schema.org. זה הרבה יותר יציב משמות מחלקות, ש‑Goodreads נוהגת לשנות בקלות.
שלב 4: חילוץ נתוני ספר מדף רשימה אחד
ננתח את דף הרשימה ונחלץ מידע בסיסי לכל ספר:
1import requests
2from bs4 import BeautifulSoup
3headers = {
4 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
5 "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
6 "Accept-Language": "en-US,en;q=0.9",
7}
8url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
9response = requests.get(url, headers=headers, timeout=15)
10soup = BeautifulSoup(response.text, "lxml")
11books = []
12rows = soup.select('tr[itemtype="http://schema.org/Book"]')
13for row in rows:
14 title_tag = row.select_one("a.bookTitle")
15 author_tag = row.select_one("a.authorName")
16 rating_tag = row.select_one("span.minirating")
17 title = title_tag.get_text(strip=True) if title_tag else ""
18 book_url = "https://www.goodreads.com" + title_tag["href"] if title_tag else ""
19 author = author_tag.get_text(strip=True) if author_tag else ""
20 rating_text = rating_tag.get_text(strip=True) if rating_tag else ""
21 books.append({
22 "title": title,
23 "author": author,
24 "rating_info": rating_text,
25 "book_url": book_url,
26 })
27print(f"Found {len(books)} books on page 1")
28for b in books[:3]:
29 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 """Visit a single book page and extract detailed metadata via JSON-LD."""
5 resp = requests.get(book_url, headers=headers, timeout=15)
6 if resp.status_code != 200:
7 return {}
8 soup = BeautifulSoup(resp.text, "lxml")
9 script = soup.find("script", {"type": "application/ld+json"})
10 if not script:
11 return {}
12 data = json.loads(script.string)
13 agg = data.get("aggregateRating", {})
14 # תגיות ז'אנר לא נמצאות ב-JSON-LD; לכן נשתמש בגיבוי מתוך ה-HTML
15 genres = [g.get_text(strip=True) for g in soup.select('span.BookPageMetadataSection__genreButton a span')]
16 return {
17 "isbn": data.get("isbn", ""),
18 "pages": data.get("numberOfPages", ""),
19 "language": data.get("inLanguage", ""),
20 "format": data.get("bookFormat", ""),
21 "avg_rating": agg.get("ratingValue", ""),
22 "rating_count": agg.get("ratingCount", ""),
23 "review_count": agg.get("reviewCount", ""),
24 "description": data.get("description", "")[:200], # קיצור לתצוגה מקדימה
25 "genres": ", ".join(genres[:5]),
26 }
27# דוגמה: העשרה של 3 הספרים הראשונים
28for book in books[:3]:
29 details = scrape_book_detail(book["book_url"], headers)
30 book.update(details)
31 print(f"Scraped: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
32 time.sleep(4) # כיבוד מגבלות הקצב
הוסיפו time.sleep() של 3-8 שניות בין בקשות. Goodreads מתחילה להגביל בקצב בערך אחרי 20-30 בקשות בדקה מ‑IP יחיד, ותתחילו לראות 403s או CAPTCHA אם תעבדו מהר יותר.
הגישה הדו-שלבית הזו — קודם לאסוף את כל כתובות הספרים מדפי הרשימה, ואז לבקר בכל דף פרטים — אמינה יותר וקל יותר להמשיך ממנה אם התהליך נקטע. זו בדיוק האסטרטגיה שרוב ה‑scrapers המוצלחים של Goodreads משתמשים בה.
הערת צד: יכול לעשות את זה אוטומטית עם גרידת תתי-עמודים. ה‑AI מבקר בכל דף פרטי ספר ומעשיר את הטבלה שלכם ב‑ISBN, תיאור, ז'אנרים ועוד — בלי קוד, בלי לולאות, בלי טיימרי המתנה.
שלב 6: טיפול בתוכן שמועלה ב‑JavaScript עם Selenium
בדפים שבהם התוכן שאתם צריכים נטען דרך JavaScript — ביקורות, פירוט דירוגים, קטעי "more details" — תצטרכו כלי אוטומציה לדפדפן. הנה דוגמה עם 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("Reviews did not load — page may require login or JS timed out")
20# עכשיו מנתחים את העמוד המעובד במלואו
21page_source = driver.page_source
22soup = BeautifulSoup(page_source, "lxml")
23reviews = soup.select("article.ReviewCard")
24for rev in reviews[:3]:
25 text = rev.select_one("span.Formatted")
26 stars = rev.select_one("span.RatingStars")
27 print(f"Rating: {stars.get_text(strip=True) if stars else 'N/A'}")
28 print(f"Review: {text.get_text(strip=True)[:150] if text else 'N/A'}...")
29 print()
30driver.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 for row in rows:
16 title_tag = row.select_one("a.bookTitle")
17 author_tag = row.select_one("a.authorName")
18 title = title_tag.get_text(strip=True) if title_tag else ""
19 book_url = "https://www.goodreads.com" + title_tag["href"] if title_tag else ""
20 author = author_tag.get_text(strip=True) if author_tag else ""
21 all_books.append({"title": title, "author": author, "book_url": book_url})
22 print(f"Page {page_num}: scraped {len(rows)} books (total: {len(all_books)})")
23 time.sleep(5) # השהיה של 5 שניות בין עמודים
24print(f"\nDone. Total books collected: {len(all_books)}")
מזהים את העמוד האחרון על ידי בדיקה אם רשימת התוצאות ריקה (לא נמצאו אלמנטים מסוג tr[itemtype="http://schema.org/Book"]) או על ידי בדיקת היעדר קישור "next" (a.next_page).
מקרה קצה: נדרשת התחברות מעבר לעמוד 5
זה המלכוד שתופס כמעט את כולם: חלק מדפי המדפים והרשימות של Goodreads מחזירים בשקט תוכן של עמוד 1 כשמבקשים עמוד 6+ בלי אימות. בלי שגיאה, בלי הפניה — רק אותם נתונים שוב ושוב.
כדי לתקן זאת, ייצאו את ה‑cookie _session_id2 מהדפדפן שלכם (באמצעות תוסף לייצוא cookies או 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 מטפל באופן טבעי גם בעימוד שמבוסס קליקים וגם ב‑infinite scroll, בלי קוד ובלי ניהול cookies. אם לוגיקת העימוד שלכם ממשיכה להישבר, שווה לשקול אותו.
הסקריפט המלא, מוכן להדבקה
הנה הסקריפט המלא והמרוכז. הוא מטפל ב‑headers, עימוד, גרידת תתי-עמודים דרך JSON-LD, הגבלת קצב וייצוא ל‑CSV. בדקתי אותו מול דפי Goodreads חיים נכון לאמצע 2025.
1"""
2goodreads_scraper.py — Scrape a Goodreads list with pagination and book detail enrichment.
3Usage: python goodreads_scraper.py
4Output: goodreads_books.csv
5"""
6import csv, json, time, requests
7from bs4 import BeautifulSoup
8HEADERS = {
9 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
10 "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
11 "Accept-Language": "en-US,en;q=0.9",
12 "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
13}
14BASE_URL = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
15MAX_PAGES = 3 # התאימו לפי הצורך
16DELAY_LISTING = 5 # שניות בין דפי רשימה
17DELAY_DETAIL = 4 # שניות בין דפי פרטים
18OUTPUT_FILE = "goodreads_books.csv"
19def scrape_listing_page(url):
20 """מחזיר רשימת dicts עם title, author, book_url מדף רשימה אחד."""
21 resp = requests.get(url, headers=HEADERS, timeout=15)
22 if resp.status_code != 200:
23 return []
24 soup = BeautifulSoup(resp.text, "lxml")
25 rows = soup.select('tr[itemtype="http://schema.org/Book"]')
26 books = []
27 for row in rows:
28 t = row.select_one("a.bookTitle")
29 a = row.select_one("a.authorName")
30 if t:
31 books.append({
32 "title": t.get_text(strip=True),
33 "author": a.get_text(strip=True) if a else "",
34 "book_url": "https://www.goodreads.com" + t["href"],
35 })
36 return books
37def scrape_book_detail(book_url):
38 """מבקר בדף ספר ומחלץ מטא-דאטה דרך JSON-LD + גיבוי HTML."""
39 resp = requests.get(book_url, headers=HEADERS, timeout=15)
40 if resp.status_code != 200:
41 return {}
42 soup = BeautifulSoup(resp.text, "lxml")
43 script = soup.find("script", {"type": "application/ld+json"})
44 if not script:
45 return {}
46 data = json.loads(script.string)
47 agg = data.get("aggregateRating", {})
48 genres = [g.get_text(strip=True)
49 for g in soup.select("span.BookPageMetadataSection__genreButton a span")]
50 return {
51 "isbn": data.get("isbn", ""),
52 "pages": data.get("numberOfPages", ""),
53 "avg_rating": agg.get("ratingValue", ""),
54 "rating_count": agg.get("ratingCount", ""),
55 "review_count": agg.get("reviewCount", ""),
56 "description": (data.get("description", "") or "")[:300],
57 "genres": ", ".join(genres[:5]),
58 "language": data.get("inLanguage", ""),
59 "format": data.get("bookFormat", ""),
60 "published": data.get("datePublished", ""),
61 }
62def main():
63 all_books = []
64 # --- מעבר 1: איסוף כתובות ספרים מדפי רשימה ---
65 for page in range(1, MAX_PAGES + 1):
66 url = f"{BASE_URL}?page={page}"
67 page_books = scrape_listing_page(url)
68 if not page_books:
69 print(f"Page {page}: empty — stopping pagination.")
70 break
71 all_books.extend(page_books)
72 print(f"Page {page}: {len(page_books)} books (total: {len(all_books)})")
73 time.sleep(DELAY_LISTING)
74 # --- מעבר 2: העשרת כל ספר מנתוני דף הפרטים ---
75 for i, book in enumerate(all_books):
76 details = scrape_book_detail(book["book_url"])
77 book.update(details)
78 print(f"[{i+1}/{len(all_books)}] {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
79 time.sleep(DELAY_DETAIL)
80 # --- ייצוא ל-CSV ---
81 if all_books:
82 fieldnames = list(all_books[0].keys())
83 with open(OUTPUT_FILE, "w", newline="", encoding="utf-8") as f:
84 writer = csv.DictWriter(f, fieldnames=fieldnames)
85 writer.writeheader()
86 writer.writerows(all_books)
87 print(f"\nSaved {len(all_books)} books to {OUTPUT_FILE}")
88 else:
89 print("No books scraped.")
90if __name__ == "__main__":
91 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 עם APIs של Sheets ו‑Drive מופעלים. התיעוד של עובר על ההגדרה בכ־5 דקות. השתמשו בפעולות batch (append_rows() עם רשימת רשימות) אם אתם דוחפים יותר מכמה מאות שורות — מגבלת הקצב של Google היא 300 בקשות ל־60 שניות לכל פרויקט.
כמובן, אם כל ההקמה הזו מרגישה מוגזמת, Thunderbit מייצא ל‑Google Sheets, Airtable, Notion, Excel, CSV ו‑JSON ב- — בלי הגדרת ספריות, בלי קובץ credentials, בלי מכסות API.
החלופה ללא קוד: גרידת Goodreads עם Thunderbit
לא כולם רוצים לתחזק סקריפט ב‑Python. אולי אתם מוציא לאור שעושה ניתוח שוק חד-פעמי, או בלוגר ספרים שרוצה פשוט גיליון אלקטרוני של רבי-המכר של השנה. בדיוק בשביל זה בנינו את Thunderbit.
איך לגרד Goodreads עם Thunderbit
- התקינו את תוסף Thunderbit ל‑Chrome מ- ונווטו לדף רשימה, מדף או תוצאות חיפוש ב‑Goodreads.
- לחצו על "AI Suggest Fields" בסרגל הצד של Thunderbit. ה‑AI קורא את הדף ומציע עמודות — בדרך כלל כותרת, מחבר, דירוג, כתובת תמונת כריכה וקישור לספר.
- לחצו על "Scrape" — הנתונים נשלפים לטבלה מובנית בתוך שניות.
- ייצאו ל‑Google Sheets, Excel, Airtable, Notion, CSV או JSON.
עבור נתוני ספרים מפורטים יותר (ISBN, תיאור, ז'אנרים, מספר עמודים), תכונת ה‑subpage scraping של 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) אוסרים "כל שימוש בכלי data mining, robots או כלים דומים לאיסוף וחילוץ נתונים." זו לשון רחבה, וכדאי להתייחס אליה ברצינות — אבל בתי המשפט קבעו שוב ושוב שהפרת ToS בלבד אינה שקולה ל"גישה לא מורשית" פלילית. פסק הדין קבע ש"הפיכת הפרות תנאי שימוש לעבירה פלילית מסכנת להפוך כל אתר למחוז פלילי משלו".
שיטות עבודה מומלצות
- להאט בקשות: 3-8 שניות בין בקשות (ה‑robots.txt של Goodreads עצמה מציע 5 שניות לבוטים)
- להישאר מתחת ל־5,000 בקשות ביום מ‑IP יחיד
- לגרד רק דפים זמינים לציבור — להימנע מגרידה מסיבית של נתונים שדורשים התחברות
- לא להפיץ מחדש טקסט ביקורות גולמי לצרכים מסחריים — ביקורות הן יצירות מוגנות בזכויות יוצרים
- לשמור רק את מה שצריך ולקבוע מדיניות שימור נתונים
- מחקר אישי מול שימוש מסחרי: גרידה של נתונים ציבוריים לניתוח אישי או למחקר אקדמי מקובלת בדרך כלל. הפצה מסחרית היא המקום שבו הסיכון המשפטי עולה.
שימוש בכלי כמו Thunderbit (שמבצע גרידה דרך סשן הדפדפן שלכם) שומר על אינטראקציה שנראית חזותית כמו גלישה רגילה, אבל אותם עקרונות אתיים חלים בלי קשר לכלי שתבחרו. אם תרצו להעמיק ב-, כיסינו זאת בנפרד.
טיפים ומלכודות נפוצות
טיפ: תמיד להתחיל עם JSON-LD. לפני שכותבים בוררי CSS מורכבים, בדקו אם הנתונים שאתם צריכים נמצאים בתגית <script type="application/ld+json">. היא יציבה יותר, קלה יותר לניתוח, ופחות צפויה להישבר כש‑Goodreads מעדכנת את ה‑frontend שלה.
טיפ: השתמשו באסטרטגיית שני מעברים. קודם אוספים את כל כתובות הספרים מדפי הרשימה, ואז מבקרים בכל דף פרטים. זה הופך את הסקרייפר לקל יותר להמשך אם הוא נופל באמצע, ואפשר לשמור את רשימת הכתובות לדיסק כ‑checkpoint.
מלכודת: לשכוח לטפל בשדות חסרים. לא לכל דף ספר יש ISBN, תגיות ז'אנר או תיאור. תמיד השתמשו ב‑.get() עם ערך ברירת מחדל, או עטפו בוררים ב‑if. שגיאת NoneType אחת יכולה להפיל ריצה של 3 שעות.
מלכודת: לרוץ מהר מדי. ברור שמתחשק להגדיר time.sleep(0.5) ולדהור. אבל Goodreads תתחיל להחזיר 403s אחרי בערך 20-30 בקשות מהירות, וברגע שסימנו אתכם, ייתכן שתצטרכו לחכות שעות או להחליף IP. השהיה של 4-5 שניות היא נקודת האיזון הטובה.
מלכודת: להאמין למדריכים ישנים. אם מדריך מזכיר את Goodreads API, או משתמש בשמות מחלקות כמו .field.value או #bookTitle, סביר שהוא כבר לא מעודכן. תמיד אימתו את הבוררים מול הדף החי לפני שבונים את הסקרייפר.
לעוד מידע על בחירת כלי וספריות גרידה נכונים, בדקו את המדריכים שלנו על ועל .
סיכום ונקודות מפתח
גרידת Goodreads עם Python היא לגמרי אפשרית — רק צריך לדעת איפה המוקשים. הגרסה הקצרה:
- Goodreads API נעלם (מאז דצמבר 2020). גרידה היא הדרך העיקרית לקבל נתוני ספרים מובנים מהפלטפורמה.
- תוצאות ריקות נגרמות כמעט תמיד מתוכן שמועלה ב‑JS, בוררים מיושנים, headers חסרים או בעיות אימות בעימוד — לא כי הקוד שלכם בהכרח שגוי.
- JSON-LD הוא החבר הכי טוב שלכם למטא-דאטה של ספרים. הוא יציב, מובנה וכמעט לא משתנה.
- עימוד דורש אימות עבור הרבה דפי מדפים ורשימות מעבר לעמוד 5. יש לכלול את ה‑cookie
_session_id2. - הגבלת קצב היא אמיתית. השתמשו בהשהיות של 3-8 שניות והישארו מתחת ל־5,000 בקשות ביום.
- אסטרטגיית שני מעברים (קודם לאסוף כתובות, ואז לגרד דפי פרטים) היא אמינה יותר וניתנת להמשך.
- למי שלא כותב קוד (או לכל מי שמעריך את הצהריים שלו), מטפל בכל זה — עיבוד JS, עימוד, העשרה מדפי משנה וייצוא — בכשתי הקלקות.
גרדו באחריות, כבדו את robots.txt, ושהנתונים שלכם מ‑Goodreads יחזרו תמיד עם יותר מ‑[].
שאלות נפוצות
האם עדיין אפשר להשתמש ב‑Goodreads API?
לא. Goodreads הוציאה משימוש את ה‑API הציבורי שלה בדצמבר 2020 ומאז אינה מנפיקה מפתחות מפתח חדשים. מפתחות קיימים שלא היו פעילים במשך 30 יום הושבתו אוטומטית. גרידת Web או APIs חלופיים (כמו Open Library או Google Books) הם כיום האפשרויות לקבלת נתוני ספרים בצורה פרוגרמטית.
למה הסקרייפר שלי ל‑Goodreads מחזיר תוצאות ריקות?
הסיבה הנפוצה ביותר היא תוכן שמועלה ב‑JavaScript. Goodreads טוענת ביקורות, התפלגויות דירוגים והרבה אזורי פרטים דרך React/JavaScript, ולכן requests.get() פשוט לא רואה אותם. עברו ל‑Selenium או Playwright עבור דפים כאלה. סיבות נוספות כוללות בוררי CSS מיושנים (כי Goodreads שינתה את ה‑HTML), headers של User-Agent חסרים (שגורמים לחסימת 403), או בקשות לא מאומתות בדפי מדפים עם עימוד.
האם זה חוקי לגרד את Goodreads?
גרידת נתונים זמינים לציבור לשימוש אישי או למחקר מקובלת בדרך כלל לפי תקדימים משפטיים עדכניים (hiQ v. LinkedIn, Meta v. Bright Data). עם זאת, תנאי השימוש של Goodreads אוסרים איסוף נתונים אוטומטי, ותמיד כדאי לבדוק את ה‑robots.txt שלהם. הימנעו מהפצה מסחרית של טקסט ביקורות מוגן, והגבילו את נפח הבקשות כדי לא להעמיס על משאבי האתר.
איך מגרדים כמה עמודים ב‑Goodreads?
מצרפים ?page=N לכתובת של המדף או הרשימה, ומריצים לולאה על מספרי העמודים. בודקים אם אין תוצאות או אם אין קישור "next" כדי לזהות את העמוד האחרון. חשוב: חלק מדפי המדפים דורשים אימות (ה‑cookie _session_id2) כדי להחזיר תוצאות מעבר לעמוד 5 — בלעדיו תקבלו בשקט שוב ושוב נתונים של עמוד 1.
אפשר לגרד Goodreads בלי לכתוב קוד?
כן. הוא תוסף Chrome שמאפשר לגרד Goodreads בכשתי הקלקות — ה‑AI מציע שדות נתונים, אתם לוחצים "Scrape", ומייצאים ישירות ל‑Google Sheets, Excel, Airtable או Notion. הוא מטפל בתוכן שמועלה ב‑JavaScript, בעימוד ובהעשרת דפי משנה אוטומטית, בלי צורך ב‑Python או בכתיבת קוד.
לקריאה נוספת