تحليل صفحة إنترنت باستخدام لغة PHP وتوابع PCRE

من أقوى ميزات لغة PHP قدرتها الفائقة على التعامل مع النصوص، وتأتي هذه القوة من دمج مكتبات معالجة النصوص الشهيرة ضمن اللغة، بحيث تحول إلى توابع يمكن استدعاؤها ببساطة.
تتضمن PHP بشكل افتراضي مكتبة معالجة النصوص PCRE أو Perl Compatible Regular Expressions، وهي مكتبة تضم مجموعة توابع فائقة القوة يمكنها بسطر واحد أن تفعل فعلا يبدو عجائبيا بالنسبة لمن لا يعرفون إمكانياتها.
سأشرح في هذه المقالة تطبيقا بسيطا لمكتبة توابع PCRE يعتمد على تحليل صفحة إنترنت مولدة آليا عبر برنامج، واستخراج مكوناتها الأساسية.
تتميز الصفحات التي تولد آليا بوجود أنماط عامة لها تنتج عن الطبيعة التكرارية للبرامج التي تولدها. ومن السهل أن نتوقع أن النجاح في تحليل صفحة واحدة من خرج برنامج ما يمكننا من تحليل أغلب الصفحات التي يولدها نفس البرنامج مباشرة، وباستخدام نفس الأداة التي نجحت في إحدى الصفحات.
المثال المشروح هو تحليل صفحة مقالات نشرة "كلنا شركاء في الوطن" التي يصدرها المهندس أيمن عبد النور، ويوزعها عبر البريد الالكتروني، وعلى الموقع www.all4syria.org. يتم تحرير وإصدار النشرة عبر برنامج من تقدمة موقع مفهوم، ويمكن تمثيل هيكلها بالمخطط التالي

 


يعنينا هنا قسمان أساسيان، وهما قسم العناوين وقسم المقالات. ويمكننا أن نعيد إنتاج النشرة كاملة إذا استطعنا تحليلها واستخراج العناوين والمقالات منها.
لنبدأ أولا بالحصول على النشرة. النص البرمجي بسيط جدا، ويعتمد على فتح الصفحة باستخدام الأمر fopen الذي يستخدم لفتح أي ملف عادي. يجب الانتباه هنا إلى أن حجم الصفحة غير معروف، ولذلك يجب قراءة محتوى الملف بالتدريج حتى الوصول إلى آخره.
للحصول على النشرة نمرر تاريخ اليوم بصيغة خاصة إلى الصفحة
$issue=isset($_GET['issue']) ? $_GET['issue'] : date("Ymd");
$url="http://www.all4syria.org/show_letter.php?issue=$issue";

ثم نفتح الصفحة ونحصل على كل محتوياتها
$fp=fopen($url,"r");
while($data=fgets($fp,128)) $page.=$data;
fclose($fp);

وهكذا نحصل على كامل الصفحة لنبدأ تحليلها. المثال الحالي يستخدم صفحة النشرة يوم 25/10/2003
لنبدأ بالعناوين، ولنستخدم التابع preg_match_all لاستخراج كل العناوين وأسماء الكتاب دفعة واحدة من الصفحة.
بإلقاء نظرة فاحصة على نص الصفحة نجد أن صيغة العناوين ثابتة باستثناء العنوان الأول، خاص بالنشرة، وهو عنوان سطره ينتهي بالأمر </td> مباشرة بعد نهاية الارتباط، وبدون إضافة سطر قبله. ولذلك علينا أن نأخذ هذه الحالة بعين الاعتبار عند صياغة معيار المقارنة.
نلاحظ أيضا أن بعض العناوين لا تحوي اسم الكاتب. ويجب معالجة هذه الحالة أيضا.
بعد عدد من المحاولات يمكن التوصل إلى معيار مقارنة يعطي نتائج دقيقة في هذه الحالة، وهو المعيار الذي نلقمه للأمر preg_match_all كما يلي:
$headlines_pattern='[<a href="#(\d*)">(.*)</a>((: <span class="blue">(.*)</span>)|</td>)]imsU';
preg_match_all($headlines_pattern,$page,$headlines,PREG_SET_ORDER);


الثابت PREG_SET_ORDER يطلب من التابع أن يرتب النتائج حسب حالات التطابق، وليس حسب المعايير، ويمكنكم أن تحذفوه وتقارنوا النتائج.
بنفس الطريقة، وبقليل من الملاحظة والتجريب، نحصل على معيار المقالات:
$articles_pattern='[<p class="standard" align="justify">(.*)</p>]imsU';
preg_match_all($articles_pattern,$page,$articles,PREG_SET_ORDER);


لكي نتأكد من دقة النتائج التي نحصل عليها يمكننا أن نستخدم التابع print_r ونمرر له أي متحول ليعطينا قيمته.
يبدو هيكل خرج الأمر preg_match_all غريبا بعض الشيء، ولكي نفهمه علينا أن نتخيل كيف تعمل مكتبة PCRE، وندرس الفصل الخاص بها في ملف التعليمات المزود مع PHP.
بعد أن حصلنا على المقالات والعناوين وأسماء الكتاب، علينا أن نخرج المقالات بشكل ما، أقترح عليكم تصميم قاعدة بيانات MySql بسيطة من جدول واحد يشبه الجدول المحدد بالأوامر التالية:
CREATE TABLE articles (
article_id int(11) NOT NULL auto_increment,
day int(11) NOT NULL default '0',
title varchar(255) NOT NULL default '',
author varchar(255) NOT NULL default '',
text longtext NOT NULL,
PRIMARY KEY (article_id)
) TYPE=MyISAM;

الآن يمكننا أن نخزن نتائجنا في قاعدة البيانات، مع الانتباه إلى أنه يفترض أن يتطابق عدد المقالات مع عدد العناوين:
if(count($articles) == count($headlines)){
   mysql_connect("localhost","root","");
   mysql_select_db("all4syria");
   mysql_query("DELETE FROM articles WHERE day = '$issue'");
   foreach($headlines as $key=>$val)
   mysql_query("INSERT INTO articles (day, title, author, text) values
      ($issue, '" . addslashes($val[2]) . "', '" . addslashes($val[5]) . "', '" .
      addslashes($articles[$key][1]) . "')"));
}

إذا أردنا أن تتم العملية يوميا بشكل آلي، يمكننا أن نشغلها من أمر Cron على المخدم كل يوم في الساعة السادسة مساء، وهكذا نحصل بشكل آلي على أرشيف لمقالات النشرة، ويمكن البحث فيه وتصفحه بطريقة مختلفة عن طريقة عرض موقع النشرة الأساسي.