การเขียน Python ไป 30 บรรทัด รัน Goodreads scraper แล้วเห็นมันคืนค่าเป็น [] นี่เป็นอะไรที่ทำให้หมดกำลังใจสุด ๆ ลิสต์ว่างเปล่า ไม่มีอะไรเลย มีแค่คุณกับเคอร์เซอร์ที่กะพริบอยู่บนหน้าจอ
ผมเห็นปัญหานี้เกิดขึ้นมานับสิบครั้ง — ทั้งจากการทดลองภายในทีมที่ เอง จากฟอรัมสาย dev และจาก issue บน GitHub ของโปรเจกต์ scraper ที่ถูกปล่อยทิ้งไว้ คำบ่นมักจะคล้าย ๆ กันเสมอ: "ส่วนรีวิวอันดับต้น ๆ ว่างเปล่า เห็นแค่ []", "ไม่ว่าจะรันหน้าไหน ก็ได้ข้อมูลจากหน้าแรกตลอด", "โค้ดเคยใช้ได้ปีที่แล้ว ตอนนี้พังแล้ว" และที่แย่ไปกว่านั้นคือ Goodreads API ถูกยกเลิกไปตั้งแต่เดือนธันวาคม 2020 ดังนั้นคำแนะนำแบบ "ก็ใช้ API สิ" ที่เจอในบทความเก่า ๆ จึงใช้ไม่ได้แล้ว
ถ้าตอนนี้คุณต้องการข้อมูลหนังสือจาก Goodreads แบบเป็นโครงสร้าง — ไม่ว่าจะเป็นชื่อหนังสือ ผู้เขียน เรตติ้ง รีวิว หมวดหมู่ หรือ ISBN — การดึงข้อมูลจากหน้าเว็บคือทางเลือกหลักในปัจจุบัน คู่มือนี้จะพาคุณทำวิธีที่ใช้งานได้จริงแบบครบถ้วนสำหรับการ scrape Goodreads ด้วย Python ครอบคลุมตั้งแต่คอนเทนต์ที่เรนเดอร์ด้วย JS การแบ่งหน้า การกันโดนบล็อก ไปจนถึงการ export ข้อมูล และถ้า Python ไม่ใช่แนวของคุณ ผมจะโชว์ทางเลือกแบบไม่ต้องเขียนโค้ดที่ทำงานได้ในประมาณสองคลิก
การ Scrape Goodreads คืออะไร และทำไมต้องใช้ Python?
การ scrape Goodreads คือการดึงข้อมูลหนังสือโดยอัตโนมัติ — เช่น ชื่อเรื่อง ผู้เขียน เรตติ้ง จำนวนรีวิว หมวดหมู่ ISBN จำนวนหน้า วันที่เผยแพร่ และอื่น ๆ — จากหน้าเว็บ Goodreads ด้วยโค้ด แทนที่จะต้องคัดลอกวางเองทีละรายการ
Goodreads เป็นหนึ่งในฐานข้อมูลหนังสือที่ใหญ่ที่สุดในโลก มีสมาชิกลงทะเบียนมากกว่า และมีรีวิวที่ผู้ใช้สร้างขึ้นราว ในแต่ละเดือนจะมีหนังสือมากกว่า 18 ล้านเล่มถูกเพิ่มเข้าไปในชั้น "Want to Read" ข้อมูลที่อัปเดตตลอดเวลาและมีโครงสร้างแบบนี้นี่แหละ คือเหตุผลที่สำนักพิมพ์ นักวิทยาศาสตร์ข้อมูล ร้านหนังสือ และนักวิจัยยังกลับมาใช้กันซ้ำ ๆ
Python เป็นภาษายอดนิยมสำหรับงานประเภทนี้ — รองรับงาน scraping ประมาณ ของโปรเจกต์ทั้งหมด ไลบรารีก็ครบและ成熟แล้ว (requests, BeautifulSoup, Selenium, Playwright, pandas) syntax ก็เป็นมิตรกับมือใหม่ และชุมชนก็ใหญ่มาก
ถ้าคุณยังไม่เคย scrape เว็บไซต์มาก่อน Python คือจุดเริ่มต้นที่เหมาะสุด
ทำไมต้อง Scrape Goodreads ด้วย Python? ตัวอย่างการใช้งานจริง
ก่อนจะลงมือเขียนโค้ด มาดูกันก่อนว่าข้อมูลพวกนี้ใครต้องใช้ และเอาไปทำอะไรได้บ้าง
| กรณีใช้งาน | ใครได้ประโยชน์ | สิ่งที่ดึงข้อมูล |
|---|---|---|
| วิจัยตลาดสำหรับสำนักพิมพ์ | สำนักพิมพ์, ตัวแทนวรรณกรรม | หมวดหมู่ที่กำลังมาแรง, หนังสือคะแนนสูงสุด, ผู้เขียนหน้าใหม่, เรตติ้งคู่แข่ง |
| ระบบแนะนำหนังสือ | นักวิทยาศาสตร์ข้อมูล, คนทำโปรเจกต์เล่น ๆ, นักพัฒนาแอป | เรตติ้ง, หมวดหมู่, ชั้นหนังสือของผู้ใช้, ความรู้สึกของรีวิว |
| ติดตามราคาและสต็อกสินค้า | ร้านหนังสืออีคอมเมิร์ซ | หนังสือที่กำลังเป็นกระแส, จำนวนรีวิว, จำนวนคนกด "Want to Read" |
| งานวิจัยเชิงวิชาการ | นักวิจัย, นักศึกษา | ข้อความรีวิว, การกระจายของเรตติ้ง, การจัดหมวดหมู่แนวหนังสือ |
| วิเคราะห์พฤติกรรมการอ่าน | บล็อกเกอร์สายหนังสือ, โปรเจกต์ส่วนตัว | ข้อมูลชั้นหนังสือส่วนตัว, ประวัติการอ่าน, สถิติสิ้นปี |
ตัวอย่างที่ชัดเจน: UCSD Book Graph ซึ่งเป็นชุดข้อมูลวิจัยที่ถูกอ้างอิงบ่อยมากในงานระบบแนะนำ มีหนังสือที่ไม่ซ้ำกันถึง โดยเก็บมาจาก shelf ของ Goodreads ที่เปิดให้เข้าถึงได้ต่อสาธารณะ Kaggle หลายชุดข้อมูล (เช่น goodbooks-10k, Best Books Ever ฯลฯ) ก็เริ่มต้นจากการ scrape Goodreads เช่นกัน และงานวิจัยปี 2025 ใน Big Data and Society ก็ได้คัดกรองเชิงคำนวณ เพื่อวิเคราะห์ว่ารีวิวที่ได้รับการสนับสนุนมีผลต่อแพลตฟอร์มอย่างไร
ฝั่งเชิงพาณิชย์ Bright Data ยังขายชุดข้อมูล Goodreads ที่ scrape ไว้แล้วในราคาต่ำสุดเพียง $0.50 ต่อ 1,000 รายการ ซึ่งเป็นหลักฐานชัดเจนว่าข้อมูลนี้มีมูลค่าในตลาดจริง
Goodreads API หายไปแล้ว — แล้วอะไรมาแทน?
ถ้าคุณเพิ่งค้นหาคำว่า "Goodreads API" เมื่อไม่นานมานี้ มีโอกาสสูงมากว่าคุณจะเจอบทความที่ล้าสมัย เมื่อวันที่ 8 ธันวาคม 2020 Goodreads ได้หยุดออก developer API key ใหม่แบบเงียบ ๆ ไม่มีบล็อกโพสต์ ไม่มีอีเมลแจ้ง มีแค่แบนเนอร์เล็ก ๆ บนหน้า docs และนักพัฒนาที่งงกันไปหมด

