Поиск по блогу

среда, 18 ноября 2009 г.

Парсинг findarticles.com - part 1

Итак, в одном из предыдущих постов (в частности, где описывала парсинг ключевиков), я пообещала рассказать о способе дальнейшего употребления данных, которые мы добыли.

Для этих ключевиков, допустим, нам понадобится собрать статьи, им соответствующие (хотя бы отдаленно). Какие-нибудь, без разницы. Я покажу как это сделать, например, с ресурса findarticles.com. Это агрегатор, он сам тянет материал из всевозможных источников, так что от него не убудет :)

Ставим себе задачу.
Имеем: список ключевиков.
Требуется: получить набор текстовых статей. Дополнительное требование: чтобы в каждой статье было не менее определенного числа символов. Это требование возникло из-за того, что на данном агрегаторе много всякого "мусора", могущего состоять из одной строчки, например. А такого добра нам не надо, сразу отсечем этот вариант.

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

Второй вариант - это разбить алгоритм на части. Такой прием я уже иллюстрировала на этом блоге в материалах, где описывала опыт парсинга аватаров. То есть берем слово, делаем запрос, собираем ссылки на статьи, выведенные по этому запросу (проходимся по всем страницам или по определенному количеству страниц, заданному в настройках). Собранные ссылки используем для второго этапа: проходим по полученному списку и парсим статьи.

Для парсинга такого большого ресурса, как findarticles, безусловно, лучше выбрать второй алгоритм, что и сделаем.



Исследуем ресурс и запросы. Введем фразу в поле для запроса, выберем поиск только по бесплатным статьям. Нажмем на кнопку :)




Принцип составления запроса понятен.
На всякий случай прогуляемся по страницам выдачи. Get-запрос в поле браузера немного видоизменился, остановимся на этом его варианте:

http://findarticles.com/p/search/?qf=free&qt=bad+boys&sn=0&tag=content;col1

Вывод результатов разделен на страницы по 10 элементов. Параметр sn в запросе отвечает за то, с какого порядкового номера выводить статьи. Для второй страницы он будет равен 10, для третьей - 20 и так далее. То есть для перехода на следующую страницу нам надо просто инкрементировать данный параметр. Как мы узнаем, что страницы закончились? Очень просто. На каждой странице мы будем собирать ссылки на материалы (составим регулярное выражение). Когда в результате поисков ссылок на странице будет ноль - это значит, что пора сворачивать деятельность и дальше уже ловить нечего :)

Вроде пока все понятно, приступаем к программированию. Для примера я помещу здесь парсинг ссылок по одному запросу, а не по списку запросов. Переделать его, если что, будет несложно.

Привожу скрипт, который у меня получился. Постаралась по-максимуму подробно прокомментировать. В листинге вы встретите функцию get_web_page, уже знакомую вам по материалам о CURL на этом блоге.


<?php
// findarticles.com parser

/* сохранять статьи, количество символов в которых больше чем */
$min_col_symbols = 100;
/* парсить количество страниц, здесь ограничимся двумя */
$col_of_str = 2;

function get_web_page( $url )
{
$uagent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)";

$ch = curl_init( $url );
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // возвращает веб-страницу
curl_setopt($ch, CURLOPT_HEADER, 0); // не возвращает заголовки
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // переходит по редиректам
curl_setopt($ch, CURLOPT_ENCODING, ""); // обрабатывает все кодировки
curl_setopt($ch, CURLOPT_USERAGENT, $uagent); // useragent
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 120); // таймаут соединения
curl_setopt($ch, CURLOPT_TIMEOUT, 120); // таймаут ответа
curl_setopt($ch, CURLOPT_MAXREDIRS, 10); // останавливаться после 10-ого редиректа

$content = curl_exec( $ch );
$err = curl_errno( $ch );
$errmsg = curl_error( $ch );
$header = curl_getinfo( $ch );
curl_close( $ch );

$header['errno'] = $err;
$header['errmsg'] = $errmsg;
$header['content'] = $content;
return $header;
}

set_time_limit(0);

$page_num = 1; // с какой по счету страницы парсить
$kw = 'bad boys'; // само ключевое слово

$need_next_str=1;

// массив со страницами, которые уже загружали
if (file_exists("results.txt"))
$link_array = file("results.txt");

