Пост про универсальный парсер

29 09 2007

Странное дело, но на эту страницу в основном попадают по фразе “php парсер без regexp“.

Итак, авторитетно заявляю - php парсеров без regexp не бывает. Если интересно почему - велком в аську. Если не интересно почему, но всё - же нужен такой - тоже велком в аську - сделаю в лучшем виде :)

Написал на армаде пост про универсальный парсер, сюда транслирую (ибо это как - никак авторский контент). Думаю они там не обидятся, там статься появилась раньше :), а мне тематического контента прибавится, как никак - несколько часов убил на то чтоб написать код и текст.

И так, между делом, кто зареген на армаде - голосуйте в технической части за меня :) Я вам тогда ещё что - нить интересного выложу ;)

Итак, давайте попробуем написать парсер. Мы будем писать универсальный парсер со сменными “боеголовками - выражениями”. Долго думал насчёт многопоточности… и решил что она или будет внешней или её вообще не будет. Она интересна, но в рамках сбора данных по одному запросу она не нужна. Вариант реализации многопоточности… вот:

Начнём с многопоточности, реализуем её по учебнику, внеся свои усовершенствования:

<?php
define("MAX_PROCESSES", 20);

$items = file("keywords.txt");

$system = $_SERVER['argv'][1];

$num_procs = 0;

foreach ($items as $item)
{
   $pid = pcntl_fork();

   if ($pid == -1)
   {
      die("could not fork");
   }
   elseif ($pid)
   {
      $num_procs++;

      if ($num_procs >= MAX_PROCESSES) while ($num_procs >= round(MAX_PROCESSES/2, 0))
      {
         pcntl_wait($status);
         $num_procs--;
         echo "wait theards... actual ".$num_procs.", max: ".MAX_PROCESSES."\n";
      }
   }
   else
   {
      echo date("Y-m-d H:i:s").": start ".trim($item)."\n";
        shell_exec("php parser.php ".$system." ".trim($item)." > log.txt & echo \$!");
      exit;
   }
}

Моё: прибавляем не по одной нити, а ждём когда все “слоты” заполнятся, а потом освобождаем половину (так процесс будет идти более интенсивно). Не забудьте в конце нити сделать exit, иначе сервер ляжет в момент.

Далее, ближе к телу, определим механизм разбора, допустим нужный регэксп будет определяться значением переменной $system и будет считываться из папки pregs, (структуру выложу), тогда нам надо определить для каждой системы:
-выражение для поиска следующей страницы
-выражение для разбора контента
-начальную маску урла

-формат сохранения.

Определим структуру так:

pregs
*google
**pagesRegexp.txt
<td nowrap class=b><a href=\"(.[^\"]*)\">.*Next<\/a>
**regexp.txt
<h2 class=r><a href=\"(.[^\"]*)\" class=l onmousedown=\"return.[^\"]*\">(.*)<\/a>&
lt;\/h2>
**startUrl.txt
http://www.google.com/search?source=ig&hl=en&q=@PARAMETR@&btnG=Google+Search

формат сохранения в файле item.txt
@1@|@2@

Теперь у нас есть регулярное выражение для поиска следйющей страницы и собственно контента. Сразу скажу, в некоторых системах это неверно, но для большинства применимо. По поводу items, будем считать что это формат сохранения, примеры файлов приведу ниже.

Теперь про отправной урл. Он должен быть, причём он должен быть динамическим. Как правило он зависит от нашего запроса, и не меняет форму… предположим что у нас есть некоторый параметр, который мы будем вводить в командной строке, и на выходе должны получить урл, со вставленным вместо @PARAMETR@ значением.

Для общения с поисковиками будем использовать курл.

Ещё нам понадобится вот функция, ей на вход страницу и урл - она отдаёт абсолютный урл.

в итоге мы получим вот такой код:

<?php

define("MAX_PAGES", 50);

define("PREGS_FOLDER", "pregs");
define("RESULTS_FOLDER", "results");

define("ITEMS_FILE", "items.txt");

