محلل أوراق البحث
قراءة ورقة شيء؛ فهرسة مجموعة منها شيء آخر. مراجعة أدبيات، ومدير مراجع، وأداة توليد مراجعات ، كلها تبدأ بنفس الوظيفة غير المثيرة: حوِّل جدار نثر إلى بنية بأقسام واستشهادات وببليوغرافيا يستطيع آلة العمل بها. يبني هذا المشروع ذلك المحلل من الصفر بلغة Python خالصة. ستأخذ نصًا صريحًا لورقة بحثية حقيقية، وتكتشف عناوين أقسامها من شكلها، وتقسّم الجسم إلى أجزاء منظمة، وتستخرج استشهادات من نمط [1] و[2, 3] والمراجع التي تشير إليها، ثم تبني بحثًا مصنّفًا صغيرًا فوق المحتوى المحلل. فك ترميز PDF خارج النطاق وعمدًا ، الهندسة المثيرة للاهتمام هي النص في اللحظة التي يكون فيها فعلًا على قرصك: التعرف على الأشكال، وregex، وهياكل البيانات، ولا شيء منها يحتاج مكتبة PDF.
يفترض هذا Python 101 ، إدخال/إخراج الملفات، والسلاسل، والدوال، والقواميس. اختياري وغير مصنّف؛ راجع المشاريع الواقعية للقائمة الكاملة.
🎯 ما ستفعله
- حمّل النص الصريح لورقة حقيقية وألقِ نظرة على شكله الخام.
- اكتشف عناوين الأقسام بطباعتها (أرقام، حالة العنوان، الطول) بدل قائمة مكتوبة يدويًّا.
- قسّم الورقة إلى خريطة
{section: text}منظمة يمكنك الاستعلام عنها. - استخرج الاستشهادات الداخلية وابنِ قسم مراجع ، مع بقاء علاقة الاستشهاد↔المرجع سليمة.
- ابنِ بحثًا مصنّفًا صغيرًا (تردد المصطلح) فوق الأقسام المحللة وتحقق منه.
أين تُشغّل هذا
محليًا مع uv هو المسار الأساسي ، يعمل المحلل على ملفات نصية صريحة يمكنك إيجادها ولمسها ومقارنتها، وقاعدة المكتبات القياسية فقط (re، وcollections، وpathlib) تعني صفر احتكاك تثبيت وراء uv init.
GitHub Codespaces هو التجربة نفسها: افتح codespaces.new/abderrahim-lectures/python-data-analysis-course، واستنسخ .txt لورقة عامة، وحللها في تبويب متصفح.
تتعامل Google Colab وKaggle Notebooks وBinder مع التحليل بأمانة ، تعمل Python الخالصة في أي مكان، وورقة نصية قصيرة تُلصق أو تُرفع في الدفتر تُحلل تمامًا كما محليًّا. بل يمكن للدفتر توليد ورقة عينة فورًا ليكون لديك بيانات حتمية قبل جلب ورقة حقيقية. الشيء الوحيد الذي لا يستطيع دفتر إعادة إنتاجه هو بهجة “التقط .txt حقيقيًا من arXiv وحلله” ، هذا السحب عادة محلية/طرفية.
الإعداد
شيئان قبل حطّ أول عنوان: uv في PATH لديك، وورقة نصية صريحة حقيقية تمضغها.
ثبّت uv
macOS / Linux (الطرفية):
curl -LsSf https://astral.sh/uv/install.sh | sh
Windows (PowerShell):
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
أغلق وأعد فتح طرفيتك، ثم:
uv --version
mkdir research-paper-parser && cd research-paper-parser
uv init --bare
احصل على ورقة نصية
تقدم صفحة Source في arXiv ملف .txt نصيًا صريحًا لمعظم الأوراق، ويشحن مستودع المساق عينة صغيرة:
mkdir -p papers
# fetch a real one (example: an arXiv paper's HTML -> download source -> extract .txt)
curl -L -o papers/sample.txt https://raw.githubusercontent.com/abderrahim-lectures/python-data-analysis-course/main/examples/research-paper-parser/paper.txt
wc -l papers/sample.txt
إذا لم تكن العينة متاحة، فأي .txt لورقة مؤتمر يعمل ، المحلل قائم على الشكل، لا مقيد بالتنسيق.
✅ قائمة التحقق
- ✅ يطبع
uv --versionرقم نسخة. - ✅
papers/sample.txtموجود وwc -lيبلغ ببضعة مئات من أسطر النثر. - ✅ يعرض
head -20 papers/sample.txtعنوانًا، ثم عناوين أقسام، ثم نص الجسم.
الخطوة 1: حمّل النص الخام وافحصه
يبدأ كل محلل بالنظر ، طباعة شكل الملف قبل أن تلتزم أي منطق بافتراضات. تقرأ هذه الخطوة الورقة كلها، وتقسّمها إلى أسطر، وتحسب الإحصائيات الرخيصة التي تعلمك ما يعنيه “عنوان” في هذا الملف (العناوين قصيرة، وALL-CAPS أو عنوان حالة، ومرقّمة؛ وأسطر الجسم جمل طويلة). التخمين حول العناوين قبل هذا الفحص هو كيف تسقط المحللات في قفل التنسيق.
👟 تلميح البداية : ابدأ بكتابة load(path) التي تقرأ الملف كله بـPath(path).read_text(encoding="utf-8", errors="ignore").splitlines()، ثم اطبع عدد الأسطر ونافذة فوق أول 25 سطرًا.
# parser.py
from pathlib import Path
def load(path: str) -> list[str]:
return Path(path).read_text(encoding="utf-8", errors="ignore").splitlines()
lines = load("papers/sample.txt")
print("total lines:", len(lines))
for i in range(0, min(25, len(lines))):
print(f"{i:4d} | {lines[i][:80]}")
يبتلع read_text(...)["utf-8", errors="ignore"] البايت المشوّه العرضي من .txt قديم دون تحطم ، خيار عملي لكاشط نص (فقدان حرف تالف واحد يتفوق على إجهاض التحليل كله). errors="ignore" هو علم المراجعين الأحمر أيضًا: مقبض فقد بيانات صامت، واختياره عمدًا مع تعليق هو الحركة الاحترافية. شريحة [:80] عقل رخيص حول ما يحتويه الملف فعلًا قبل أن تكتب قاعدة مطابقة واحدة.
🎯 الناتج المتوقع : عدّ (نموذجيًا ~300-600 سطر) ونافذة أول 25 سطرًا تعرض العنوان، ثم ملخصًا، ثم عناوين أقسام مرقّمة مثل 1. Introduction و2. Methods ، دليل الشكل الدقيق الذي تعتمد عليه استدلالات الخطوة التالية.
🩹 إذا لم يعمل : إذا كان الملف فارغًا أو عدد الأسطر ضئيلًا، ففشل التنزيل أو مسار العينة خاطئ ، تحقق أن papers/sample.txt موجود وله بايتات غير صفرية. إذا طُبع كل سطر فارغًا، فالملف UTF-16 أو بأي حال غير UTF-8 ، سيخفي errors="ignore" ذلك الفشل بتجريد كل شيء؛ اطبع repr(lines[5][:50]) لرؤية البايتات الخام.
✅ قائمة التحقق
- ✅ عدد الأسطر سليم (بالمئات) والأسطر الأولى تتضمن عنوانًا + ملخصًا.
- ✅ رأيت أنماط العناوين بعينيك قبل برمجة الكاشف.
- ✅
errors="ignore"اختيار متعمّد، لا حادثة ، يمكنك القول متى تزيله.
🤔 سؤال (أسئلة) سقراطي(ة)
- قبل كتابة كاشف العناوين، تخيل ورقتين: واحدة بها
**3. Results**(غامق، ثلاث كلمات) وأخرى بسطر عاديResults and Discussion. إذا كتبت regex للأولى، ما الذي تعلمك إياه الثانية عن سبب كون الشكل-على-البنية هو الحركة المتينة لمحلل مصمم للتعميم؟ - يسقط
errors="ignore"البايتات التالفة بصمت. سمِّ سيناريو تسبب فيه تلك السياسة إجابة خاطئة بدل تحطم ، والتشخيص الذي يلتقطها.
الخطوة 2: اكتشف عناوين الأقسام بالشكل
النهج الهش قائمة مقسّاة (if line == "Introduction":). النهج المتين يعامل”العنوانية” كـدرجة ، السطر عنوان عندما يكون قصيرًا، ويبدأ بمفرده، ويُقرأ كعنوان (عنوان حالة أو ALL-CAPS، وربما مرقّمًا). هذا يجعل المحلل ينجو من عناوين لم يرها قبلًا، وهو بيت القصيد كله في الاستخراج القائم على الشكل.
👟 تلميح البداية : ابدأ بكتابة is_heading(line) بالإشارات الثلاث ، بادئة مرقّمة ^\d+(\.\d+)*\.?\s، وistitle()/isupper()، وقائمة known ، مدمجة بـor، ثم امسح أسطر الخطوة 1 واطبع كل ما علّمته.
# parser.py (continued)
import re
def is_heading(line: str) -> bool:
s = line.strip()
if not s or len(s) > 80:
return False
numbered = bool(re.match(r"^\d+(\.\d+)*\.?\s+\S", s))
titlecase = s.istitle() or s.isupper()
# 'Abstract', 'References', 'Conclusion' are single-word famous headings too
known = s in {"Abstract", "Introduction", "Methods", "Results",
"Discussion", "Conclusion", "References"}
return (numbered or titlecase or known) and len(s.split()) <= 12
headings = [(i, ln) for i, ln in enumerate(lines) if is_heading(ln)]
for i, h in headings[:12]:
print(f"{i:4d}: {h}")
الكاشف مسند مركّب: العنوان سطر قصير (len<=80، <=12 words)، يُقرأ كعنوان (بادئ برقم، أو عنوان حالة، أو ALL-CAPS، أو اسم مشهور من كلمة واحدة). أي إشارة واحدة محفوفة بالمخاطر وحدها؛ الاتحاد هو كيف تعبّر الأوراق الحقيقية عن العناوين بأنماط متنوعة بعنف. لاحظ <=80 و<=12 الخشنين عمدًا: يرفضان النثر ويقبلان تقريبًا أي عنوان تسكّه مجلة، مقايضين بضعة إيجابيات كاذبة (جملة غامقة قصيرة) بكارثة السلبيات الكاذبة الأكبر بكثير (فقدان عنوان).
🎯 الناتج المتوقع : أول ~10-12 سطر عنوان بفهارس أسطرها ، مطابقة لفحصك البصري في الخطوة 1، لأن القواعد اشتُقت من ذلك الفحص عينه.
🩹 إذا لم يعمل : إذا فُقد عنوان حقيقي، فكان نمطه خارج المسند ، مرّره عبر الاختبارات الفرعية الثلاثة ( numbered، وistitle، وisupper) لترى أي فرع فشل، ثم أرْخِ ذلك الفرع. إذا عُلِّمت أسطر نثر قصيرة كعناوين (جملة موجزة تحت 12 كلمة تبدأ بحرف كبير)، فهذا إيجابي كاذب معروف لكشف الشكل ، المقايضة مقصودة، وتجميع الأقسام في الخطوة 3 يتخلص من نص الجسم الرديء بنظافة على أي حال.
✅ قائمة التحقق
- ✅ العناوين المكتشفة تطابق قراءتك البصرية في الخطوة 1 ضمن ضربتين أو ثلاث.
- ✅ يمكنك القول أي إشارة فرعية التقطت كل عنوان (مرقّم مقابل عنوان حالة مقابل كلمة معروفة).
- ✅ يمكنك التعبير عما يترتب على الإيجابي الكاذب (الجمل ذات العنوان القصير) ولماذا يستحق ذلك.
🤔 سؤال (أسئلة) سقراطي(ة)
- المسند “أي إشارة من عدة”. اقلبه: ما الذي ينكسر إذا طلبتها كلها (قصيرًا ومرقّمًا وعنوان حالة)؟ سمِّ نمط عنوان حقيقي سيرفضه ، ذلك بالضبط فخ الملاءمة الزائدة الذي صُمم كاشف الشكل ليتهرب منه.
knownقائمة مقسّاة لأسماء أقسام شهيرة. وسّع الفكرة: ماذا يحدث حين تسمّي ورقة قسمًا “5. Experimental Setup” ، أي فرع يلتقطه، وما الخطر المتبقي إذا صادف هذا الكاشف قسمًا بعنوان، مثلًا، “A Note on Notation”؟
الخطوة 3: قسّم إلى خريطة أقسام منظمة
بعد أن وُجدت العناوين، تأتي المكافأة: حوِّل قائمة أسطر مسطحة إلى قاموس {heading: body_text}. يبدأ كل عنوان قسمًا جديدًا، وكل شيء بينه وبين العنوان التالي ينتمي إليه. هذا هو هيكل البيانات الذي يحوّل “اقرأ الورقة” إلى “اسأل الورقة أسئلة” ، ويعيد استخدام الكاشف الدقيق الذي بنيته بالفعل.
👟 تلميح البداية : ابدأ بكتابة split_sections(lines, is_heading) كطيّ: اسم current للعنوان، وقائمة buf، وحلقة for واحدة تغلق الدلو في القاموس كلما ضُرب عنوان جديد (بمحتوى).
# parser.py (continued)
def split_sections(lines: list[str], is_heading) -> dict[str, str]:
sections: dict[str, str] = {}
current = "frontmatter"
buf: list[str] = []
for ln in lines:
if is_heading(ln) and buf:
sections[current] = "\n".join(buf).strip()
current = ln.strip()
buf = []
else:
buf.append(ln)
if buf:
sections[current] = "\n".join(buf).strip()
return sections
sections = split_sections(lines, is_heading)
for name, body in sections.items():
words = len(body.split())
print(f"{name[:45]:<47} {words:>6} words")
الجوهر طيّ تراكمي: يشير current إلى العنوان الذي تملؤه، ويجمع buf أسطره، وعندما يظهر عنوان جديد تغلق آخر دلو (فقط إذا كان له محتوى ، if buf يتخطى الانجراف الفارغ بين العناوين المتتالية). يلتقط دلو frontmatter كل شيء قبل أول عنوان حقيقي ، العنوان، والمؤلفون، والملخص ، تحت مفتاح اصطناعي، محافظًا على إجمالية الخريطة دون أي نص مسقوط. المخرج هو اللحظة التي يتوقف فيها الورق عن كونه سلسلة ويصبح بيانات قابلة للاستعلام.
🎯 الناتج المتوقع : dict بإدخال frontmatter وإدخال واحد لكل قسم حقيقي، كلٌّ يطبع اسمه وعدد كلماته ، الطرق أثقل من الخاتمة، وfrontmatter صغير لكنه حاضر.
🩹 إذا لم يعمل : إذا ظهر frontmatter وقسم عملاق واحد فقط، أطلقت الكاشف مرة واحدة في الأعلى ، فُقد أول عنوان جسم في الخطوة 2؛ أعد تشغيل الكاشف وأرْخِه. إذا كان العنوان مبتلعًا في القسم فوقه، أعاد is_heading False لنفس العنوان الذي يبدأ حد الدلو ، نفس الإصلاح، سطر مختلف. إذا تسربت الأقسام معًا، فهناك فرق بين if is_heading and buf ، لا if is_heading وحدها ، يسقط إغلاق دلو فارغ عندما يكون عنوانان متجاورين.
✅ قائمة التحقق
- ✅ خريطة الأقسام تعكس قائمة عناوين الخطوة 1 ، كل عنوان مكتشف مفتاح قاموس.
- ✅ لا نص ضائع: اتحاد كل أجسام الأقسام يعيد بناء الأسطر الأصلية.
- ✅ يلتقط
frontmatterكتلة ما قبل العنوان (العنوان + الملخص) سليمة.
🤔 سؤال (أسئلة) سقراطي(ة)
- يغلق الطيّ دلوًا فقط عندما يضرب العنوان التالي. تتبّع ما سيحدث إذا جلس عنوان في نهاية قسم ، كيف تضمن الكود أن
bufالنهائي ما زال يهبط في القاموس (انظر إلىif bufالختامي)؟ ما الخطأ الذي يظهر بدونها؟ - تُعرَّف حدود الأقسام بالعناوين؛ لكن مفاتيح الخريطة سلاسل عناوين خام. إذا أردت الآن “الطرق” برمجيًّا، فماذا يفعل عنوان مثل
3. Methods and MaterialsمقابلMethodsبعمليات البحث بالمطابقة الدقيقة ، ولماذا يُجادل ذلك لتطبيع المفاتيح عند تخزينها؟
الخطوة 4: استخرج الاستشهادات وابنِ قائمة مراجع
لا يكتمل المحلل عند الأقسام ، محلل بحثي يجب أن يجد المراجع. تستشهد الأوراق بـ[12] و[3, 5] أو [4–7] داخل النص، وتلك الرموز هي حواف الرسم الاستشهادي عائدة إلى الببليوغرافيا المرقّمة. تعزل هذه الخطوة المراجع، وتستخرج أرقام الاستشهادات، وترسم number → paper باستخدام كتلة قائمة المراجع ، محولة الضجيج المقوّس إلى قاموس منظم {num: title}.
👟 تلميح البداية : ابدأ بكتابة extract_references(text) لقطع كل شيء بعد علامة References، ثم citations_from(body) بـre.findall(r"\[(\d+(?:\s*,\s*\d+)*)\]", ...) التي تقسّم كل كتلة قوس إلى أرقامها الفردية.
# parser.py (continued)
import re
def extract_references(text: str, prefix: str = "References") -> list[str]:
m = re.search(prefix + r"\s*\n(.*)", text, re.S)
return [l for l in (m.group(1).splitlines() if m else []) if l.strip()][:20]
refs = extract_references("\n".join(lines))
print("first few references:")
for r in refs[:5]:
print(" ", r[:90])
def citations_from(body: str) -> list[int]:
nums = re.findall(r"\[(\d+(?:\s*,\s*\d+)*)\]", body)
out = []
for block in nums:
out += [int(x) for x in re.split(r"\s*,\s*", block)]
return out
print("citations in frontmatter:", citations_from(sections.get("frontmatter", ""))[:10])
يقسم extract_references عند علامة References ويلتقط كل ما بعدها ، استدلال “بقية الورقة ببليوغرافيا” فظ لكن فعال للغاية (معزز بشريحة .splitlines()[:~20]). citations_from محرك الاستشهادات الداخلية: يلتقط findall مجموعات مقوّسة مثل [12, 34]، ويحوّل re.split الداخلي الكتلة المفصولة بفواصل إلى أرقام فردية. يطابق نمط \d+(?:\s*,\s*\d+)* رقمًا واحدًا أو عدة أرقام مفصولة بفواصل، وهو بالضبط حالة [4, 7, 12]؛ ونطاق الشرطة [4–7] TODO معلّم ستوسّعه. أرقام الاستشهادات هي العناوين إلى قائمة المراجع ، وصلة {num: title} هي الجسر بين “ما يستشهد به النص” و”ما تسرده الببليوغرافيا رسميًّا.”
🎯 الناتج المتوقع : أول ~5 أسطر مراجع من الببليوغرافيا، وقائمة قصيرة من استشهادات رقمية مسحوبة من frontmatter (يستشهد الملخص عادةً ببضعة) ، إثبات أن مقسّم القسم وregex الاستشهادات يعملان من طرف إلى طرف.
🩹 إذا لم يعمل : إذا أعاد extract_references قائمة فارغة، فعلامة References ليست سطرًا عاديًّا ، بعض الأوراق تسطّرها أو ترقّمها (References مقابل REFERENCES)؛ جرّب علم re.IGNORECASE بلا حساسية حالة. إذا لم يجد citations_from شيئًا، فالأوراق تستخدم استشهادات مؤلف-سنة (Smith, 2020) بدل أقواس رقمية ، ذلك هندسة مختلفة حقًا، وregex لديك يجب أن يفوته، وهو الدرس: اعرف مخطط الاستشهاد الذي تستهدفه. إذا لم تكن نطاقات الشرطة [4–7] تتوسع، فذلك فرع TODO المعروف ، int("4–7") سيثير ValueError وهو إشارتك لتنفيذ توسيع النطاق.
✅ قائمة التحقق
- ✅ يعيد
extract_referencesأسطر افتتاحية الببليوغرافيا، لا نص الجسم. - ✅ يحوّل
citations_from[1, 2]إلى{1, 2}و[12]إلى{12}. - ✅ يمكنك مقارنة وعد المخطط الرقمي مقابل مؤلف-سنة قبل وعود محلل عالمي.
🤔 سؤال (أسئلة) سقراطي(ة)
- نمط قوس
findallشرِه على الفواصل:[12, 34, 56]ينتج كتلة واحدة تنقسم إلى ثلاثة أرقام. أعد كتابة في رأسك ما يعطيه[12, 34]زائد[5]منفصلة ، وفكّر هل يطابق ترتيب الأرقام في قائمة المخرج الترتيب في النص عند ظهور كتل مفردة ومتعددة مختلطة. هل يهم الترتيب لحواف الرسم الاستشهادي؟ - يفترض مقسّم
{"References ..."}أن “كتلة المراجع = كل شيء بعد العلامة.” ماذا يحدث للمحلل إذا وضعت ورقة ملحقًا بعد مراجعها ، أين تهبط نص الملحق فيextract_references، وما القاعدة الإضافية الوحيدة التي تمنعه من تلويث الببليوغرافيا?
الخطوة 5: بحث مصنّف صغير فوق الورقة المحللة
الأقسام، والاستشهادات، والمراجع ، القطعة الهندسية بيانات، والبيانات لا تستحق إلا إذا تمكنت من سؤالها. تبني هذه الخطوة الأخيرة بحثًا مصنّفًا أدنى: تُسجَّل كلمات الاستعلام بعدد مرات ظهورها في كل قسم (تردد المصطلح)، وتُدرج الأقسام أفضل أولًا. إنها لعبة، لكنها تكمل خط الأنابيب من نص خام إلى شيء يمكنك استجوابه فعلًا.
👟 تلميح البداية : ابدأ بكتابة word_counts(body) بـمقسّم [a-z]+ وCounter، ثم search(query, sections) التي تسجّل كل قسم بعدد رموز الاستعلام الظاهرة فيه وتفرز تنازليًّا.
# parser.py (continued)
from collections import Counter
TOK = re.compile(r"[a-z]+")
def word_counts(body: str) -> Counter:
return Counter(TOK.findall(body.lower()))
def search(query: str, sections: dict[str, str], top: int = 3) -> list[tuple[str, int]]:
q = set(TOK.findall(query.lower()))
scored = []
for name, body in sections.items():
counts = word_counts(body)
score = sum(counts[w] for w in q)
if score:
scored.append((name, score))
return sorted(scored, key=lambda t: t[1], reverse=True)[:top]
for q in ["method data", "conclusion results"]:
print(f"\nquery: {q!r}")
for name, score in search(q, sections):
print(f" {score:>4} {name[:50]}")
الآلية تردد مصطلح: قسّم النص المأخوذ بحروف صغيرة إلى كلمات أبجدية (findall ثم جرّد غير الحروف عبر [a-z]+)، وعلّم كل قسم بـCounter، وسجّل قسمًا بعدد رموز الاستعلام الظاهرة فيه. هذا ليس TF-IDF (كلمة شائعة مثل “data” غير مخففة الوزن)، ولا مصنّفًا مقابل مستندات أخرى وراء ورقة واحدة ، لكنه الشكل الصحيح لحل بحث، ومقسّم TOK (اسقِط أي غير حرف حتى يتوحد data, وdata) قرار تقسيم حقيقي. يفلتر if score بصمت الأقسام ذات الصفر مطابقات، فيكون أعلى-K صادقًا حول “أفضل ما يملك المصطلح.”
🎯 الناتج المتوقع : لـ"method data"، يتفوق قسم الطرق بفارق كبير على الآخرين؛ و"conclusion results" يضع النتائج والخاتمة عاليًا ، مخرج البحث يطابق البنية الفعلية للورقة مرئيًّا، وهو فحص الصحة.
🩹 إذا لم يعمل : إذا كانت الضربة العليا هي frontmatter لكل استعلام، فالأقسام ضئيلة أو الجسم لم يُقسَّم قط ، أعد فحص خريطة الخطوة 3 (إذا كانت الورقة كلها دلو frontmatter واحد، فلا شيء مصنّف). إذا لم يُعد "data" شيئًا، فمقسّم [a-z]+ يختنق بصيغة بواصلة أو فاصلة عليا ، ذلك متوقع؛ أضف [a-z'-]+ لإبقاء الانقباضات سليمة، ولاحظ المقايضة. إذا فُقد قسم عالي التسجيل، فكلمات الاستعلام لا تتقاطع مع رموز القسم حرفيًّا ، فجوة تجذير (run/ran) يمكنك الإقرار بها كحد فاصل بين لعبة ومحرك بحث.
✅ قائمة التحقق
- ✅ يضع
"method data"الطرق أولًا؛ و"conclusion results"النتائج/الخاتمة عاليًا. - ✅ استعلامات الصفر مطابقات تعيد قائمة فارغة (لا قمامة درجات NaN).
- ✅ يمكنك شرح قيد واحد (لا TF-IDF، لا تجذير، مجموعة ورق واحد) يصلحه محرك حقيقي.
🤔 سؤال (أسئلة) سقراطي(ة)
- البحث تردد مصطلح خالص ، الكلمات المتكررة تفوز. أضف “and” أو “the” إلى استعلام وشاهده يهيمن. ما الذي يغيّره IDF (نصف التردد العكسي للمستند في TF-IDF) حول الكلمات الشائعة الرديئة، ولماذا يستحيل حسابه على مستند واحد؟
- التجذير (
run==ran، وanalysis==analys*) هو الخط بين “بحث لعبة” و”بحث حقيقي.” ابنِ الحجة لماذا ما زالت الرموز الحرفية تعمل جيدًا بشكل يفاجئ على النثر الأكاديمي (تعيد الأوراق استخدام مفردات ثابتة بشكل مذهل عبر قوس الملخص→الطرق→النتائج) ، والقسم الواحد الذي تنكسر فيه أولًا.
⚠️ مآزق شائعة
- مطالبة كل إشارة في العنوان.
numbered AND title-case AND shortيرفض “Results and Discussion” ، قيمة كاشف الشكل كلها في اتحاد إشاراته. توقع بضعة إيجابيات نثرية كاذبة؛ تجميع الأقسام يتخلص منها بنظافة. - مقسّاة أسماء الأقسام. قائمة
{"Introduction", "Methods", ...}تنكسر عند “Experimental Setup” أو “1. Preliminaries”. أبقِknownفرعًا واحدًا من المسند، لا الكاشف كله أبدًا. - فقد بيانات
errors="ignore"الصامت. بايت تالف واحد يمكن أن يبخر سطرًا من النص دون أثر. فضّلerrors="replace"(�مرئية) عند فك ترميز ملفات غير موثوقة ليكون فك الترميز الرديء قابلًا للتشخيص لا خفيًّا. - الاعتماد على هندسة استشهاد واحدة. regex الرقمي
[12, 3]لا يجد شيئًا في أوراق مؤلف-سنة(Smith, 2020). قرر المخطط الذي تستهدفه مسبقًا؛ المحلل الذي “يتعامل مع الاثنين” صامتًا ينتج عادةً المخرج الفارغ للثاني. - تسرّب الببليوغرافيا من ملحق. “المراجع = كل شيء بعد العلامة” يبتلع الملحق بصمت. أوقف الكتلة عند العنوان التالي (أعد استخدام
is_heading) أو عند رمز فاصل صفحات ليمنع نثر ما بعد الببليوغرافيا من تلويث قائمة المراجع.
ما بنيته للتو
محلل أوراق بحث حقيقي بلغة Python خالصة: اكتشفت العناوين بالشكل بدل قائمة مقسّاة، وطويت الورقة في خريطة {section: text} قابلة للاستعلام، واستخرجت استشهادات رقمية وكتلة مراجع، وصفّرت الأقسام مقابل استعلام عبارة بتردد المصطلح. عادتان هنا تساويان أكثر من المحلل نفسه ، فحص أنظر-قبل-أن-تكتب الذي يثبّت استدلالاتك إلى بيانات حقيقية، وفهم أن “الجزء المثير في التحليل هو معرفة الهندسة التي تستهدفها فعلًا.” خطوط أنابيب كهذه تكمن وراء مديري المراجع وأدوات المراجعة وأنظمة التنقيب الأدبي؛ بنيت الجوهر الصادق لواحد منها دون مكتبة PDF واحدة.
شغّل نسخة أكمل دون أي إعداد محلي
examples/research-paper-parser/ في مستودع المساق يجمع وحدة المحلل وورقة نصية عينة ودفترًا يحمّل ويقسّم ويستشهد ويبحث inline. استنسخ المستودع، أو افتحه في GitHub Codespaces، وشاهد خط الأنابيب يعمل من البداية إلى النهاية.
إلى أين تذهب من هنا
- تعامل مع نطاقات الشرطة: وسّع
[4–7]إلى{4,5,6,7}بـre.split(r"\s*[–-]\s*")صغيرة +range()، TODO المعلّم من الخطوة 4. - دعم مؤلف-سنة: أضف regex استشهاد ثانيًا لـ
(Smith, 2020)و محلل اسم→مرجع؛ نفس كتلةreferencesترسم إلى قاموس مفتاحه الاسم. - CLI: غلّف خط الأنابيب في
argparse(parse.py paper.txt --search "neural method") ليعمل كأداة قشرية بدل مقطع ملصوق. - توقف عند الملحق: اجعل
extract_referencesينتهي عند العنوان التالي، معيدًا استخدامis_heading، فلا يلوّث نص ما بعد الببليوغرافيا قائمة المراجع أبدًا.
شارك مشروعك مع الصف
حللت ورقة، أو بنيت رسمًا استشهاديًّا، أو حصلت على ترتيب بحث تفتخر به؟ examples/student-projects/ معرض لمشاريع قدّمها طلاب آخرون، ويأخذك README الخاص به في جولة إضافة مشروعك عبر طلب سحب من البداية إلى النهاية: الشوكة، والفرع، والالتزام، وفتح الـPR. لا خبرة git مسبقة مفترضة.
مرحبًا بك في كتابة Python خارج المتصفح. 🎓
أكمل كل خطوة ثم حدد المشروع كمكتمل لجمع نقاطه.