เมื่อไม่กี่เดือนก่อน ผมอยากทำสรุปข่าวเด่นจาก Hacker News แบบรายวันให้ทีมที่ Thunderbit ใช้ดูทุกเช้า ตอนแรกผมนึกว่าแค่บุ๊กมาร์กเว็บไว้แล้วเลื่อนอ่านเองทุกวันก็น่าจะพอ แต่ทำได้แค่ราว 3 วัน ผมก็รู้เลยว่าตัวเองเสียเวลาไปวันละ 20 นาทีแค่กับการอ่านพาดหัวข่าวและคัดลอกลิงก์ลงสเปรดชีต
Hacker News เป็นแหล่งข้อมูลด้านเทคที่เข้มข้นและอัดแน่นที่สุดแห่งหนึ่งบนอินเทอร์เน็ต — มีทราฟฟิกประมาณ , มีโพสต์ใหม่ราว 1,300 เรื่องต่อวัน และคอมเมนต์อีกประมาณ 13,000 ข้อความต่อวัน ไม่ว่าคุณจะติดตามเทรนด์เทคโนโลยีใหม่ ๆ เฝ้าดูแบรนด์ของตัวเอง สร้างลิสต์ผู้สมัครงานจากเธรด "Who's Hiring" หรือแค่อยากตามให้ทันว่าคนสาย dev สนใจอะไรกันอยู่ การอัปเดตแบบแมนนวลแทบเป็นไปไม่ได้เลย
ข่าวดีคือ การสแครป Hacker News ด้วย Python ทำได้ตรงไปตรงมากว่าที่คิด และในคู่มือนี้ ผมจะพาคุณไปดู 2 วิธีแบบครบจบ — การสแครป HTML ด้วย BeautifulSoup และการใช้ HN Firebase API อย่างเป็นทางการ — พร้อมทั้งสอนเรื่อง pagination, การส่งออกข้อมูล, รูปแบบที่เหมาะกับงานจริง และทางลัดแบบไม่ต้องเขียนโค้ดเผื่อกรณีที่ Python ดูเกินความจำเป็นไปหน่อย
ทำไมต้องสแครป Hacker News ด้วย Python?
Hacker News ไม่ใช่แค่เว็บรวมลิงก์ทั่วไป แต่มันคือฟีดที่คัดสรรโดยชุมชน เรื่องที่น่าสนใจที่สุดจะไต่ขึ้นมาด้วยการโหวตและการถกเถียงอย่างคึกคัก ผู้ใช้งานส่วนใหญ่เป็นสายเทคโนโลยีอย่างชัดเจน (ประมาณ ) และสัดส่วนทราฟฟิกแบบ direct สูงถึง 66% บอกได้เลยว่านี่คือฐานผู้อ่านที่เหนียวแน่นและกลับมาใช้เป็นประจำ ไม่ใช่คนที่แค่แวะเข้ามาดูผ่าน ๆ
เหตุผลที่คนมักดึงข้อมูลจาก HN มีดังนี้:
| กรณีใช้งาน | สิ่งที่คุณจะได้ |
|---|---|
| สรุปข่าวเทครายวัน | ข่าวเด่น คะแนนโหวต และลิงก์ ส่งเข้ากล่องอีเมลหรือ Slack |
| เฝ้าระวังแบรนด์/คู่แข่ง | แจ้งเตือนเมื่อมีการพูดถึงบริษัทหรือสินค้า |
| วิเคราะห์เทรนด์ | ติดตามว่าเทคโนโลยี ภาษาโปรแกรม หรือหัวข้อใดกำลังมาแรง |
| สรรหาบุคลากร | ดึงโพสต์จาก "Who's Hiring" เพื่อหาโปรไฟล์งาน, สแตกเทค และสัญญาณเรื่องเงินเดือน |
| รีเสิร์ชคอนเทนต์ | หาหัวข้อที่มีแนวโน้มทำผลงานดี เพื่อนำไปเขียนหรือแชร์ต่อ |
| วิเคราะห์ความรู้สึกของชุมชน | ดูมุมมองของผู้คนต่อสินค้า การเปิดตัว หรือการเปลี่ยนแปลงในอุตสาหกรรม |
บริษัทมูลค่ารวมกันกว่า 4 แสนล้านดอลลาร์อย่าง Stripe, Dropbox และ Airbnb ต่างยอมรับว่าพวกเขาได้ฟีดแบ็กสำคัญและผู้ใช้ช่วงเริ่มต้นจาก Hacker News ทั้งนั้น Drew Houston เคยนำเดโมของ Dropbox ไปโพสต์บน HN ในเดือนเมษายน 2007 แล้วขึ้นอันดับ 1 จากนั้นรายชื่อรอเบตาก็พุ่งจาก 5,000 เป็น 75,000 คนภายในวันเดียว ข้อมูลจาก HN ไม่ได้แค่น่าสนใจ แต่ยังมีมูลค่าทางธุรกิจจริงด้วย
ข้อมูลนี้เปิดให้เข้าถึงสาธารณะ แต่โครงสร้างเว็บทำให้การเก็บแบบแมนนวลยุ่งยาก การใช้ Python จึงเป็นทางออกที่ใช้งานได้จริงที่สุด
2 วิธีสแครป Hacker News ด้วย Python: ภาพรวม
คู่มือนี้ครอบคลุม 2 แนวทางที่ใช้งานได้จริงและรันได้ทันที:
- การสแครป HTML ด้วย
requests+ BeautifulSoup — ดึง HTML ดิบจาก news.ycombinator.com แล้ว parse เพื่อดึงข้อมูลของแต่ละเรื่อง เหมาะสำหรับคนที่อยากเรียนพื้นฐานการสแครปและดึงเฉพาะข้อมูลที่อยู่บนหน้าเว็บ - Hacker News Firebase API อย่างเป็นทางการ — เรียก JSON endpoint โดยตรง ไม่ต้อง parse HTML เหมาะกว่าในงานที่ต้องการความเสถียร รวมถึงการดึงคอมเมนต์และข้อมูลย้อนหลัง
ด้านล่างคือการเปรียบเทียบแบบเห็นภาพเพื่อช่วยตัดสินใจว่าควรเลือกแบบไหน:
| เกณฑ์ | การสแครป HTML (requests + BS4) | HN Firebase API | Thunderbit (ไม่ต้องเขียนโค้ด) |
|---|---|---|---|
| ความซับซ้อนในการตั้งค่า | ปานกลาง (ต้อง parse HTML selector) | ต่ำ (JSON endpoint) | ไม่มี (Chrome extension คลิก 2 ครั้ง) |
| ความสดของข้อมูล | หน้าแรกแบบเรียลไทม์ | เรียลไทม์ (ดึง item ใดก็ได้ด้วย ID) | เรียลไทม์ |
| ความเสี่ยงโดนจำกัดการเรียก | ปานกลาง (robots.txt ระบุ crawl delay 30 วินาที) | ต่ำ (เป็นทางการและให้ใช้งานได้ค่อนข้างกว้าง) | Thunderbit จัดการให้ |
| การเข้าถึงคอมเมนต์ | ยาก (HTML ซ้อนกันเยอะ) | ง่าย (ไล่ item ID แบบ recursive) | ฟีเจอร์สแครป subpage |
| ข้อมูลย้อนหลัง | จำกัด | ใช้ Algolia Search API | ไม่มี |
| เหมาะที่สุดสำหรับ | เรียนพื้นฐานการสแครป | pipeline ที่เชื่อถือได้ | ผู้ใช้ไม่ใช่ dev, export เร็ว ๆ |
ทั้งสองวิธีมีโค้ด Python แบบเต็มที่รันได้จริง และถ้าคุณแค่อยากได้ข้อมูลโดยไม่อยากเขียนโค้ดเลย ผมก็มีทางเลือกนั้นให้ด้วย
ก่อนเริ่มต้น
- ระดับความยาก: ผู้เริ่มต้นถึงระดับกลาง
- เวลาโดยประมาณ: ประมาณ 15–20 นาทีต่อวิธี
- สิ่งที่ต้องมี:
- ติดตั้ง Python 3.11+ แล้ว
- Terminal หรือ code editor
- Chrome browser (ถ้าต้องการ inspect HTML ของ HN หรืออยากลองแบบไม่ใช้โค้ด)
- (ตัวเลือกเสริม สำหรับวิธีไม่เขียนโค้ด)