define("REGEXP_FILE", "regexp.txt");
define("PAGES_REGEXP_FILE", "pagesRegexp.txt");
define("START_URL_FILE", "startUrl.txt");

define("USER_AGENT", "Mozilla/4.0 (compatible; MSIE 5.01; Widows NT)");

$system = $_SERVER['argv'][1];
$parametr = $_SERVER['argv'][2];

function curlGetPost($url, $referer = null, $postContent = false, $init = false, $followLocation = true)
{
   if ($init || !isset($ch)) $ch = curl_init();

   curl_setopt($ch, CURLOPT_URL, $url);

   curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $followLocation);
   curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
   curl_setopt($ch, CURLOPT_MAXREDIRS, 5);

   if ($referer) curl_setopt($ch, CURLOPT_REFERER, $referer);

   curl_setopt($ch, CURLOPT_USERAGENT, USER_AGENT);

   if ($postContent)
   {
      curl_setopt($ch, CURLOPT_POST, 1);
      curl_setopt($ch, CURLOPT_POSTFIELDS, $postContent);
   }
   else
   {
      curl_setopt($ch, CURLOPT_POST, false);
   }

   if (strtolower(substr($url, 0, 5)) == 'https') {
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
   }

   curl_setopt($ch, CURLOPT_COOKIEFILE, "cooks.txt");

   return curl_exec($ch);
}

function getAbsUrl($page, $link)
{
   $page = trim($page);
   $link = trim($link);

   if (strpos($link, "#") !== false) $link = substr($link, 0, strpos($link, "#"));
   if (strpos($page, "?") !== false) $page = substr($page, 0, strrpos($page, "?"));

   if (strtolower(substr($link, 0, 7)) == "http://") return $link;
   if (strtolower(substr($link, 0, 8)) == "https://") return $link;
   if (strtolower(substr($link, 0, 4)) == "www.") return "http://".$link;

   if (strtolower(substr($link, 0, 7)) == "mailto:") return false;
   if (strtolower(substr($link, 0, 11)) == "javascript:") return false;

   if (substr($link, 0, 1) == "/")
   {
      $url = parse_url($page);
      $url = "http://".$url['host'].$link;
   }
   elseif (substr($link, 0, 1) == "?")
   {
      $url = $page.$link;
   }
   else
   {
      $url = substr($page, 0, strrpos($page, "/")+1).$link;
   }

   while (strpos($url, "../") !== false)
   {
      $nstr = substr($url, 0, strpos($url, "../") - 1);
      $nstr = substr($nstr, 0, strrpos($nstr, "/")+1);
      if (substr($nstr, -2) == "//") return false;
      $url = $nstr.substr($url, strpos($url, "../") +3);
   }

   $url = str_replace("./", "", $url);

   return $url;
}