while ($need_next_str==1)
{
$new_rec=0;
$from = 10*($page_num-1);
// не забываем про urlencode!
$url = 'http://findarticles.com/p/search/?qf=free&qt='.urlencode($req).'&sn='.$from.'&tag=content;col1';

$result = get_web_page( $url );
$html = $result['content'];

$a_pattern = '#<h2>[\n\s] <a\shref="?(.*?)"?>(.*?)<\/a>&nbsp;[\n\s] <\/h2>#im';
if (preg_match_all($a_pattern, $html,$a_matches, PREG_SET_ORDER))
{
foreach ($a_matches as $a)
{
// если такая ссылка уже была найдена - не добавлять
$was_found = 0;
// проходим по списку из файла и сравниваем ссылку с имеющимися в наличии
for ($i=0;$i<count($link_array);$i++)
{
if (strcmp(trim($link_array[$i]),trim($a[1]))===0)
{
$was_found = 1;
break;
}
}
if ($was_found==0)
{
// дописываем в конец файла ссылку для дальнейшей обработки
$f_name = 'results.txt';
$f=fopen ($f_name, 'a');
$cont = $a[1]."\r\n";
fwrite ($f, $cont);
fclose ($f);
// а также добавляем в массив ссылок, который держим в уме, чтобы не повторяться :)
$link_array[] = $a[1];
}
}
}
else { echo 'Ничего не найдено!'; $need_next_str = 0;}

// если на странице нет новых ссылок, то дальше ходить и не надо
if ($new_rec==0) { $need_next_str=0; }

// если количество пропарсенных страниц достигло значения, указанного в настройках - остановиться
if ($page_num >= $col_of_str) { $need_next_str=0; }

$page_num++;
if ($need_next_str == 1) sleep(1);
}

echo 'Парсинг окончен!';

?>



Результатом работы файла будет файл results.txt с ссылками на статьи. Они пригодятся нам для второго этапа.


В следующей части рассмотрим парсинг статей по ссылкам.
___

Чтобы быть в курсе обновлений блога, можно подписаться на RSS.

___

А еще продолжаю выражать благодарность людям, упомянувшим на своих блогах мой RSSAdder. У меня пока нет времени его развивать, но когда-нибудь оно обязательно появится! И хорошим стимулом к этому может послужить счетчик скачиваний программы :))

Итак, сегодня мои "спасибо" — Владимиру (вот его блог) и Дмитрию, автору блога обо всем и ни о чем :)

Остальных поблагодарю позже, чтобы не было каши из ссылок :)

Статьи схожей тематики:



6 комментариев:

  1. Блин, Люди добрые, Маша. я вас прошу, помогите. я сутки пытаюсь реализовать работу функции

    function em_return(pattern,text:string):MatchCollection;
    var
    R: TRegExp;
    mc: MatchCollection;
    m: Match;
    sm: SubMatches;
    i, j: Integer;
    full_text:string;
    begin cs.Enter;
    R := TRegExp.Create(nil);
    try
    R.Pattern := pattern;
    R.IgnoreCase := True;
    R.Global := True;
    R.Multiline:=True;
    mc := R.Execute(text) as MatchCollection;
    Result:= mc;
    finally
    m := nil;
    sm := nil;
    mc := nil;
    R.Free;
    end;
    cs.Leave;
    end;

    в потоке, но она всё равно выдает
    ошибку! я вас прошу подскажите что не правильно делаю или другую функцию дайте плиз. очень нужно :(

    ОтветитьУдалить
  2. Мельком просмотрел тематику бложика, прочитал о чем он и удивился - парсеры на Delphi и PHP? Эмм а как же PERL ведь именно он «практический язык для извлечения данных и составления отчётов» в котором и была разработана концепция регулярных выражений. В нем парсинг документа проводится намного легче понятнее и быстрее.

    ОтветитьУдалить
  3. Gar¦k, PERL уныл чуть менее чем полностью. С РНР и Delphi работать гораздо проще и понятнее.

    ОтветитьУдалить
  4. а вот это не подходит?
    http://wordstat.yandex.ru/?cmd=words&page=1&text=&geo=&text_geo=

    ОтветитьУдалить
  5. Ошибка в тексте программы - условие циклм for задано не до конца!
    for ($i=0;$i {
    if (strcmp(trim($link_array[$i]),trim($a[1]))===0)

    ОтветитьУдалить
  6. Спасибо, что заметили! Код был верный, но blogger знак "меньше" по ходу воспринял как начало тега и поэтому некорректно отобразил.

    Вы можете скачать исходные коды парсера по ссылке http://narod.ru/disk/16283941000/fa_parser.zip.html. Там-то уж блоггер точно ничего не "зажевал"))

    ОтветитьУдалить

Комментарии модерируются, вопросы не по теме удаляются, троллинг тоже.

К сожалению, у меня нет столько свободного времени, чтобы отвечать на все частные вопросы, так что, может, свой вопрос лучше задать на каком-нибудь форуме?

Поделиться