ตั้งค่าสภาพแวดล้อม Python
ก่อนจะไปแตะข้อมูล HN เรามาเตรียม environment กันก่อน ผมแนะนำให้สร้าง virtual environment เพื่อให้ dependencies ของโปรเจกต์เป็นระเบียบ
1# สร้างและเปิดใช้งาน virtual environment
2python3 -m venv hn-scraper
3# macOS/Linux:
4source hn-scraper/bin/activate
5# Windows:
6hn-scraper\Scripts\activate
7# ติดตั้งแพ็กเกจที่ต้องใช้ในทั้งสองวิธี
8pip install requests==2.33.1 beautifulsoup4==4.14.3 pandas==3.0.2 openpyxl==3.1.5
ถ้าจะใช้แพตเทิร์นแบบ production ต่อไปด้วย เช่น caching และ retries คุณควรติดตั้งเพิ่ม:
1pip install requests-cache==1.3.1 tenacity==9.1.4
ไม่ต้องใช้ API key ไม่ต้องยืนยันตัวตน ข้อมูลของ HN เปิดให้เข้าถึงได้อยู่แล้ว
วิธีที่ 1: สแครป Hacker News ด้วย Python โดยใช้ BeautifulSoup
นี่คือวิธีคลาสสิก — ดึง HTML มา parse แล้วค่อยหยิบข้อมูลที่ต้องการออกมา เป็นวิธีที่คนส่วนใหญ่ใช้เรียน web scraping และ layout แบบตารางของ HN ก็เหมาะมากสำหรับฝึกมือ
ขั้นตอนที่ 1: ดึงหน้าแรกของ Hacker News
เปิด editor แล้วสร้างไฟล์ชื่อ scrape_hn_bs4.py โค้ดเริ่มต้นมีดังนี้:
1import requests
2from bs4 import BeautifulSoup
3> This paragraph contains content that cannot be parsed and has been skipped.
4print(f"Status: {response.status_code}, Page length: {len(response.text)} chars")
ลองรันดู คุณควรเห็น Status: 200 และความยาวหน้าเว็บราว 40,000–50,000 ตัวอักษร นั่นคือ HTML ดิบของหน้าแรก HN ที่ถูกโหลดไว้ในหน่วยความจำ พร้อมให้ parse ต่อ
ขั้นตอนที่ 2: ทำความเข้าใจโครงสร้าง HTML
HN ใช้ layout แบบ table — ไม่มี CSS grid หรือ flex แบบเว็บสมัยใหม่ เรื่องหนึ่ง ๆ บนหน้าจะประกอบด้วย <tr> สำคัญ 2 แถว:
- แถวของเรื่อง (
<tr class="athing submission">): มีอันดับ ชื่อเรื่อง และลิงก์ - แถวข้อมูลประกอบ (แถว
<tr>ถัดมา): มีคะแนน ผู้เขียน เวลาโพสต์ และจำนวนคอมเมนต์
selector สำคัญมีดังนี้:
span.titleline > a— ชื่อเรื่องและ URLspan.score— จำนวนโหวต เช่น "118 points"a.hnuser— ชื่อผู้เขียนspan.age— เวลาที่โพสต์<a>ตัวสุดท้ายใน.subtextที่มีคำว่า "comment" — จำนวนคอมเมนต์
ถ้าคุณคลิกขวาที่ชื่อเรื่องใน Chrome แล้วเลือก "Inspect" คุณจะเห็น HTML ประมาณนี้:
1<span class="titleline">
2 <a href="https://darkbloom.dev">Darkbloom – Private inference on idle Macs</a>
3</span>
และแถวข้อมูลด้านล่าง:
1<span class="score" id="score_47788542">118 points</span>
2by <a href="user?id=twapi" class="hnuser">twapi</a>
3<span class="age" title="2026-04-16T04:06:39 1776312399">
4 <a href="item?id=47788542">2 hours ago</a>
5</span>
6| <a href="item?id=47788542">65 comments</a>
การเข้าใจ selector เหล่านี้สำคัญมาก เพราะถ้า HN เปลี่ยน markup เมื่อไหร่ คุณก็ต้องอัปเดตโค้ดตาม (สปอยล์: วิธีใช้ API จะเลี่ยงปัญหานี้ไปได้ทั้งหมด)
ขั้นตอนที่ 3: ดึงชื่อเรื่อง ลิงก์ และคะแนน
มาถึงงานจริงแล้ว เราจะวนทุกแถวของเรื่อง ดึงชื่อและลิงก์จากแถวของเรื่อง แล้วดึงคะแนนจากแถวข้อมูลที่อยู่ถัดลงมาทันที
1import requests
2from bs4 import BeautifulSoup
3from pprint import pprint
4> This paragraph contains content that cannot be parsed and has been skipped.
5stories = []
6story_rows = soup.select("tr.athing")
7for row in story_rows:
8 # ชื่อเรื่องและ URL จากแถวของเรื่อง
9 title_tag = row.select_one("span.titleline > a")
10 if not title_tag:
11 continue
12 title = title_tag.get_text()
13 link = title_tag.get("href", "")
14 # ข้อมูลประกอบจากแถวถัดไป
15 meta_row = row.find_next_sibling("tr")
16 score = 0
17 author = ""
18 comments = 0
19> This paragraph contains content that cannot be parsed and has been skipped.
20> This paragraph contains content that cannot be parsed and has been skipped.
21# กรองเฉพาะเรื่องที่มี 50+ คะแนน แล้วเรียงจากมากไปน้อย
22top_stories = sorted(
23 [s for s in stories if s["score"] >= 50],
24 key=lambda x: x["score"],
25 reverse=True,
26)
27pprint(top_stories[:10])
ข้อสังเกตเกี่ยวกับโค้ด:
- walrus operator (
:=) ใช้ได้ตั้งแต่ Python 3.8 ขึ้นไป ช่วยให้ assign และเช็กในบรรทัดเดียว เหมาะกับ element ที่อาจไม่มีเสมอ เช่นspan.score(โพสต์งานบางอันไม่มีคะแนน) - HN ใช้
\xa0ซึ่งเป็น non-breaking space คั่นระหว่างตัวเลขกับคำว่า comments จึงต้อง split ด้วยค่านี้ - เรื่องที่ลิงก์ไปหน้า HN อื่น เช่นโพสต์แบบ "Ask HN" จะมี URL แบบ relative ที่ขึ้นต้นด้วย
item?id=คุณอาจต้องเติมhttps://news.ycombinator.com/ข้างหน้าเอง
ขั้นตอนที่ 4: รันและดูผลลัพธ์
บันทึกไฟล์แล้วรัน:
1python scrape_hn_bs4.py
คุณควรเห็นผลลัพธ์ประมาณนี้:
1[{'author': 'twapi',
2 'comments': 65,
3 'score': 118,
4 'title': 'Darkbloom – Private inference on idle Macs',
5 'url': 'https://darkbloom.dev'},
6 {'author': 'sebg',
7 'comments': 203,
8 'score': 247,
9 'title': 'Show HN: I built an open-source Perplexity alternative',
10 'url': 'https://github.com/...'},
11 ...]
นั่นคือ 30 เรื่องจากหน้าแรก แต่ HN มีเรื่องที่ยัง active อยู่จำนวนมากกว่านั้น เดี๋ยวเราจะพูดเรื่อง pagination ในหัวข้อถัดไป
วิธีที่ 2: สแครป Hacker News ด้วย Python โดยใช้ API อย่างเป็นทางการ
HN Firebase API คือวิธีที่ Hacker News รองรับอย่างเป็นทางการในการเข้าถึงข้อมูล ไม่มีการยืนยันตัวตน ไม่ต้องใช้ API key และไม่ต้อง parse HTML คุณจะได้ JSON ที่สะอาดและใช้งานง่าย ผมใช้วิธีนี้กับงานที่ต้องรันได้เสถียรใน production เสมอ
endpoint หลักที่ควรรู้
Base URL คือ https://hacker-news.firebaseio.com/v0/ โดย endpoint สำคัญมีดังนี้:
This paragraph contains content that cannot be parsed and has been skipped.
ตัวอย่าง story item จะหน้าตาประมาณนี้:
1{
2 "by": "twapi",
3 "descendants": 65,
4 "id": 47788542,
5 "kids": [47789171, 47788769, 47788762],
6 "score": 118,
7 "time": 1776312399,
8 "title": "Darkbloom – Private inference on idle Macs",
9 "type": "story",
10 "url": "https://darkbloom.dev"
11}
ฟิลด์ kids จะเก็บ ID ของคอมเมนต์ลูกโดยตรง คอมเมนต์แต่ละอันก็เป็น item อีกตัวหนึ่งที่อาจมี kids ของตัวเอง นี่คือโครงสร้างของต้นไม้คอมเมนต์
ขั้นตอนที่ 1: ดึง ID ของเรื่องเด่น
สร้างไฟล์ชื่อ scrape_hn_api.py:
1import requests
2import time
3from pprint import pprint
4API_BASE = "https://hacker-news.firebaseio.com/v0"
5# ดึง ID ของเรื่องเด่น
6response = requests.get(f"{API_BASE}/topstories.json")
7story_ids = response.json()
8print(f"Got {len(story_ids)} top story IDs")
9# Output: Got 500 top story IDs
ได้ ID ของเรื่อง 500 เรื่องในคำขอเดียว — ไม่มีการ parse ไม่มี selector มีแค่อาร์เรย์ JSON
ขั้นตอนที่ 2: ดึงรายละเอียดของแต่ละเรื่องตาม ID
ต่อไปเราต้องเอาข้อมูลของแต่ละ story จริง ๆ ตรงนี้จะเห็นปัญหาเรื่อง fan-out ชัดขึ้น: ถ้ามี 500 เรื่อง ก็ต้องเรียก API แยก 500 ครั้ง จากการทดสอบของผม การเรียก item ทีละรายการแบบต่อเนื่องใช้เวลาประมาณ 1.2 วินาทีต่อครั้ง ถ้า 500 เรื่อง ก็ประมาณ 10 นาที
แต่ในหลายกรณี คุณไม่จำเป็นต้องดึงครบ 500 เรื่อง ด้านล่างคือโค้ดสำหรับดึง 30 เรื่องแรก:
1def fetch_story(story_id):
2 """ดึงรายละเอียดของ story หนึ่งรายการจาก HN API"""
3 resp = requests.get(f"{API_BASE}/item/{story_id}.json")
4 return resp.json()
5> This paragraph contains content that cannot be parsed and has been skipped.
6# เรียงตามคะแนน แล้วแสดง 10 อันดับแรก
7top = sorted(stories, key=lambda x: x["score"], reverse=True)[:10]
8pprint(top)
time.sleep(0.1) ช่วยหน่วงแบบสุภาพ แม้ Firebase API จะไม่ได้ประกาศ rate limit แบบชัดเจน แต่การยิง API รัว ๆ โดยไม่พักก็ไม่ใช่มารยาทที่ดี
ขั้นตอนที่ 3: ดึงคอมเมนต์แบบ recursive tree walk
ตรงนี้แหละที่ API เด่นกว่า HTML ชัดเจน คอมเมนต์ใน HN ซ้อนกันลึกมาก — ตอบกันไปตอบกันมาเป็นชั้น ๆ ถ้าเป็น HTML ต้อง parse ตารางที่ซ้อนกันยุ่งสุด ๆ แต่ด้วย API คอมเมนต์แต่ละตัวจะมี kids บอก ID ของลูก เราแค่ไล่ต้นไม้แบบ recursive ได้เลย
1def fetch_comments(item_id, depth=0, max_depth=3):
2 """ดึงคอมเมนต์แบบ recursive จนถึง max_depth"""
3 item = requests.get(f"{API_BASE}/item/{item_id}.json").json()
4 if not item or item.get("type") != "comment":
5 return []
6> This paragraph contains content that cannot be parsed and has been skipped.
7 if depth < max_depth and item.get("kids"):
8 for kid_id in item["kids"]:
9 comments.extend(fetch_comments(kid_id, depth + 1, max_depth))
10 time.sleep(0.05)
11 return comments
12# ตัวอย่าง: ดึงคอมเมนต์ของเรื่องเด่นอันดับแรก
13if stories:
14 top_story = stories[0]
15 top_story_full = requests.get(f"{API_BASE}/item/{top_story['id']}.json").json()
16 if top_story_full.get("kids"):
17 print(f"\nComments for: {top_story['title']}")
18 all_comments = []
19 for kid_id in top_story_full["kids"][:5]: # คอมเมนต์ชั้นบนสุด 5 อันแรก
20 all_comments.extend(fetch_comments(kid_id, depth=0, max_depth=2))
21 time.sleep(0.1)
22 for c in all_comments[:15]:
23 indent = " " * c["depth"]
24 preview = c["text"][:80].replace("\n", " ") if c["text"] else "[no text]"
25 print(f"{indent}[{c['author']}] {preview}...")
วิธี recursive แบบนี้ง่ายกว่าการพยายาม parse thread คอมเมนต์ที่ซ้อนกันใน HTML มาก ถ้าคุณต้องการ tree ของคอมเมนต์แบบเต็ม ๆ API คือคำตอบ
ขั้นตอนที่ 4: รันและดูผลลัพธ์
1python scrape_hn_api.py
คุณจะเห็นข้อมูลของเรื่องในรูปแบบมีโครงสร้าง ตามด้วยตัวอย่างคอมเมนต์ที่ซ้อนกัน ข้อมูลสะอาดกว่า การเข้าถึงคอมเมนต์ก็ง่ายกว่า และไม่ต้องกลัวว่าตัวสแครปเปอร์จะพังเพราะ HN เปลี่ยนชื่อ CSS class
ไปให้ไกลกว่าหน้า 1: Pagination และข้อมูลย้อนหลัง
บทความสแครป HN ส่วนใหญ่มักจบแค่หน้า 1 — 30 เรื่อง เท่านั้น ซึ่งก็พอสำหรับเดโมเร็ว ๆ แต่ในงานจริงคุณมักต้องการข้อมูลมากกว่านั้น
สแครปหลายหน้าด้วย BeautifulSoup
ระบบ pagination ของ HN ใช้แพตเทิร์น URL ง่าย ๆ: ?p=2, ?p=3 และอื่น ๆ แต่ละหน้ามี 30 เรื่อง และเว็บจะให้ข้อมูลได้ประมาณถึงหน้า 20 (ราว 600 เรื่องรวม) จากนั้นจะเริ่มเป็นหน้าว่าง
1import time
2def scrape_hn_pages(num_pages=5):
3 """สแครปหลายหน้าจากหน้าแรกของ HN"""
4 all_stories = []
5 for page in range(1, num_pages + 1):
6 url = f"https://news.ycombinator.com/news?p={page}"
7 response = requests.get(url, headers=headers)
8 soup = BeautifulSoup(response.text, "html.parser")
9 story_rows = soup.select("tr.athing")
10 if not story_rows:
11 print(f"Page {page}: no stories found, stopping.")
12 break
13 for row in story_rows:
14 title_tag = row.select_one("span.titleline > a")
15 if not title_tag:
16 continue
17 meta_row = row.find_next_sibling("tr")
18 score = 0
19 if meta_row and (score_tag := meta_row.select_one("span.score")):
20 score = int(score_tag.get_text().replace(" points", ""))
21> This paragraph contains content that cannot be parsed and has been skipped.
22 print(f"Page {page}: scraped {len(story_rows)} stories")
23 # เคารพ crawl-delay 30 วินาทีตาม robots.txt
24 if page < num_pages:
25 time.sleep(30)
26 return all_stories
27stories = scrape_hn_pages(5)
28print(f"\nTotal stories scraped: {len(stories)}")
time.sleep(30) สำคัญมาก HN ระบุไว้ใน ว่าต้องการ crawl delay 30 วินาที หากไม่ทำตาม คุณอาจโดนจำกัดความถี่ (HTTP 429) หรือถูกบล็อกชั่วคราว การดึง 5 หน้าโดยหน่วงทุกครั้งใช้เวลาประมาณ 2.5 นาที — ไม่เร็ว แต่สุภาพ
สำหรับคนที่ไม่อยากจัดการ pagination เอง รองรับทั้ง pagination แบบคลิก และแบบ infinite scroll แบบอัตโนมัติ มันคลิกปุ่ม "More" ที่ท้ายหน้า HN ให้เลยโดยไม่ต้องตั้งค่าอะไรเพิ่ม
ดึงข้อมูล Hacker News ย้อนหลังด้วย Algolia API
Firebase API ให้ข้อมูลปัจจุบัน แต่ถ้าคุณต้องการวิเคราะห์ย้อนหลัง — เช่น "เรื่องเกี่ยวกับ Python ที่ติดอันดับในปี 2023 มีอะไรบ้าง" หรือ "สัดส่วนข่าว AI เปลี่ยนไปอย่างไรในช่วง 5 ปีที่ผ่านมา" — คุณต้องใช้
1import requests
2ALGOLIA_BASE = "https://hn.algolia.com/api/v1"
3> This paragraph contains content that cannot be parsed and has been skipped.
4# ตัวอย่าง: หาเรื่องเกี่ยวกับ Python scraping ที่มีคะแนน 10+ ตั้งแต่ ม.ค. 2024
5results = search_hn(
6 query="python scraping",
7 tags="story",
8)
9print(f"Found {results['nbHits']} total results")
10for hit in results["hits"][:5]:
11 print(f" [{hit.get('points', 0)} pts] {hit['title']}")
ถ้าต้องการค้นหาแบบกรองตามวันที่ ให้ใช้ numericFilters:
1import calendar, datetime
2# เรื่องตั้งแต่ 1 มกราคม 2024
3start_date = datetime.datetime(2024, 1, 1)
4start_ts = int(calendar.timegm(start_date.timetuple()))
5> This paragraph contains content that cannot be parsed and has been skipped.
6Algolia API เร็วมาก (เวลาประมวลผลฝั่งเซิร์ฟเวอร์ประมาณ 5–9 ms) ไม่ต้องใช้ API key และรองรับ pagination ได้ถึง 500 หน้า สำหรับการวิเคราะห์ข้อมูลย้อนหลังปริมาณมาก นี่คือตัวเลือกที่ดีที่สุด
7## ส่งออกข้อมูล Hacker News ที่สแครปมาเป็น CSV, Excel และ Google Sheets
8บทความสแครป HN ที่ผมเคยเห็นส่วนใหญ่จบด้วย `pprint()` บนเทอร์มินัล ซึ่งดีสำหรับการ debug แต่ถ้าคุณจะทำสรุปรายวันหรือวิเคราะห์เทรนด์ คุณต้องเอาข้อมูลไปไว้ในไฟล์ นี่คือวิธีทำ
9### ส่งออกเป็น CSV ด้วย Python
10```python
11import csv
12def export_to_csv(stories, filename="hn_stories.csv"):
13 """บันทึกเรื่องที่สแครปมาเป็นไฟล์ CSV"""
14 fieldnames = ["title", "url", "score", "author", "comments"]
15 with open(filename, "w", newline="", encoding="utf-8") as f:
16 writer = csv.DictWriter(f, fieldnames=fieldnames)
17 writer.writeheader()
18 writer.writerows(stories)
19 print(f"Saved {len(stories)} stories to {filename}")
20export_to_csv(stories)
ส่งออกเป็น Excel ด้วย Python
1import pandas as pd
2def export_to_excel(stories, filename="hn_stories.xlsx"):
3 """บันทึกเรื่องที่สแครปมาเป็นไฟล์ Excel"""
4 df = pd.DataFrame(stories)
5 df.to_excel(filename, index=False, engine="openpyxl")
6 print(f"Saved {len(stories)} stories to {filename}")
7export_to_excel(stories)
อย่าลืมติดตั้ง openpyxl เพราะ pandas ใช้มันเป็น engine สำหรับ Excel ถ้าไม่มีจะเจอ ImportError
ส่งเข้า Google Sheets โดยตรง (ตัวเลือกเสริม)
ถ้าจะทำ workflow แบบอัตโนมัติ คุณอาจต้องส่งข้อมูลเข้า Google Sheets โดยตรงผ่านไลบรารี gspread วิธีนี้ต้องตั้งค่า Google Cloud service account ก่อนครั้งหนึ่ง:
1import gspread
2gc = gspread.service_account(filename="service_account.json")
3sh = gc.open("HN Daily Digest")
4worksheet = sh.sheet1
5# แปลง stories เป็นแถว
6header = list(stories[0].keys())
7rows = [list(s.values()) for s in stories]
8worksheet.clear()
9worksheet.update([header] + rows)
10print("Pushed to Google Sheets")
ทางเลือกแบบไม่ต้องเขียนโค้ดสำหรับการ export
ถ้าการตั้ง service account และเขียนโค้ดส่งออกดูยุ่งกว่าตัวงานสแครปเอง ผมเข้าใจเลย ที่ Thunderbit เราทำฟีเจอร์ export ฟรีที่ส่งข้อมูลที่สแครปมาไปยัง Excel, Google Sheets, Airtable หรือ Notion ได้ตรง ๆ — ไม่ต้องเขียนโค้ด ไม่ต้องใช้ credentials และไม่ต้องดูแล pipeline เอง สำหรับการดึงข้อมูลครั้งเดียว มันเร็วกว่าแบบเห็นได้ชัด เดี๋ยวผมจะอธิบายเพิ่มด้านล่าง
ทำให้สแครปเปอร์พร้อมใช้งานจริง: จัดการ error, caching และการตั้งเวลา
ถ้าคุณรันสแครปเปอร์เล่น ๆ ครั้งเดียว โค้ดข้างบนก็เพียงพอ แต่ถ้าจะให้มันรันทุกวันใน workflow จริง คุณต้องมีองค์ประกอบเพิ่มอีกหน่อย
การจัดการ error และ retry
เครือข่ายมีล่มได้ เซิร์ฟเวอร์อาจ throttle ได้ request ที่พลาดครั้งเดียวไม่ควรทำให้การสแครปทั้งหมดพัง นี่คือฟังก์ชัน retry พร้อม exponential backoff:
1from tenacity import retry, stop_after_attempt, wait_exponential_jitter
2import requests
3@retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=1, max=60))
4def fetch_with_retry(url):
5 """ดึง URL พร้อม retry อัตโนมัติและ exponential backoff"""
6 response = requests.get(url, timeout=10)
7 response.raise_for_status()
8 return response
9# วิธีใช้:
10try:
11 resp = fetch_with_retry("https://hacker-news.firebaseio.com/v0/topstories.json")
12 story_ids = resp.json()
13except Exception as e:
14 print(f"Failed after retries: {e}")
ไลบรารี tenacity จัดการ retry ได้สวยงาม มันจะลองใหม่สูงสุด 5 ครั้งด้วย exponential backoff ที่สุ่ม jitter โดยเริ่มที่ 1 วินาที และสูงสุด 60 วินาที ช่วยรองรับ HTTP 429 (ถูกจำกัดความถี่), 503 (บริการไม่พร้อมใช้งาน) และปัญหาเครือข่ายชั่วคราวได้ดี
แคชผลลัพธ์เพื่อลดการดึงซ้ำ
ตอนพัฒนา คุณอาจต้องรันสแครปเปอร์หลายครั้งเพื่อปรับ logic การ parse ถ้าไม่มี cache ทุกครั้งจะยิงไปที่เซิร์ฟเวอร์ HN ใหม่หมด requests-cache แก้ได้ใน 2 บรรทัด:
1import requests_cache
2requests_cache.install_cache("hn_cache", expire_after=3600) # แคชไว้ 1 ชั่วโมง
หลังเพิ่มโค้ดนี้ไว้บนสุดของสคริปต์ requests.get() ทุกครั้งจะถูกแคชลงฐานข้อมูล SQLite ในเครื่องโดยอัตโนมัติ รันสคริปต์ซ้ำ 10 ครั้งในชั่วโมงเดียว คำขอจริงจะเกิดแค่ครั้งแรกเท่านั้น นี่คือเครื่องมือที่ และก็มีเหตุผลรองรับชัดเจน
แยกขั้นตอน crawl ออกจาก parse
แพตเทิร์นที่นักสแครปเปอร์สายอาชีพชอบใช้คือ: ดาวน์โหลดข้อมูลดิบก่อน แล้วค่อย parse ทีหลัง แบบนี้ถ้า logic การ parse มีบั๊ก คุณแก้แล้ว parse ซ้ำได้โดยไม่ต้องดึงข้อมูลใหม่
1import os, json
2def crawl_and_save(story_ids, output_dir="raw_data"):
3 """ดึงข้อมูล story แล้วบันทึก JSON ดิบลงดิสก์"""
4 os.makedirs(output_dir, exist_ok=True)
5 for sid in story_ids:
6 filepath = os.path.join(output_dir, f"{sid}.json")
7 if os.path.exists(filepath):
8 continue # ข้ามรายการที่ดึงไว้แล้ว
9 resp = fetch_with_retry(f"{API_BASE}/item/{sid}.json")
10 with open(filepath, "w") as f:
11 json.dump(resp.json(), f)
12> This paragraph contains content that cannot be parsed and has been skipped.
13แนวทางสองขั้นแบบนี้มีประโยชน์มากเมื่อคุณสแครปข้อมูลเป็นร้อย ๆ รายการและอยากปรับวิธีประมวลผลให้เร็วขึ้น
14### ตั้งเวลาให้สแครปเปอร์ทำงานอัตโนมัติ
15ถ้าจะทำ HN digest รายวัน คุณต้องให้สแครปเปอร์รันเองอัตโนมัติ ตัวเลือกที่นิยมมี 2 แบบ:
16**ตัวเลือก 1: cron (Linux/Mac)**
17```bash
18# รันทุกวันเวลา 8:30 น. UTC
1930 8 * * * /usr/bin/python3 /home/user/scrape_hn.py >> /home/user/scrape.log 2>&1
ตัวเลือก 2: GitHub Actions (ฟรี ไม่ต้องมีเซิร์ฟเวอร์)
1name: Scrape Hacker News
2on:
3 schedule:
4 - cron: '30 8 * * *' # ทุกวันเวลา 8:30 UTC
5 workflow_dispatch: # ปุ่มรันเองแบบ manual
6jobs:
7 scrape:
8 runs-on: ubuntu-latest
9 steps:
10 - uses: actions/checkout@v4
11 - uses: actions/setup-python@v6
12 with:
13 python-version: '3.12'
14 - run: pip install requests beautifulsoup4 pandas openpyxl
15 - run: python scrape_hn.py
16 - run: |
17 git config user.name "GitHub Actions Bot"
18 git config user.email "actions@github.com"
19 git add -A
20 git diff --staged --quiet || git commit -m "Update HN data $(date -u +%Y-%m-%dT%H:%M:%SZ)"
21 git push
ข้อควรระวังของการตั้งเวลาด้วย GitHub Actions: cron ทั้งหมดใช้เวลา UTC, การหน่วงอาจเกิดขึ้นได้ 15–60 นาทีบ่อยครั้ง (ควรเลือกเวลาไม่ตรงชั่วโมง เช่น :30 แทน :00) และ GitHub อาจปิด workflow ที่ scheduled ไว้ถ้า repo ไม่มี activity นาน 60 วัน ดังนั้นควรใส่ workflow_dispatch เสมอเพื่อกดรันเองตอนทดสอบได้
ถ้าอยากได้ทางเลือกที่ง่ายกว่านั้น Thunderbit มี Scheduled Scraper ที่ให้คุณพิมพ์บอกเป็นภาษาธรรมดาได้ เช่น "สแครปทุกเช้า 8 โมง" โดยไม่ต้องตั้ง server หรือ cron เอง
เมื่อ Python เกินความจำเป็น: วิธีไม่ต้องเขียนโค้ดสำหรับสแครป Hacker News
ขอพูดตรง ๆ ทั้งที่ผมชอบ Python และทีมของผมก็สร้างเครื่องมือสำหรับ developer อยู่แล้ว ถ้าคุณแค่อยากได้ 100 ข่าวเด่นของ HN วันนี้ลงสเปรดชีต — ตอนนี้เลย แบบครั้งเดียว — การเขียน ดีบัก และรันสคริปต์ Python ถือว่าเกินจำเป็น การตั้งค่าอย่างเดียว (virtual environment, ติดตั้งแพ็กเกจ, ไล่หาตัวเลือก selector) ก็ใช้เวลามากกว่าตัวงานดึงข้อมูลจริงเสียอีก
นี่คือจุดที่ เข้ามาช่วยได้ เวิร์กโฟลว์เป็นแบบนี้:
- เปิด
news.ycombinator.comใน Chrome - คลิกไอคอน Thunderbit แล้วเลือก "AI Suggest Fields"
- AI จะอ่านหน้าเว็บและเสนอคอลัมน์ให้ เช่น Title, URL, Score, Author, Comment Count, Time Posted
- ปรับฟิลด์ได้ตามต้องการ (เปลี่ยนชื่อ ลบ หรือเพิ่มฟิลด์เอง — จะใส่ prompt แบบ "Categorize as AI/DevTools/Web/Other" ก็ได้)
- คลิก "Scrape" — ข้อมูลจะออกมาเป็นตารางที่มีโครงสร้าง
- Export ไป Excel, Google Sheets, Airtable หรือ Notion
แค่คลิกสองครั้งก็ได้ข้อมูลเป็นตาราง ไม่ต้องมี selector ไม่ต้องเขียนโค้ด ไม่ต้องดูแลต่อ
ข้อดีอีกอย่างคือ AI ของ Thunderbit ปรับตัวตามการเปลี่ยน layout ได้อัตโนมัติ สคริปต์ที่พึ่ง CSS selector แบบดั้งเดิมมักพังเมื่อเว็บไซต์เปลี่ยน markup — และถึง HN จะมี HTML ค่อนข้างนิ่ง แต่มันก็เคยเปลี่ยนมาแล้ว (class="athing submission" ถูกอัปเดต และ span.titleline มาแทน a.storylink แบบเดิม) สแครปเปอร์ที่ขับเคลื่อนด้วย AI จะอ่านหน้าเว็บใหม่ทุกครั้ง จึงไม่แคร์ว่าชื่อ class จะเปลี่ยนหรือไม่