if (!file_exists($pregFolder = "./".PREGS_FOLDER."/".$system."/"&
#41;)
{
   echo "Parser regular extension dont exists\n";
   exit;
}
else
{
   //формат сохранения
   if (!file_exists($itemsFile = $pregFolder.ITEMS_FILE))
   {
      echo "Items file not exists\n";
      exit;
   } else echo "items - ok\n";
   $items = file_get_contents($itemsFile);

   //регэксп для контента
   if (!file_exists($regexpFile = $pregFolder.REGEXP_FILE))
   {
      echo "Regexp file not exists\n";
      exit;
   } else echo "regexp - ok\n";
   $regexp = file_get_contents($regexpFile);

   //регэксп для сделующей страницы
   if (!file_exists($pagesRegexpFile = $pregFolder.PAGES_REGEXP_FILE))
   {
      echo "Pages regexp file not exists\n";
      exit;
   } else echo "pages - ok\n";
   $pagesRegexp = file_get_contents($pagesRegexpFile);

   //стартовый урл
   if (!file_exists($startUrlFile = $pregFolder.START_URL_FILE))
   {
      echo "Start url file not exists\n";
      exit;
   } else echo "start page - ok\n";

   $startUrl = str_replace("@PARAMETR@", urlencode($parametr), file_get_contents($startUrlFile));

   $tackt = 0;

   $fp = fopen("./".RESULTS_FOLDER."/result_".dat
e("Ymd-His").".txt", 'w');

   $url = $startUrl;

   $grabAll = 0;

   do
   {
      $tackt++;

      $content = curlGetPost($url, $url);

      if ($grab = preg_match_all("/".$regexp."/iUs", $content, $result))
      {
         unset($result[0]);

         foreach ($result[1] as $key => $value)
         {
            $source = trim($items);

            foreach ($result as $lineKey => $lineValue)
            {
               $source = str_replace("@".$lineKey."@", $result[$lineKey][$key], $source);
            }

            fwrite($fp, $source."\n");
         }

         $grabAll += $grab;

         echo "Tackt ".$tackt.", grab:".$grab.", all: ".$grabAll."\n";
      }
      else
      {
         echo "Tackt ".$tackt.", current empty, all: ".$grabAll."\n";
      }

      if (!preg_match("/".$pagesRegexp."/iUs"
, $content, $result))
      {
         echo "Parsing ends, next page not found, tackt: ".$tackt."\n";
         fclose($fp);
         exit;
      }
      else
      {
         $url = getAbsUrl($url, $result[1]);
      }

   } while ($tackt <= MAX_PAGES);
}

Код работает, проверял. Исходя из описания парсить он может что угодно (что он парсит определяется 3 файлами, содержание которых я привел. Вот здесь можно скачать полную версию .

Извините за то что я плохо код описал и нет коментов… данная статья только для программистов, спрашивайте кому что интересно.

Ясное дело что на папку results нужно 777 поставить.

Google Bookmarks Digg Reddit del.icio.us Technorati News2.ru БобрДобр.ru RUmarkz Ваау! Memori.ru

Informations

9 responses to “Пост про универсальный парсер”

30 09 2007
XYDOGHIK (12:44:52) :

По мне так универсальный это когда парсит все и без шаблонов. А это обычный парсер.

1 10 2007
admin (00:46:18) :

Извини, но когда парсит всё и без шаблонов - это бред. Так не бывает в природе. Надо всё - таки отталкиваться от действительности, а не от фильмов Спилберга….

5 03 2008
zetro (23:39:44) :

AI в студию! :-)

6 03 2008
admin (04:26:56) :

что в студию?

6 03 2008
zetro (22:54:17) :

Искуственный интеллект) Про парсер который парсит все и без шаблонов) Пишешь в конфиге: Спарсить википедию, ну пожалста! Утром просыпаешься, а википедия уже на твоем хостинге работает)))

7 03 2008
admin (13:51:45) :

а… :) да, было - б классно. За одно чтоб скрипты, обвязку, структуру и данные из БД. Ну и операционку с сервера, если она специфичная.

И админа конечно :) А то вдруг упадёт, кто поднимать будет? :)

20 01 2009
Траф на сайт » Seo service, мои мысли и дела, seo, водка и фотки в Пензе (04:00:51) :

[...] php парсер без regexp Уважаемые… парсер без регэкспа - это как деревянный дом без гвоздей. Уже давно не делают, да и не надо. В общем по таким вопросам пишем напрямую в аську, на тему помощи - денег не возьму. Если что - то надо написать - договоримся :). [...]

28 02 2010
kolchakA (21:24:48) :

Вот на серче продается тоже из этой серии – только в виде ворпдресс плагина:
forum searchengines ru/showthread.php?t=464753

[kolchaka net]

5 03 2010
admin (17:04:41) :

Are you robot?

Уважаемые комментаторы!

Если вы пишете в комментарии к посту или странички и в нём указываете адрес своего сайта, этот сайт должен быть вашим блогом, или личным сайтам. Все комментарии с ссылками на непонятные саттелиты будут редактироваться. Не утруждайте себя и не тратьте своё и моё время. Ссылки на ваши проекты должны быть в тексте и должны быть по теме предложения в комментарии (по теме или нет - определяю Я).
Комменты не по теме, либо по теме "блин, кипяток - то горячий!" приравниваю к спаму.

*
To prove that you're not a bot, enter this code
Anti-Spam Image

You can use these tags : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>



Блогун - монетизируем блоги