ผลกระทบเกิดขึ้นทันที นักพัฒนาคนหนึ่งชื่อ Kyle K เคยทำ Discord bot สำหรับแชร์คำแนะนำหนังสือ — "อยู่ ๆ ก็ POOF หยุดทำงานเฉย ๆ" อีกคนชื่อ Matthew Jones สูญเสียสิทธิ์เข้าถึง API ก่อนการโหวต Reddit r/Fantasy Stabby Awards แค่หนึ่งสัปดาห์ ทำให้ต้องย้อนกลับไปใช้ Google Forms ส่วน Elena Neacsu นักศึกษาปริญญาโทก็โดนโปรเจกต์ธีสิสที่ทำอยู่สะดุดกลางทาง
แล้วตอนนี้เหลืออะไรบ้าง? ภาพรวมปัจจุบันเป็นแบบนี้:
| แนวทาง | ข้อมูลที่ได้ | ความง่ายในการใช้งาน | ข้อจำกัดอัตราเรียกใช้ | สถานะ |
|---|---|---|---|---|
| Goodreads API | เมตาดาต้าครบ, รีวิว | ง่าย (เคยเป็น) | 1 req/sec | ยุติการใช้งาน (ธ.ค. 2020) — ไม่มี key ใหม่ |
| Open Library API | ชื่อหนังสือ, ผู้เขียน, ISBN, ปก (~30 ล้านชื่อเรื่อง) | ง่าย | 1-3 req/sec | ยังใช้งานได้, ฟรี, ไม่ต้องยืนยันตัวตน |
| Google Books API | เมตาดาต้า, พรีวิว | ง่าย | ฟรี 1,000 ครั้ง/วัน | ยังใช้งานได้ (แต่ข้อมูล ISBN ภาษาอื่นอาจขาด) |
| Python scraping (requests + BS4) | ทุกอย่างที่อยู่ใน HTML เริ่มต้น | ปานกลาง | จัดการเอง | ใช้ได้กับคอนเทนต์แบบ static |
| Python scraping (Selenium/Playwright) | รวมคอนเทนต์ที่เรนเดอร์ด้วย JS ด้วย | ยากกว่า | จัดการเอง | จำเป็นสำหรับรีวิวและบางรายการ |
| Thunderbit (Chrome extension แบบไม่ต้องเขียนโค้ด) | ข้อมูลที่มองเห็นได้บนหน้าเว็บ | ง่ายมาก (2 คลิก) | ใช้เครดิต | ใช้งานได้ — ไม่ต้องใช้ Python |
Open Library เป็นตัวเสริมที่ดี โดยเฉพาะสำหรับการค้นหา ISBN และข้อมูลพื้นฐาน แต่ถ้าคุณต้องการเรตติ้ง รีวิว แท็กหมวดหมู่ หรือจำนวนคนกด "Want to Read" คุณต้อง scrape Goodreads โดยตรง — ไม่ว่าจะด้วย Python หรือด้วยเครื่องมืออย่าง Thunderbit ซึ่งสามารถ scrape หน้า Goodreads ได้ (รวมถึงหน้าย่อยของรายละเอียดหนังสือ) พร้อมฟิลด์ที่ AI แนะนำและ export ตรงไปยัง Google Sheets, Notion หรือ Airtable
ทำไม Goodreads Python Scraper ของคุณถึงได้ผลลัพธ์ว่าง และแก้อย่างไร
นี่คือส่วนที่ผมอยากให้มีตั้งแต่ตอนเริ่มทำงานกับข้อมูล Goodreads เลย ปัญหา "ผลลัพธ์ว่าง" เป็นข้อร้องเรียนที่พบบ่อยที่สุดในฟอรัม dev และมันมีหลายสาเหตุที่ต่างกัน — ซึ่งแต่ละแบบก็มีวิธีแก้ของตัวเอง
| อาการ | สาเหตุหลัก | วิธีแก้ |
|---|---|---|
รีวิว/เรตติ้งคืนค่าเป็น [] | คอนเทนต์เรนเดอร์ด้วย JS (React/lazy-load) | ใช้ Selenium หรือ Playwright แทน requests |
| ดึงได้แค่หน้า 1 ตลอด | พารามิเตอร์ pagination ไม่ถูกใช้ หรือควบคุมด้วย JS | ส่งพารามิเตอร์ ?page=N ให้ถูกต้อง; ใช้ browser automation ถ้าเป็น infinite scroll |
| โค้ดเคยใช้ได้ปีที่แล้ว แต่ตอนนี้พัง | Goodreads เปลี่ยนชื่อ class ใน HTML | ใช้ selector ที่ทนทานกว่า (JSON-LD, แอตทริบิวต์ data-testid) |
| เจอ 403/ถูกบล็อกหลังไม่กี่คำขอ | ไม่มี headers / ส่งคำขอเร็วเกินไป | เพิ่ม User-Agent, ใส่ time.sleep(), หมุน proxy |
| เจอ login wall บนหน้า shelf/list | ต้องใช้คุกกี้/เซสชัน | ใช้ requests.Session() กับคุกกี้ หรือ scrape ผ่าน browser |
คอนเทนต์ที่เรนเดอร์ด้วย JS: รีวิวและเรตติ้งขึ้นเป็นค่าว่าง
Goodreads ใช้ frontend แบบ React ถ้าคุณเรียก requests.get() ไปที่หน้า book page คุณจะได้แค่ HTML เริ่มต้น — แต่รีวิว การแจกแจงเรตติ้ง และส่วน "more info" หลายส่วนจะถูกโหลดเพิ่มทีหลังด้วย JavaScript ดังนั้น scraper ของคุณจึงมองไม่เห็นมันจริง ๆ
วิธีแก้: สำหรับหน้าที่ต้องใช้คอนเทนต์ที่เรนเดอร์ด้วย JS ให้เปลี่ยนไปใช้ Selenium หรือ Playwright ผมแนะนำ Playwright สำหรับโปรเจกต์ใหม่ — เพราะมันเร็วกว่า Selenium ด้วยโปรโตคอลแบบ WebSocket และมีระบบ stealth กับ async support ที่ดีกว่า

