قبل بضعة أشهر، كنت أريد أجهّز ملخّص يومي لأهم قصص Hacker News لفريقنا في Thunderbit. أول فكرة خطرت ببالي كانت أني أحفظ الموقع في المفضلة وأفتحه كل صباح. استمر هذا الروتين حوالي ثلاثة أيام فقط، وبعدها انتبهت أني أضيّع 20 دقيقة يوميًا فقط في قراءة العناوين ولصق الروابط يدويًا داخل جدول بيانات.
Hacker News يُعتبر من أغنى المصادر وأكثرها تركيزًا على ذكاء التقنية في الإنترنت — بحوالي ، ونحو 1,300 قصة جديدة يوميًا، وما يقارب 13,000 تعليق يوميًا. سواء كنت تتابع الاتجاهات التقنية الجديدة، أو تراقب علامتك التجارية، أو تبني قناة توظيف من خلال خيوط "Who's Hiring"، أو حتى تحاول فقط تواكب ما يهتم به عالم المطورين، فمتابعة كل هذا يدويًا معركة خاسرة.
الخبر الحلو: استخراج بيانات Hacker News باستخدام Python أبسط بكثير مما تتوقع. في هذا الدليل، سأشرح لك طريقتين كاملتين — استخراج HTML باستخدام BeautifulSoup وواجهة HN Firebase API الرسمية — مع شرح الترقيم بين الصفحات، وتصدير البيانات، وأنماط جاهزة للاستخدام في الإنتاج، بالإضافة إلى بديل بلا كود عندما تشعر أن Python أكثر مما تحتاج.
لماذا نستخرج بيانات Hacker News باستخدام Python؟
Hacker News ليس مجرد مجمّع روابط عادي. هو موجز منتقى من المجتمع، حيث ترتفع أكثر القصص التقنية إثارة إلى الأعلى بفضل التصويتات والنقاشات النشطة. الجمهور يميل بشكل كبير إلى المتخصصين في التقنية ()، كما أن معدل الزيارات المباشرة البالغ 66% يوضح أن القراء أوفياء ومعتادون على الموقع، وليسوا زائرين عابرين.
إليك أسباب قيام الناس باستخراج بيانات HN:
| حالة الاستخدام | ما الذي تحصل عليه |
|---|---|
| ملخّص تقني يومي | أهم القصص، النقاط، والروابط تصل إلى بريدك أو Slack |
| مراقبة العلامة التجارية/المنافسين | تنبيهات عندما يتم ذكر شركتك أو منتجك |
| تحليل الاتجاهات | تتبع التقنيات أو اللغات أو المواضيع التي تكتسب زخمًا بمرور الوقت |
| التوظيف | تحليل خيوط "Who's Hiring" لاستخراج الوظائف، وحِزم التقنية، وإشارات الرواتب |
| بحث المحتوى | العثور على مواضيع ذات أداء عالٍ للكتابة عنها أو مشاركتها |
| تحليل المشاعر | قياس رأي المجتمع تجاه المنتجات، والإطلاقات، والتحولات في الصناعة |
شركات تبلغ قيمتها مجتمعة أكثر من 400 مليار دولار — مثل Stripe وDropbox وAirbnb — تنسب إلى Hacker News جزءًا مهمًا من أولى ملاحظاتها وأول جمهور لها. نشر Drew Houston عرض Dropbox التجريبي على HN في أبريل 2007، وطلع إلى المركز الأول، وقفزت قائمة الانتظار للنسخة التجريبية من 5,000 إلى 75,000 مستخدم في يوم واحد فقط. بيانات HN ليست مجرد شيء ممتع للاطلاع عليه — بل لها قيمة تجارية حقيقية أيضًا.
البيانات متاحة للعامة، لكن بنية الموقع تجعل جمعها يدويًا متعبًا. الأتمتة باستخدام Python هي الحل العملي.
طريقتان لاستخراج Hacker News باستخدام Python: نظرة عامة
هذا الدليل يغطي طريقتين كاملتين وقابلتين للتشغيل:
- استخراج HTML باستخدام
requests+ BeautifulSoup — جلب HTML الخام من news.ycombinator.com وتحليله لاستخراج بيانات القصص. ممتاز جدًا لتعلّم أساسيات الاستخراج والحصول على ما يظهر في الصفحة بالضبط. - واجهة Hacker News Firebase API الرسمية — الاتصال مباشرة بنقاط JSON من دون الحاجة إلى تحليل HTML. أفضل لخطوط البيانات الموثوقة، والوصول إلى التعليقات، والبيانات التاريخية.
إليك مقارنة سريعة تساعدك تختار الأنسب:
| المعيار | استخراج HTML (requests + BS4) | HN Firebase API | Thunderbit (بلا كود) |
|---|---|---|---|
| تعقيد الإعداد | متوسط (تحليل محددات HTML) | منخفض (نقاط JSON) | لا يوجد (إضافة Chrome من خطوتين) |
| حداثة البيانات | الصفحة الرئيسية مباشرة | مباشر (أي عنصر عبر ID) | مباشر |
| خطر الحظر أو تحديد المعدل | متوسط (robots.txt يطلب 30 ثانية بين الطلبات) | منخفض (رسمي وسخي) | تتم إدارته بواسطة Thunderbit |
| الوصول إلى التعليقات | صعب (HTML متداخل) | سهل (IDs متكررة بشكل递归) | ميزة استخراج الصفحات الفرعية |
| البيانات التاريخية | محدودة | عبر Algolia Search API | غير متوفر |
| الأفضل لـ | تعلّم أساسيات الاستخراج | خطوط بيانات موثوقة | غير المطورين، والتصدير السريع |
كلتا الطريقتين تتضمنان كود Python كاملًا وقابلًا للتشغيل. وإذا كنت تريد البيانات فقط من دون كتابة أي كود، فسأغطي ذلك أيضًا.
قبل أن تبدأ
- مستوى الصعوبة: مبتدئ إلى متوسط
- الوقت المطلوب: نحو 15–20 دقيقة لكل طريقة
- ما ستحتاج إليه:
- Python 3.11+ مثبت
- طرفية أو محرر أكواد
- متصفح Chrome (إذا أردت فحص HTML الخاص بـ HN أو تجربة الخيار بلا كود)
- (اختياري، للطريقة بلا كود)

