Пост про универсальный парсер
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 поставить.

По мне так универсальный это когда парсит все и без шаблонов. А это обычный парсер.
Извини, но когда парсит всё и без шаблонов - это бред. Так не бывает в природе. Надо всё - таки отталкиваться от действительности, а не от фильмов Спилберга….
AI в студию!
что в студию?
Искуственный интеллект) Про парсер который парсит все и без шаблонов) Пишешь в конфиге: Спарсить википедию, ну пожалста! Утром просыпаешься, а википедия уже на твоем хостинге работает)))
а…
да, было - б классно. За одно чтоб скрипты, обвязку, структуру и данные из БД. Ну и операционку с сервера, если она специфичная.
И админа конечно
А то вдруг упадёт, кто поднимать будет?
[...] php парсер без regexp Уважаемые… парсер без регэкспа - это как деревянный дом без гвоздей. Уже давно не делают, да и не надо. В общем по таким вопросам пишем напрямую в аську, на тему помощи - денег не возьму. Если что - то надо написать - договоримся :). [...]
Вот на серче продается тоже из этой серии – только в виде ворпдресс плагина:
forum searchengines ru/showthread.php?t=464753
[kolchaka net]
Are you robot?