Pagination ที่ดึงได้แค่หน้า 1
อันนี้หลอกเก่งมาก คุณเขียนลูป เพิ่ม ?page=N แต่กลับได้ผลลัพธ์เดิมทุกครั้ง บน Goodreads หน้า shelf จะส่งกลับเนื้อหาหน้า 1 แบบเงียบ ๆ ไม่ว่าจะใส่ ?page= อะไร ถ้าคุณยังไม่ได้ยืนยันตัวตน ไม่มี error ไม่มี redirect — มีแต่หน้าแรกเดิมซ้ำ ๆ
วิธีแก้: ใส่คุกกี้ session ที่ยืนยันตัวตนแล้ว โดยเฉพาะ _session_id2 เดี๋ยวจะอธิบายในส่วน pagination ด้านล่าง
โค้ดที่เคยใช้ได้ปีที่แล้ว แต่ตอนนี้ใช้ไม่ได้
Goodreads เปลี่ยนชื่อ class และโครงสร้างหน้าเป็นระยะ ๆ repo ยอดนิยม maria-antoniak/goodreads-scraper บน GitHub ตอนนี้มีข้อความแจ้งถาวรว่า "This project is unmaintained and no longer functioning." วิธีแก้คือใช้ selector ที่ทนการเปลี่ยนแปลงได้มากกว่า เช่น JSON-LD structured data (ที่อิงมาตรฐาน schema.org และแทบไม่เปลี่ยน) หรือ data-testid แทนการยึด class name ที่เปราะบาง
เจอ 403 หรือถูกบล็อก
ไลบรารี requests ของ Python มี TLS fingerprint ต่างจาก Chrome แม้จะใส่ Chrome User-Agent แล้ว ระบบตรวจจับบอทอย่าง AWS WAF (ซึ่ง Goodreads ใช้ เพราะเป็นบริษัทในเครือ Amazon) ก็ยังดูออกอยู่ดี วิธีแก้คือใส่ browser headers ที่สมจริง เว้น time.sleep() ระหว่างคำขอ 3-8 วินาที และถ้าจะ scrape ขนาดใหญ่จริง ๆ ให้พิจารณาใช้ curl_cffi เพื่อให้ TLS fingerprint ตรงกับเบราว์เซอร์
Login wall บนหน้า Shelf และ List
บางหน้า shelf และ list ของ Goodreads ต้องล็อกอินถึงจะเข้าถึงคอนเทนต์เต็มได้ โดยเฉพาะหลังหน้า 5 เป็นต้นไป ใช้ requests.Session() พร้อมคุกกี้ที่ export มาจากเบราว์เซอร์ หรือใช้ Selenium/Playwright กับโปรไฟล์ที่ล็อกอินไว้แล้ว Thunderbit จัดการเรื่องนี้ให้โดยธรรมชาติ เพราะมันรันอยู่ใน Chrome ที่คุณล็อกอินไว้เอง
ก่อนเริ่มต้น
- ระดับความยาก: ปานกลาง (สมมติว่ามีพื้นฐาน Python)
- เวลาที่ต้องใช้: ประมาณ 20-30 นาทีสำหรับบทเรียนเต็ม
- สิ่งที่ต้องมี:
- Python 3.8+
- Chrome browser (สำหรับตรวจ DOM และใช้ Selenium/Playwright)
- ไลบรารี:
requests,beautifulsoup4,seleniumหรือplaywright,pandas - (ทางเลือก)
gspreadสำหรับ export ไป Google Sheets - (ทางเลือก) สำหรับทางเลือกแบบไม่ต้องเขียนโค้ด