Thunderbit ยังจัดการ pagination ได้ด้วย (คลิกปุ่ม "More" ของ HN ให้อัตโนมัติ) และสแครป subpage ได้ (เข้าไปยังหน้าคอมเมนต์ของแต่ละเรื่องเพื่อดึงข้อมูลการสนทนา) สำหรับกรณี นี่เทียบได้กับโค้ด recursive ในวิธีที่ 2 แต่ไม่ต้องเขียนแม้แต่บรรทัดเดียว
ข้อแลกเปลี่ยนก็ชัดเจน: Python เหมาะเมื่อคุณต้องการ logic เฉพาะ การแปลงข้อมูลซับซ้อน pipeline อัตโนมัติแบบตั้งเวลา หรือกำลังเรียนเขียนโค้ด Thunderbit เหมาะเมื่อคุณต้องการข้อมูลเร็ว ๆ ไม่อยากดูแลโค้ด หรือไม่ได้เป็น developer เลือกเครื่องมือให้ตรงกับสถานการณ์จะดีที่สุด
Python vs API vs No-code: ควรเลือกวิธีไหนดี?
นี่คือกรอบตัดสินใจแบบเต็ม:
| เกณฑ์ | BeautifulSoup (HTML) | Firebase API | Algolia API | Thunderbit (ไม่ต้องเขียนโค้ด) |
|---|---|---|---|---|
| ทักษะเทคนิคที่ต้องมี | Python ระดับกลาง | Python ระดับเริ่มต้น | Python ระดับเริ่มต้น | ไม่ต้องมี |
| เวลาในการตั้งค่า | 10–15 นาที | 5–10 นาที | 5–10 นาที | 2 นาที |
| ภาระการดูแล | ปานกลาง (selector อาจพัง) | ต่ำ (JSON เสถียร) | ต่ำ (JSON เสถียร) | ไม่มี |
| ความลึกของข้อมูล | หน้าแรกเท่านั้น | item ใดก็ได้, ผู้ใช้ | ค้นหา + ข้อมูลย้อนหลัง | หน้าแรก + subpage |
| คอมเมนต์ | ยาก | ง่าย (recursive) | ง่าย (nested tree) | สแครป subpage |
| ข้อมูลย้อนหลัง | ไม่มี | ไม่มี | มี (archive เต็ม) | ไม่มี |
| ตัวเลือก export | ต้องเขียนเอง | ต้องเขียนเอง | ต้องเขียนเอง | มีในตัว (Excel, Sheets, ฯลฯ) |
| การตั้งเวลา | cron / GitHub Actions | cron / GitHub Actions | cron / GitHub Actions | มี scheduler ในตัว |
| เหมาะที่สุดสำหรับ | เรียนการสแครป | pipeline ที่เชื่อถือได้ | งานรีเสิร์ชและวิเคราะห์ | ดึงข้อมูลเร็ว ๆ |
ถ้าคุณกำลังเรียน Python หรือทำของเฉพาะทาง ลองวิธีที่ 1 หรือ 2 ถ้าต้องการวิเคราะห์ย้อนหลัง ให้เพิ่ม Algolia API ถ้าคุณแค่อยากได้ข้อมูลโดยไม่เขียนโค้ดเลย
บทสรุปและประเด็นสำคัญ
ตอนนี้คุณมีเครื่องมือในมือครบแล้ว:
- 2 วิธี Python แบบเต็ม สำหรับสแครป Hacker News — BeautifulSoup สำหรับ parse HTML และ Firebase API สำหรับข้อมูล JSON ที่สะอาด
- เทคนิค pagination สำหรับสแครปเกินหน้า 1 รวมถึง Algolia API สำหรับข้อมูลย้อนหลังตั้งแต่ปี 2007
- โค้ด export ไป CSV, Excel และ Google Sheets — เพราะข้อมูลที่อยู่แค่ในเทอร์มินัลไม่ได้ช่วยทีมของคุณเท่าไร
- แพตเทิร์นแบบ production — retry logic, caching, แยก crawl/parse และตั้งเวลาอัตโนมัติด้วย cron หรือ GitHub Actions
- ทางเลือกไม่ต้องเขียนโค้ด เผื่อเวลาที่ Python ดูเกินกว่าที่ต้องการ
คำแนะนำของผมคือ เริ่มจาก Firebase API (วิธีที่ 2) สำหรับงานส่วนใหญ่ เพราะมันสะอาด เสถียร และดึงคอมเมนต์ได้โดยไม่ต้องปวดหัวกับ HTML ที่ซ้อนกัน ใช้ Algolia API เพิ่มเมื่อคุณต้องการข้อมูลย้อนหลัง และเก็บ ไว้ในบุ๊กมาร์กสำหรับวันที่คุณแค่ต้องการสเปรดชีตเร็ว ๆ โดยไม่อยากตั้งโปรเจกต์ Python ขึ้นมาใหม่
ถ้าอยากลงลึกกว่านี้ ลองสแครปคอมเมนต์ HN เพื่อทำ , สร้าง pipeline สรุปรายวันด้วย GitHub Actions หรือสำรวจ Algolia API เพื่อดูว่าเทรนด์เทคโนโลยีเปลี่ยนไปอย่างไรในรอบสิบปีที่ผ่านมา
คำถามที่พบบ่อย
การสแครป Hacker News ผิดกฎหมายไหม?
ข้อมูลของ HN เปิดให้เข้าถึงสาธารณะ และ Y Combinator ก็มี API อย่างเป็นทางการสำหรับการใช้งานแบบโปรแกรมอยู่แล้ว ของเว็บอนุญาตให้สแครปเนื้อหาแบบอ่านอย่างเดียวได้ (หน้าแรก หน้ารายการ item และหน้าผู้ใช้) แต่ขอให้หน่วงเวลา crawl 30 วินาทีด้วย เคารพเวลาหน่วง ไม่สแครปจุดที่ต้องโต้ตอบ (เช่น โหวตหรือเข้าสู่ระบบ) ก็ถือว่าอยู่ในกรอบที่ปลอดภัย ถ้าอยากอ่านเพิ่มเติมเรื่องจริยธรรมของ web scraping ดูคู่มือ ของเรา
Hacker News มี API อย่างเป็นทางการไหม?
มีครับ ที่ hacker-news.firebaseio.com/v0/ ใช้ฟรี ไม่ต้องยืนยันตัวตน และให้เข้าถึง stories, comments, user profiles รวมถึงฟีดทุกประเภท (top, new, best, ask, show, jobs) ได้ ข้อมูลที่ได้เป็น JSON สะอาด และไม่ได้ประกาศ rate limit ไว้ชัดเจน แต่ก็ยังควรเรียกใช้อย่างสุภาพอยู่ดี
ฉันจะสแครปคอมเมนต์ของ Hacker News ด้วย Python ได้อย่างไร?
ถ้าใช้ Firebase API ให้ดึง story item เพื่อเอาฟิลด์ kids ซึ่งเป็นอาร์เรย์ของ ID คอมเมนต์ระดับบนสุด คอมเมนต์แต่ละตัวก็เป็น item ที่มี kids ของตัวเองสำหรับรีพลาย จากนั้นใช้ฟังก์ชัน recursive ไล่ดึงคอมเมนต์และลูกของมันทีละชั้น ดูหัวข้อ "Scrape Comments (Recursive Tree Walk)" ด้านบนสำหรับโค้ดครบ หรือจะใช้ เพื่อดึงต้นไม้คอมเมนต์แบบเต็มในคำขอเดียวก็ได้ ซึ่งเร็วมากสำหรับเรื่องที่คอมเมนต์เยอะ
สแครป Hacker News โดยไม่เขียนโค้ดได้ไหม?
ได้ครับ ทำงานเป็น Chrome extension — เปิด HN แล้วคลิก "AI Suggest Fields" ระบบจะระบุคอลัมน์อย่าง title, URL, score และ author ให้อัตโนมัติ จากนั้นคลิก "Scrape" แล้ว export ไป Excel, Google Sheets, Airtable หรือ Notion ได้ทันที มันรองรับ pagination และยังเข้าไปดึงข้อมูลจาก subpage เพื่อเก็บคอมเมนต์ได้ด้วย ไม่ต้องใช้ Python ไม่ต้องไล่ selector ไม่ต้องดูแลโค้ด
จะเอาข้อมูล Hacker News แบบย้อนหลังได้อย่างไร?
คือเครื่องมือที่ดีที่สุดสำหรับเรื่องนี้ ใช้ endpoint search_by_date ร่วมกับ numericFilters=created_at_i>TIMESTAMP เพื่อกรองตามช่วงวันที่ คุณค้นหาด้วยคีย์เวิร์ด กรองตามประเภท story และไล่ pagination ได้ถึง 500 หน้า สำหรับการวิเคราะห์ย้อนหลังเป็นจำนวนมาก ยังมี public dataset บน (archive เต็ม), (28 ล้านเรคคอร์ด) และ (4 ล้านเรื่อง) ด้วย
เรียนรู้เพิ่มเติม