إعداد بيئة Python
قبل ما نلمس أي بيانات من HN، خلّينا نجهز البيئة. أنصحك تنشئ بيئة افتراضية حتى تبقى تبعيات المشروع مرتبة.
1# إنشاء وتفعيل بيئة افتراضية
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
ولأنماط الإنتاج لاحقًا (مثل التخزين المؤقت وإعادة المحاولة)، ستحتاج أيضًا إلى:
1pip install requests-cache==1.3.1 tenacity==9.1.4
ما فيه مفاتيح API خاصة ولا رموز مصادقة. بيانات HN مفتوحة.
الطريقة الأولى: استخراج Hacker News باستخدام Python عبر BeautifulSoup
هذه هي الطريقة الكلاسيكية — نجلب HTML، ثم نحلله، ثم نستخرج البيانات المطلوبة. هذا غالبًا أول شيء يتعلمه الناس عند دخول عالم web scraping، وتصميم HN البسيط المعتمد على الجداول يجعله ساحة تدريب ممتازة.
الخطوة 1: جلب الصفحة الرئيسية لـ Hacker News
افتح المحرر وأنشئ ملفًا باسم 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 داخل الذاكرة، وجاهز للتحليل.
الخطوة 2: فهم بنية HTML
HN يستخدم تصميمًا قائمًا على الجداول — ما فيه CSS grid أو flexbox حديث. كل قصة في الصفحة تتكون من صفَّي <tr> أساسيين:
- صف القصة (
<tr class="athing submission">): يحتوي على الترتيب والعنوان والرابط - صف البيانات الوصفية (الصف
<tr>التالي): يحتوي على النقاط، والكاتب، والوقت، وعدد التعليقات
أهم المحددات:
span.titleline > a— عنوان القصة والرابطspan.score— عدد الأصوات (مثلًا: "118 points")a.hnuser— اسم المستخدم للكاتبspan.age— وقت النشر- آخر
<a>داخل.subtextوالذي يحتوي على كلمة "comment" — عدد التعليقات
إذا نقرت بزر الفأرة الأيمن على أي عنوان قصة في Chrome واخترت "Inspect"، فسترى شيئًا مثل هذا:
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>
فهم هذه المحددات مهم جدًا — إذا غيّر HN ترميز الصفحة يومًا ما، فستحتاج إلى تحديثها. (وللتوضيح: طريقة الـ 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 # العنوان والرابط من صف القصة
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])
بعض الملاحظات على الكود:
- عامل التعيين داخل الشرط (
:=) يعمل في Python 3.8+. يتيح لنا التعيين والتحقق في سطر واحد — وهو مفيد للعناصر الاختيارية مثلspan.scoreالتي قد لا تكون موجودة في كل صف (مثل منشورات الوظائف التي لا تملك نقاطًا). - يستخدم HN الرمز
\xa0(مسافة غير قابلة للكسر) بين الرقم وكلمة "comments"، لذا نقسم عليه. - القصص التي تشير إلى صفحات داخل HN نفسه (مثل منشورات "Ask HN") سيكون لها روابط نسبية تبدأ بـ
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 فيه مئات القصص النشطة في أي وقت. سنغطي الترقيم بين الصفحات بعد قليل.
الطريقة الثانية: استخراج Hacker News باستخدام Python عبر الـ API الرسمي
واجهة HN Firebase API هي الطريقة الرسمية للوصول إلى بيانات Hacker News. لا توجد مصادقة ولا مفاتيح API ولا تحليل HTML. تحصل على ردود JSON نظيفة. أستخدم هذه الطريقة مع أي شيء لازم يشتغل بثبات في الإنتاج.
أهم نقاط النهاية التي تحتاج إلى معرفتها
عنوان الأساس هو https://hacker-news.firebaseio.com/v0/. وهذه هي النقاط المهمة:
This paragraph contains content that cannot be parsed and has been skipped.
تبدو القصة بهذا الشكل:
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 يحتوي على معرفات التعليقات الفرعية المباشرة. وكل تعليق نفسه عنصر مستقل قد يملك kids خاصة به — وهكذا تُبنى شجرة التعليقات.
الخطوة 1: جلب معرفات أهم القصص
أنشئ ملفًا باسم scrape_hn_api.py:
1import requests
2import time
3from pprint import pprint
4API_BASE = "https://hacker-news.firebaseio.com/v0"
5# جلب معرفات أهم القصص
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
500 معرف قصة في طلب واحد — بدون تحليل، بدون محددات، فقط مصفوفة JSON.
الخطوة 2: جلب تفاصيل القصة عبر المعرف
الآن نحتاج إلى بيانات القصة الفعلية. وهنا تظهر مشكلة التفرع: 500 قصة يعني 500 طلب API منفصل. في اختباراتي، يستغرق كل طلب عنصر حوالي 1.2 ثانية بشكل متسلسل. ولـ 500 قصة، هذا يقارب 10 دقائق.
في أغلب حالات الاستخدام، لن تحتاج إلى الـ 500 كلها. إليك كودًا لجلب أفضل 30 قصة:
1def fetch_story(story_id):
2 """جلب تفاصيل قصة واحدة من 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 ما تذكر حدًا رسميًا للمعدل، لكن الضغط على أي API من دون فواصل ما يعتبر ممارسة جيدة.
الخطوة 3: استخراج التعليقات (تجوال شجري递归)
هنا تتفوق الـ API فعلًا على استخراج HTML. التعليقات في HN متداخلة بعمق — ردود على ردود على ردود. في HTML، هذا يعني تحليل هياكل جداول متداخلة ومعقدة. أما مع الـ API، فالحقل kids لكل تعليق يعطيك معرفات أطفاله، ويمكنك فقط السير في الشجرة بشكل递归.
1def fetch_comments(item_id, depth=0, max_depth=3):
2 """جلب التعليقات بشكل递归 حتى 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}...")
هذا الأسلوب الت递归ي أسهل بكثير من محاولة تحليل خيوط التعليقات المتداخلة في HTML. إذا كنت تحتاج إلى أشجار تعليقات كاملة، فالـ API هو الخيار الأفضل.
الخطوة 4: التشغيل ومراجعة النتائج
1python scrape_hn_api.py
سترى بيانات قصص منظمة يتبعها معاينة لتعليقات متداخلة. البيانات أنظف، والوصول إلى التعليقات سهل جدًا، وما فيه خطر من أن يتعطل السكربر لأن HN غيّر اسم فئة CSS.
ما بعد الصفحة الأولى: الترقيم والبيانات التاريخية
معظم الشروحات الخاصة بـ HN تتوقف عند الصفحة الأولى — 30 قصة. هذا جيد كعرض سريع، لكن الاستخدام الحقيقي غالبًا يحتاج إلى عمق أكبر.
استخراج عدة صفحات باستخدام BeautifulSoup
يستخدم HN نمطًا بسيطًا للروابط: ?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 # احترام robots.txt الذي يطلب 30 ثانية بين الزحف والآخر
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 يطلب صراحةً 30 ثانية كفاصل بين عمليات الزحف. تجاهله وقد تواجه تقييدًا للمعدل (HTTP 429) أو حظرًا مؤقتًا. خمس صفحات بفواصل 30 ثانية يعني حوالي 2.5 دقيقة — ليس فوريًا، لكنه محترم.
إذا كنت لا تريد تدير كود الترقيم بنفسك، فإن يتولى الترقيم بالنقر أو التمرير اللانهائي تلقائيًا. هو ينقر زر "More" أسفل صفحات HN من دون أي إعداد.
الوصول إلى البيانات التاريخية لـ Hacker News عبر Algolia API
واجهة Firebase API تعطيك البيانات الحالية. أما إذا كنت تريد تحليلًا تاريخيًا — مثل "ما هي أفضل قصص Python في 2023؟" أو "كيف تغيّر محتوى الذكاء الاصطناعي خلال السنوات الخمس الماضية؟" — فستحتاج إلى .
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.
6واجهة Algolia API سريعة جدًا (وقت معالجة خادم بين 5 و9 مللي ثانية)، ولا تتطلب مفتاح API، وتدعم الترقيم حتى 500 صفحة. ولتحليل تاريخي واسع النطاق، هي أفضل خيار متاح.
7## تصدير بيانات Hacker News إلى CSV وExcel وGoogle Sheets
8كل شرح شفته حول HN ينتهي بمخرجات `pprint()` في الطرفية. هذا ممتاز للتصحيح، لكن إذا كنت تبني ملخّصًا يوميًا أو تحلل الاتجاهات، فأنت تحتاج البيانات في ملف. إليك الطريقة.
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 تستخدمه كمحرّك Excel. إذا كان غير مثبت، فستحصل على خطأ ImportError.
الدفع إلى Google Sheets (اختياري)
لخطوط العمل المؤتمتة، قد ترغب في إرسال البيانات مباشرة إلى Google Sheets باستخدام مكتبة gspread. هذا يتطلب إعداد حساب خدمة في Google Cloud (وهو إجراء لمرة واحدة):
1import gspread
2gc = gspread.service_account(filename="service_account.json")
3sh = gc.open("HN Daily Digest")
4worksheet = sh.sheet1
5# تحويل القصص إلى صفوف
6header = list(stories[0].keys())
7rows = [list(s.values()) for s in stories]
8worksheet.clear()
9worksheet.update([header] + rows)
10print("Pushed to Google Sheets")
البديل بلا كود للتصدير
إذا حسّيت أن إعداد حسابات الخدمة وكتابة كود التصدير أكبر من جهد عملية الاستخراج نفسها، فأنا أتفهم ذلك. في Thunderbit، بنينا ميزة تصدير بيانات مجانية تتيح لك إرسال البيانات المستخرجة مباشرة إلى Excel أو Google Sheets أو Airtable أو Notion — بلا كود، بلا بيانات اعتماد، وبلا خطوط أنابيب تحتاج إلى صيانة. ولحالة سحب البيانات مرة واحدة، فهي أسرع فعلًا. سأشرح ذلك أكثر أدناه.
جعل السكربر جاهزًا للإنتاج: معالجة الأخطاء، التخزين المؤقت، والجدولة
إذا كنت تشغّل السكربر مرة واحدة بدافع الفضول، فالكود أعلاه يكفي. أما إذا كنت تشغّله يوميًا ضمن سير عمل، فستحتاج إلى بعض الأشياء الإضافية.
معالجة الأخطاء ومنطق إعادة المحاولة
الشبكات تفشل، والخوادم تحدّ من المعدل. طلب سيئ واحد لا ينبغي أن يوقف عملية الاستخراج كلها. إليك دالة إعادة محاولة مع 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 مع إعادة المحاولة تلقائيًا و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 تتولى منطق إعادة المحاولة بشكل مرتب. ستعيد المحاولة حتى 5 مرات مع backoff أُسّي متدرج — يبدأ من ثانية واحدة ويصل إلى 60 ثانية كحد أقصى. هذا يتعامل بسلاسة مع HTTP 429 (تقييد المعدل)، و503 (الخدمة غير متاحة)، وأخطاء الشبكة المؤقتة.
تخزين الاستجابات مؤقتًا لتجنب إعادة الزحف
أثناء التطوير، ستشغّل السكربر مرارًا وأنت تعدّل منطق التحليل. من دون التخزين المؤقت، سيضرب كل تشغيل خوادم HN مرة ثانية لنفس البيانات. مكتبة requests-cache تحل هذا في سطرين:
1import requests_cache
2requests_cache.install_cache("hn_cache", expire_after=3600) # التخزين لمدة ساعة
بعد إضافة هذين السطرين في بداية السكربت، ستُخزّن جميع نداءات requests.get() تلقائيًا في قاعدة بيانات SQLite محلية. أعد تشغيل السكربت 10 مرات في ساعة واحدة، وسيكون التشغيل الأول فقط هو الذي يطلب البيانات من الشبكة. هذه أداة ، ولسبب وجيه.
فصل عملية الزحف عن عملية التحليل
هناك نمط يفضله أصحاب الخبرة في الاستخراج: حمّل البيانات الخام أولًا، ثم حللها لاحقًا. بهذه الطريقة، إذا كان منطق التحليل فيه خطأ، يمكنك إصلاحه وإعادة التحليل من دون جلب البيانات مرة ثانية.
1import os, json
2def crawl_and_save(story_ids, output_dir="raw_data"):
3 """جلب بيانات القصص وحفظ 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، فيجب أن يعمل السكربر تلقائيًا. هناك خياران شائعان:
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: # زر تشغيل يدوي
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 مهام الجدولة في المستودعات التي لا تشهد نشاطًا لمدة 60 يومًا. أضف دائمًا workflow_dispatch حتى تتمكن من التشغيل يدويًا أثناء الاختبار.
ولخيار أبسط، تتيح لك ميزة Scheduled Scraper في Thunderbit وصف الجدول باللغة الطبيعية — مثل "استخرج كل صباح في 8" — من دون أي إعداد لخادم أو cron.
عندما يكون Python أكثر من اللازم: الطريقة بلا كود لاستخراج Hacker News
سأكون صريحًا هنا، رغم أنني أحب Python وفريقي يبني أدوات للمطورين. إذا كنت تحتاج فقط إلى أفضل 100 قصة اليوم من HN في جدول بيانات — الآن، لمرة واحدة — فإن كتابة سكربت Python وتصحيحه وتشغيله يعتبر عبئًا غير ضروري. حتى الإعداد نفسه (بيئة افتراضية، تثبيت الحزم، فهم المحددات) قد يأخذ وقتًا أطول من عملية جمع البيانات نفسها.
هنا يأتي دور . هذا هو سير العمل:
- افتح
news.ycombinator.comفي Chrome - انقر على أيقونة إضافة Thunderbit ثم اختر "AI Suggest Fields"
- يقرأ الذكاء الاصطناعي الصفحة ويقترح أعمدة مثل: العنوان، الرابط، النقاط، الكاتب، عدد التعليقات، ووقت النشر
- عدّل الحقول إذا أردت (إعادة تسمية، حذف، أو إضافة حقول مخصصة — ويمكنك حتى إضافة prompt مثل "صنّفها إلى AI/DevTools/Web/Other")
- انقر "Scrape" — وستظهر البيانات في جدول منظم
- صدّر إلى Excel أو Google Sheets أو Airtable أو Notion
نقرتان فقط للحصول على بيانات منظمة. لا محددات، لا كود، لا صيانة.
ومن المزايا الحقيقية هنا: ذكاء Thunderbit يتكيف تلقائيًا مع تغييرات التصميم. أدوات CSS-selector التقليدية تتعطل عندما يغيّر الموقع هيكله — ورغم أن HTML في HN مستقر نسبيًا، إلا أنه تغيّر من قبل (تم تحديث class="athing submission" واستبدال a.storylink القديم بـ span.titleline). أما الأداة المعتمدة على الذكاء الاصطناعي فتقرأ الصفحة من جديد كل مرة، لذلك ما تهمها أسماء الفئات.

Thunderbit يتولى أيضًا الترقيم بين الصفحات (بالنقر تلقائيًا على زر "More" في HN) واستخراج الصفحات الفرعية (زيارة صفحة تعليقات كل قصة لسحب بيانات النقاش). وفي حالة ، فذلك يعادل كود الـ API التكراري في الطريقة الثانية — لكن من دون كتابة سطر واحد.
المفاضلة بسيطة: Python هو الخيار المناسب عندما تحتاج إلى منطق مخصص، أو تحويلات بيانات معقدة، أو خطوط أتمتة مجدولة، أو عندما تتعلم البرمجة. وThunderbit هو الخيار المناسب عندما تحتاج البيانات بسرعة، ولا تريد صيانة كود، أو لست مطورًا. اختر الأداة التي تناسب وضعك.
Python مقابل API مقابل بلا كود: أي طريقة تختار؟
إليك إطار القرار الكامل:
| المعيار | BeautifulSoup (HTML) | Firebase API | Algolia API | Thunderbit (بلا كود) |
|---|---|---|---|---|
| المهارة التقنية المطلوبة | Python متوسط | Python مبتدئ | Python مبتدئ | لا شيء |
| وقت الإعداد | 10–15 دقيقة | 5–10 دقائق | 5–10 دقائق | دقيقتان |
| عبء الصيانة | متوسط (المحددات قد تتكسر) | منخفض (JSON ثابت) | منخفض (JSON ثابت) | لا شيء |
| عمق البيانات | الصفحة الرئيسية فقط | أي عنصر، المستخدمون | بحث + تاريخي | الصفحة الرئيسية + الصفحات الفرعية |
| التعليقات | صعب | سهل (recursive) | سهل (شجرة متداخلة) | استخراج الصفحات الفرعية |
| البيانات التاريخية | لا | لا | نعم (أرشيف كامل) | لا |
| خيارات التصدير | تبرمجها بنفسك | تبرمجها بنفسك | تبرمجها بنفسك | مدمجة (Excel، Sheets، إلخ) |
| الجدولة | cron / GitHub Actions | cron / GitHub Actions | cron / GitHub Actions | مجدول مدمج |
| الأفضل لـ | تعلّم الاستخراج | خطوط موثوقة | البحث والتحليل | السحب السريع للبيانات |
إذا كنت تتعلم Python أو تبني شيئًا مخصصًا، فاختر الطريقة الأولى أو الثانية. وإذا كنت تحتاج إلى تحليل تاريخي، أضف Algolia API. وإذا كنت تريد فقط البيانات من دون كود، فـ .
الخلاصة وأهم النقاط
إليك ما أصبح لديك الآن في صندوق أدواتك:
- طريقتان كاملتان بـ Python لاستخراج Hacker News — BeautifulSoup لتحليل HTML وFirebase API للحصول على JSON نظيف
- تقنيات ترقيم بين الصفحات لاستخراج ما بعد الصفحة الأولى، بما في ذلك Algolia API للبيانات التاريخية الممتدة حتى 2007
- كود تصدير إلى CSV وExcel وGoogle Sheets — لأن البيانات داخل الطرفية ما تفيد فريقك
- أنماط جاهزة للإنتاج — منطق إعادة المحاولة، التخزين المؤقت، الفصل بين الزحف والتحليل، والأتمتة المجدولة عبر cron أو GitHub Actions
- بديل بلا كود عندما يكون Python أكثر من حاجتك
توصيتي: ابدأ بـ Firebase API (الطريقة الثانية) في معظم الحالات. فهي أنظف، وأكثر موثوقية، وتمنحك الوصول إلى التعليقات من دون صداع تحليل HTML المتداخل. أضف Algolia API عندما تحتاج إلى بيانات تاريخية. واحتفظ في المفضلة للأوقات التي تحتاج فيها فقط إلى جدول بيانات سريع ولا تريد تشغيل مشروع Python كامل.
إذا أردت تتعمق أكثر، فجرب استخراج تعليقات HN من أجل ، أو ابنِ خط ملخّص يومي باستخدام GitHub Actions، أو استكشف Algolia API لتتبع كيف تغيّرت الاتجاهات التقنية خلال العقد الماضي.
الأسئلة الشائعة
هل من القانوني استخراج بيانات Hacker News؟
بيانات HN متاحة للعامة، وY Combinator توفر واجهة API رسمية مخصصة للوصول البرمجي. كما أن ملف يسمح باستخراج المحتوى للقراءة فقط (الصفحة الرئيسية، صفحات العناصر، صفحات المستخدمين)، لكنه يطلب فاصلًا مدته 30 ثانية بين طلبات الزحف. التزم بهذا الفاصل، ولا تستخرج نقاط النهاية التفاعلية (التصويت، تسجيل الدخول)، وستكون في وضع آمن. للمزيد حول أخلاقيات الاستخراج، راجع دليلنا حول .
هل لدى Hacker News واجهة API رسمية؟
نعم. على hacker-news.firebaseio.com/v0/ مجانية، ولا تتطلب مصادقة، وتوفر الوصول إلى القصص، والتعليقات، وملفات المستخدمين، وجميع أنواع الخلاصة (top، new، best، ask، show، jobs). وهي تُرجع JSON نظيفًا ولا تذكر حدًا رسميًا للمعدل، رغم أن مراعاة فواصل الطلبات تبقى ممارسة جيدة دائمًا.
كيف أستخرج تعليقات Hacker News باستخدام Python؟
باستخدام Firebase API، اجلب عنصر القصة لتحصل على الحقل kids (مصفوفة بمعرفات التعليقات الرئيسية). وكل تعليق هو عنصر مستقل له أيضًا kids خاصة بالردود. يمكنك السير في الشجرة بشكل递归 بدالة تجلب كل تعليق وأطفاله. راجع قسم "استخراج التعليقات (تجوال شجري递归)" أعلاه للحصول على الكود الكامل. وبديلًا عن ذلك، فإن نقطة النهاية /items/<id> في تُرجع شجرة التعليقات المتداخلة كاملة في طلب واحد — وهو أسرع بكثير للقصص كثيرة التعليقات.
هل يمكنني استخراج Hacker News من دون كتابة كود؟
نعم. يعمل كإضافة Chrome — افتح HN، وانقر "AI Suggest Fields"، وسيحدد تلقائيًا أعمدة مثل العنوان والرابط والنقاط والكاتب. انقر "Scrape" ثم صدّر مباشرة إلى Excel أو Google Sheets أو Airtable أو Notion. يتعامل مع الترقيم ويمكنه حتى زيارة الصفحات الفرعية لسحب بيانات التعليقات. لا Python، لا محددات، لا صيانة.
كيف أحصل على البيانات التاريخية لـ Hacker News؟
هو أفضل أداة لذلك. استخدم نقطة النهاية search_by_date مع numericFilters=created_at_i>TIMESTAMP للتصفية حسب النطاق الزمني. يمكنك البحث بالكلمات المفتاحية، وتصفية نوع القصة، والانتقال عبر ما يصل إلى 500 صفحة من النتائج. وللتحليل التاريخي واسع النطاق، تتوفر أيضًا مجموعات بيانات عامة على (الأرشيف الكامل)، و (28 مليون سجل)، و (4 ملايين قصة).
اعرف المزيد