ขั้นตอนที่ 1: ตั้งค่าสภาพแวดล้อม Python ของคุณ
ติดตั้งไลบรารีที่จำเป็น เปิดเทอร์มินัลแล้วรัน:
1pip install requests beautifulsoup4 selenium pandas lxml
ถ้าคุณอยากใช้ Playwright (แนะนำสำหรับโปรเจกต์ใหม่):
1pip install playwright
2playwright install chromium
ถ้าจะ export ไป Google Sheets (ทางเลือก):
1pip install gspread oauth2client
ตรวจสอบให้แน่ใจว่าคุณใช้ Python 3.8 ขึ้นไป เช็กได้ด้วย python --version
หลังติดตั้งแล้ว ควร import ทุกไลบรารีได้โดยไม่เกิด error ลองรัน python -c "import requests, bs4, pandas; print('Ready')" เพื่อยืนยัน
ขั้นตอนที่ 2: ส่งคำขอแรกพร้อม Headers ที่เหมาะสม
เปิด Goodreads genre shelf หรือ list page ในเบราว์เซอร์ — เช่น 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 ด้านบนจำลอง session ของ Chrome จริง
ขั้นตอนที่ 3: ตรวจหน้าเว็บและหา selector ที่ถูกต้อง
เปิด Chrome DevTools (F12) บนหน้า Goodreads list page จากนั้นคลิกขวาที่ชื่อหนังสือแล้วเลือก "Inspect" คุณจะเห็นโครงสร้าง DOM ของแต่ละรายการ
สำหรับ list page แต่ละหนังสือมักถูกห่อด้วย <tr> ที่มี itemtype="http://schema.org/Book" ภายในคุณจะเจอ:
- ชื่อเรื่อง:
a.bookTitle(ข้อความในลิงก์คือชื่อหนังสือ และhrefคือ URL ของหนังสือ) - ผู้เขียน:
a.authorName - เรตติ้ง:
span.minirating(มีค่าเฉลี่ยเรตติ้งและจำนวนเรตติ้ง) - รูปปก:
imgภายในแถวของหนังสือ
สำหรับหน้า detail ของหนังสือแต่ละเล่ม ให้ข้าม CSS selector ไปเลยแล้วใช้ JSON-LD โดยตรง Goodreads ฝัง structured data ไว้ในแท็ก <script type="application/ld+json"> ซึ่งเป็นรูปแบบ schema.org Book และเสถียรกว่าชื่อ class มาก เพราะ Goodreads เปลี่ยนพวกนั้นบ่อยตามใจ
ขั้นตอนที่ 4: ดึงข้อมูลหนังสือจากหน้า list เพียงหน้าเดียว
มาลอง parse หน้า list แล้วดึงข้อมูลพื้นฐานของแต่ละหนังสือกัน:
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" และ URL ไปยังหน้ารายละเอียดของหนังสือ
ขั้นตอนที่ 5: Scrape หน้าย่อยเพื่อข้อมูลหนังสือแบบละเอียด
หน้ารายการให้ข้อมูลพื้นฐาน แต่ข้อมูลสำคัญจริง ๆ — อย่าง 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 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) # เคารพ rate limit
ใส่ time.sleep() 3-8 วินาทีระหว่างคำขอ Goodreads มักเริ่มจำกัดเมื่อมีประมาณ 20-30 requests ต่อนาทีจาก IP เดียว และถ้าคุณเร็วเกินไปก็จะเริ่มเจอ 403 หรือ CAPTCHA
แนวทางสองรอบแบบนี้ — รอบแรกเก็บ URL ของหนังสือจากหน้า list แล้วรอบสองค่อยเข้าไปดึงข้อมูลจากหน้า detail — เชื่อถือได้มากกว่าและกลับมาทำต่อได้ง่ายถ้าถูกขัดจังหวะ นี่คือกลยุทธ์ที่ scraper Goodreads ที่ทำงานได้จริงส่วนใหญ่ใช้
หมายเหตุ: สามารถทำสิ่งนี้ให้โดยอัตโนมัติผ่านการ scrape หน้าย่อย AI จะเข้าไปยังหน้ารายละเอียดของแต่ละหนังสือและเติมข้อมูลลงในตารางให้ครบ — ไม่ต้องเขียนโค้ด ไม่ต้องทำลูป ไม่ต้องตั้ง sleep timer
ขั้นตอนที่ 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 — หน้าอาจต้องล็อกอินหรือ JS หมดเวลา")
20# จากนั้น parse หน้าเต็มที่เรนเดอร์แล้ว
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), หน้า list, หน้า shelf (หน้า 1), และข้อมูล Choice Awards - ใช้ Selenium หรือ Playwright สำหรับรีวิว การแจกแจงเรตติ้ง และคอนเทนต์ใด ๆ ที่ไม่โผล่ใน HTML ดิบ
โดยทั่วไป Playwright เป็นตัวเลือกที่ดีกว่าสำหรับโปรเจกต์ใหม่ — เร็วกว่า ใช้หน่วยความจำน้อยกว่า และมีค่าเริ่มต้นด้าน stealth ที่ดีกว่า แต่ Selenium ก็มีชุมชนใหญ่กว่าและมีตัวอย่างโค้ดเกี่ยวกับ Goodreads เยอะกว่า
Pagination ที่ใช้งานได้จริง: การ scrape Goodreads หลายหน้าให้ครบ
Pagination คือจุดพังที่พบบ่อยที่สุดใน scraper ของ Goodreads และผมยังไม่เคยเจอบทความคู่แข่งที่อธิบายมันได้ถูกต้องเลย นี่คือวิธีทำให้มันใช้ได้จริง
URL ของ Pagination ใน Goodreads ทำงานอย่างไร
Goodreads ใช้พารามิเตอร์ ?page=N แบบตรงไปตรงมาสำหรับหน้าที่แบ่งหน้าเกือบทั้งหมด:
- Lists:
https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2 - Shelves:
https://www.goodreads.com/shelf/show/thriller?page=2 - Search:
https://www.goodreads.com/search?q=fantasy&page=2
แต่ละหน้า list มักแสดงหนังสือ 100 เล่ม ส่วน shelves แสดง 50 รายการต่อหน้า
เขียนลูป Pagination ที่รู้ว่าเมื่อไรต้องหยุด
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
นี่คือกับดักที่ทำให้คนพลาดกันเยอะมาก: บางหน้า shelf และ list ของ Goodreads จะคืนข้อมูลหน้า 1 แบบเงียบ ๆ ถ้าคุณขอหน้า 6 ขึ้นไปโดยไม่ยืนยันตัวตน ไม่มี error ไม่มี redirect — แค่ข้อมูลหน้าแรกซ้ำเดิม
วิธีแก้คือ export คุกกี้ _session_id2 ออกจากเบราว์เซอร์ของคุณ (ใช้ extension สำหรับ export คุกกี้ หรือ Chrome DevTools > Application > Cookies) แล้วใส่ลงใน requests:
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 รองรับทั้ง pagination แบบคลิกต่อหน้าและแบบ infinite scroll ได้โดยตรง ไม่ต้องเขียนโค้ด ไม่ต้องจัดการคุกกี้ ถ้าลูป pagination ของคุณพังบ่อย ๆ ก็น่าลอง
สคริปต์ Python แบบครบชุดที่พร้อมคัดลอกไปใช้ได้เลย
นี่คือสคริปต์ฉบับรวมทั้งหมด มันจัดการทั้ง headers, pagination, การ scrape หน้าย่อยผ่าน JSON-LD, rate limiting และการ export เป็น 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 """คืนค่า list ของ dict ที่มี 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 + fallback จาก 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: เก็บ URL ของหนังสือจากหน้ารายการ ---
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 # --- Export เป็น 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 วินาทีระหว่างการเรียก detail page) ไฟล์ CSV ที่ได้จะมีคอลัมน์อย่าง title, author, book_url, isbn, pages, avg_rating, rating_count, review_count, description, genres, language, format และ published
Export เกินกว่า CSV: ไป Google Sheets ด้วย gspread
ถ้าคุณอยากเอาข้อมูลไปไว้ใน Google Sheets แทน CSV หรือใช้ทั้งคู่ ให้เพิ่มส่วนนี้หลังการ export 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 service account ที่เปิดใช้ Sheets และ Drive API แล้ว โดย อธิบายขั้นตอนได้ภายในประมาณ 5 นาที ถ้าจะส่งข้อมูลมากกว่าสองสามร้อยแถว ควรใช้ batch operations (append_rows() กับ list of lists) เพราะ Google จำกัดไว้ที่ 300 requests ต่อ 60 วินาทีต่อโปรเจกต์
แน่นอนว่า ถ้าคุณรู้สึกว่าทั้งหมดนี้เยอะเกินไป Thunderbit export ไปยัง Google Sheets, Airtable, Notion, Excel, CSV และ JSON ได้ด้วย — ไม่ต้องตั้งค่าไลบรารี ไม่ต้องไฟล์ credentials ไม่ต้องกังวลโควตา API
ทางเลือกแบบไม่ต้องเขียนโค้ด: Scrape Goodreads ด้วย Thunderbit
ไม่ใช่ทุกคนที่อยากดูแล Python script เอง บางทีคุณอาจเป็นสำนักพิมพ์ที่ต้องทำ market analysis แบบครั้งเดียว หรือบล็อกเกอร์สายหนังสือที่อยากได้สเปรดชีตของ bestseller ปีนี้ นั่นแหละคือสถานการณ์ที่เราทำ Thunderbit มาเพื่อแก้ปัญหา
วิธี Scrape Goodreads ด้วย Thunderbit
- ติดตั้ง Thunderbit Chrome extension จาก แล้วเปิดหน้า Goodreads list, shelf หรือ search results
- คลิก "AI Suggest Fields" ใน sidebar ของ Thunderbit AI จะอ่านหน้าเว็บและแนะนำคอลัมน์ให้ เช่น title, author, rating, URL ของรูปปก และลิงก์หนังสือ
- คลิก "Scrape" — ข้อมูลจะถูกดึงเข้า table ที่มีโครงสร้างภายในไม่กี่วินาที
- Export ไปยัง Google Sheets, Excel, Airtable, Notion, CSV หรือ JSON
ถ้าต้องการข้อมูลหนังสือแบบละเอียด เช่น ISBN คำอธิบาย หมวดหมู่ และจำนวนหน้า Thunderbit มีฟีเจอร์ subpage scraping ที่จะเข้าไปยังหน้ารายละเอียดของแต่ละเล่มและเติมข้อมูลให้ตารางอัตโนมัติ — ไม่ต้องเขียนลูป ไม่ต้องตั้ง sleep timer ไม่ต้อง debug
Thunderbit ยังรองรับรายการที่มี pagination แบบอัตโนมัติอีกด้วย คุณแค่บอกให้มันคลิก "Next" หรือ scroll แล้วมันจะรวบรวมข้อมูลจากทุกหน้าโดยไม่ต้องเขียนโค้ด
ข้อแลกเปลี่ยนก็ตรงไปตรงมา: สคริปต์ Python ให้การควบคุมเต็มที่และฟรี (ยกเว้นเวลาของคุณ) ส่วน Thunderbit แลกความยืดหยุ่นบางส่วนกับการประหยัดเวลาแบบมหาศาลและไม่ต้องดูแลรักษา สำหรับรายการ 300 หนังสือ สคริปต์ Python ใช้เวลารันประมาณ 25 นาที บวกเวลาที่คุณใช้เขียนและแก้บั๊ก Thunderbit ได้ข้อมูลเดียวกันในประมาณ 3 นาที ด้วยสองคลิก
Scrape Goodreads อย่างรับผิดชอบ: robots.txt, ข้อกำหนดการใช้งาน และจริยธรรม
เรื่องนี้ควรพูดกันตรง ๆ ไม่ใช่ยกเป็นย่อหน้าคำเตือนแบบผ่าน ๆ
robots.txt ของ Goodreads จริง ๆ บอกอะไร
robots.txt ของ Goodreads ปัจจุบันค่อนข้างชัดเจน หน้ารายละเอียดหนังสือ (/book/show/), list สาธารณะ (/list/show/), shelf สาธารณะ (/shelf/show/) และหน้า author (/author/show/) ไม่ได้ถูกบล็อก สิ่งที่ถูกบล็อกคือ /api, /book/reviews/, /review/list, /review/show, /search และ path อื่น ๆ อีกหลายรายการ GPTBot และ CCBot (Common Crawl) ถูกบล็อกทั้งหมดด้วย Disallow: / และมีคำสั่ง Crawl-delay: 5 สำหรับ bingbot แต่ไม่มีการกำหนด delay แบบทั่วทั้งระบบ
ข้อกำหนดการใช้งานของ Goodreads แบบเข้าใจง่าย
ToS (ปรับล่าสุดเมื่อ 28 เมษายน 2021) ห้าม "การใช้ data mining, robots หรือเครื่องมือเก็บ/ดึงข้อมูลในลักษณะคล้ายกัน" ซึ่งเป็นถ้อยคำที่กว้างและควรให้ความสำคัญ — แต่ศาลก็มีแนววินิจฉัยต่อเนื่องว่า การละเมิด ToS เพียงอย่างเดียวไม่ถือเป็นการ "เข้าถึงโดยไม่ได้รับอนุญาต" ในทางอาญา คดี ระบุไว้ว่า "การทำให้การละเมิดเงื่อนไขการใช้งานเป็นอาชญากรรม เสี่ยงที่จะทำให้แต่ละเว็บไซต์กลายเป็นเขตอำนาจทางอาญาของตัวเอง"
แนวปฏิบัติที่ดี
- เว้นช่วงคำขอ: 3-8 วินาทีระหว่าง requests (robots.txt ของ Goodreads เองแนะนำ 5 วินาทีสำหรับบอท)
- อย่าเกิน 5,000 requests ต่อวัน จาก IP เดียว
- ดึงเฉพาะหน้าที่เข้าถึงได้สาธารณะ — หลีกเลี่ยงการ scrape ข้อมูลที่ต้องล็อกอินจำนวนมาก
- อย่าเอาข้อความรีวิวดิบไปแจกต่อในเชิงพาณิชย์ — รีวิวคือผลงานสร้างสรรค์ที่มีลิขสิทธิ์
- เก็บเฉพาะข้อมูลที่จำเป็น และกำหนดนโยบายการเก็บรักษาข้อมูล
- งานวิจัยส่วนตัว vs การใช้งานเชิงพาณิชย์: การ scrape ข้อมูลสาธารณะเพื่อวิเคราะห์ส่วนตัวหรือวิจัยเชิงวิชาการโดยทั่วไปมักยอมรับได้ ความเสี่ยงทางกฎหมายจะสูงขึ้นเมื่อมีการนำไป redistribe ในเชิงพาณิชย์
การใช้เครื่องมืออย่าง Thunderbit (ซึ่ง scrape ผ่าน session ในเบราว์เซอร์ของคุณเอง) ทำให้การใช้งานดูเหมือนการท่องเว็บปกติ แต่หลักจริยธรรมเดียวกันก็ยังใช้ได้ไม่ว่าจะใช้เครื่องมืออะไร ถ้าคุณอยากอ่านต่อในเรื่อง เราได้อธิบายแยกไว้แล้ว
เคล็ดลับและข้อผิดพลาดที่พบบ่อย
เคล็ดลับ: เริ่มจาก JSON-LD เสมอ ก่อนจะเขียน CSS selector ซับซ้อน ให้ตรวจดูก่อนว่าข้อมูลที่คุณต้องการอยู่ในแท็ก <script type="application/ld+json"> หรือไม่ มันเสถียรกว่า แปลงข้อมูลง่ายกว่า และพังยากกว่าเวลาที่ Goodreads ปรับ frontend
เคล็ดลับ: ใช้กลยุทธ์สองรอบ รอบแรกเก็บ URL ของหนังสือจากหน้า list ให้ครบก่อน แล้วค่อยเข้าไป scrape หน้ารายละเอียด วิธีนี้ทำให้กลับมาทำต่อได้ง่ายถ้า scraper หยุดกลางทาง และคุณสามารถบันทึกรายการ URL ลงดิสก์เป็น checkpoint ได้
ข้อผิดพลาด: ลืมจัดการฟิลด์ที่อาจไม่มีค่า ไม่ใช่ทุกหน้า book page จะมี ISBN แท็กหมวดหมู่ หรือคำอธิบายเสมอไป ใช้ .get() พร้อมค่าเริ่มต้น หรือห่อ selector ด้วยเช็ก if เสมอ error แค่ NoneType เดียวอาจทำให้รัน scraping 3 ชั่วโมงพังได้
ข้อผิดพลาด: เร็วเกินไป ผมรู้ว่ามันน่าใส่ time.sleep(0.5) แล้ววิ่งให้สุด แต่ Goodreads จะเริ่มคืน 403 หลังจาก requests รัว ๆ ประมาณ 20-30 ครั้ง และถ้าโดนมองว่าเป็นบอทแล้ว คุณอาจต้องรอเป็นชั่วโมงหรือเปลี่ยน IP เลย ช่วงหน่วง 4-5 วินาทีถือว่ากำลังดี
ข้อผิดพลาด: เชื่อบทความเก่าเกินไป ถ้าคู่มือไหนยังพูดถึง Goodreads API หรือใช้ class name อย่าง .field.value หรือ #bookTitle มีโอกาสสูงว่ามันล้าสมัยแล้ว ตรวจ selector กับหน้าเว็บจริงทุกครั้งก่อนเริ่มสร้าง scraper
ถ้าอยากอ่านเพิ่มเกี่ยวกับการเลือกเครื่องมือและเฟรมเวิร์กที่เหมาะสม ลองดูคู่มือของเราที่ และ
บทสรุปและประเด็นสำคัญ
การ scrape Goodreads ด้วย Python ทำได้แน่นอน — คุณแค่ต้องรู้ว่าดักตรงไหนบ้าง สรุปสั้น ๆ คือ:
- Goodreads API หายไปแล้ว (ตั้งแต่ธันวาคม 2020) การ scrape คือวิธีหลักในการดึงข้อมูลหนังสือแบบมีโครงสร้างจากแพลตฟอร์มนี้
- ผลลัพธ์ว่าง มักเกิดจากคอนเทนต์ที่เรนเดอร์ด้วย JS, selector ที่ล้าสมัย, headers ที่ไม่ครบ หรือปัญหาการยืนยันตัวตนใน pagination — ไม่ได้แปลว่าโค้ดคุณผิดเสมอไป
- JSON-LD คือเพื่อนที่ดีที่สุดของคุณสำหรับเมตาดาต้าหนังสือ มันเสถียร มีโครงสร้าง และแทบไม่เปลี่ยน
- Pagination หลายหน้าต้องยืนยันตัวตน สำหรับ shelf และ list หลายหน้า โดยเฉพาะหลังหน้า 5 ให้ใส่คุกกี้
_session_id2 - Rate limiting มีจริง ใช้ดีเลย์ 3-8 วินาที และอย่าเกิน 5,000 requests ต่อวัน
- กลยุทธ์สองรอบ (เก็บ URL ก่อน แล้วค่อย scrape หน้ารายละเอียด) เชื่อถือได้กว่าและกลับมาทำต่อได้ง่าย
- สำหรับคนไม่อยากเขียนโค้ด (หรือใครก็ตามที่อยากได้เวลาช่วงบ่ายคืนมา) จัดการทั้งหมดนี้ — JS rendering, pagination, subpage enrichment และ export — ได้ในประมาณสองคลิก
Scrape อย่างรับผิดชอบ เคารพ robots.txt และขอให้ข้อมูลหนังสือของคุณกลับมาแบบมากกว่า [] เสมอ
คำถามที่พบบ่อย
ยังใช้ Goodreads API ได้ไหม?
ไม่ได้แล้ว Goodreads ยุติ public API ในเดือนธันวาคม 2020 และไม่ออก developer key ใหม่อีกต่อไป key เดิมที่ไม่ได้ใช้งานภายใน 30 วันจะถูกปิดอัตโนมัติ ทางเลือกตอนนี้คือการ scrape เว็บหรือใช้ API อื่น เช่น Open Library หรือ Google Books
ทำไม Goodreads scraper ของฉันถึงคืนผลลัพธ์ว่าง?
สาเหตุที่พบบ่อยที่สุดคือคอนเทนต์ที่เรนเดอร์ด้วย JavaScript Goodreads โหลดรีวิว การแบ่งเรตติ้ง และส่วนรายละเอียดหลายส่วนผ่าน React/JavaScript ซึ่ง requests.get() แบบง่าย ๆ มองไม่เห็น ให้เปลี่ยนไปใช้ Selenium หรือ Playwright สำหรับหน้าพวกนั้น สาเหตุอื่น ๆ ได้แก่ CSS selector ที่ล้าสมัย (Goodreads เปลี่ยน HTML), ขาด User-Agent headers (ทำให้โดน 403), หรือส่งคำขอแบบไม่ได้ล็อกอินบนหน้า shelf ที่มี pagination
การ scrape Goodreads ผิดกฎหมายไหม?
การ scrape ข้อมูลสาธารณะเพื่อใช้ส่วนตัวหรือเพื่อการวิจัย โดยทั่วไปยังได้รับการยอมรับภายใต้บรรทัดฐานทางกฎหมายปัจจุบัน (เช่น hiQ v. LinkedIn, Meta v. Bright Data) อย่างไรก็ตาม ToS ของ Goodreads ห้ามการเก็บข้อมูลอัตโนมัติ และคุณควรตรวจ robots.txt ของพวกเขาเสมอ หลีกเลี่ยงการ redistribe ข้อความรีวิวที่มีลิขสิทธิ์ในเชิงพาณิชย์ และจำกัดปริมาณคำขอเพื่อไม่ให้กระทบทรัพยากรของเว็บไซต์
จะ scrape หลายหน้าใน Goodreads ยังไง?
ให้เติม ?page=N ต่อท้าย URL ของ shelf หรือ list แล้ววนลูปตามหมายเลขหน้า ตรวจว่าผลลัพธ์ว่างหรือไม่มีลิงก์ "next" เพื่อรู้ว่าถึงหน้าสุดท้ายแล้ว จุดสำคัญคือบางหน้า shelf ต้องล็อกอิน (คุกกี้ _session_id2) ถึงจะได้ผลลัพธ์เกินหน้า 5 — ถ้าไม่มี คุณจะได้ข้อมูลหน้า 1 ซ้ำแบบเงียบ ๆ
ถ้าไม่อยากเขียนโค้ด สามารถ scrape Goodreads ได้ไหม?
ได้ คือ Chrome extension ที่ให้คุณ scrape Goodreads ได้ในสองคลิก — AI จะช่วยแนะนำฟิลด์ข้อมูล คุณกด "Scrape" แล้ว export ตรงไปยัง Google Sheets, Excel, Airtable หรือ Notion ได้เลย มันจัดการคอนเทนต์ที่เรนเดอร์ด้วย JavaScript, pagination และการเติมข้อมูลจากหน้าย่อยให้อัตโนมัติ โดยไม่ต้องใช้ Python หรือเขียนโค้ด
เรียนรู้เพิ่มเติม