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

четверг, 24 декабря 2009 г.

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

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

Ресурс findarticles.com как раз поможет мне продемонстрировать ход "изысканий" :)

Открываем любую статью и тщательно вглядываемся в код.

Как я уже писала, есть два основных способа "добычи" данных из html: применение регулярных выражений и разбор DOM-модели. Наверное, для практики стоит разобрать оба.

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

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

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

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

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

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

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

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

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

среда, 14 октября 2009 г.

CURL и реализация POST-запросов

С GET-запросами мы вроде как разобрались. Разобраться с POST-запросами будет ничуть не сложнее. В библиотеке CURL предусмотрены возможности отправления POST-запросов, а так же поддержка работы с сессиями.

Разберем сразу на примере. Так как принципы работы такие же, что и принципы работы с POST-запросами в Delphi (см. статью Пример авторизации на сайте с помощью idHTTP.Post), то и пример возьму тот же, что и в той статье, а именно — авторизация в ЖЖ.

При организации POST-запроса достаточно будет добавить в свойства объекта curl параметр CURLOPT_POST, установленный в 1 (или true) и CURLOPT_POSTFIELDS, значением которого будет строка со значениями переменных в таком виде:

param1=value1&param2=value2&param3=value3&...&paramN=valueN


среда, 30 сентября 2009 г.

Мысли об RSSAdder-е и свободнораспространяемом ПО вообще

В этот крайний день первого месяца осени решила подвести кое-какие итоги относительно RSSAdder-а. Программа выложена на потеху публике уже почти полтора месяца. За это время ее скачали около двухсот человек. Ini-файл (тот, который со списком из 45 агрегаторов вместо изначальных 20) скачали всего 70 раз, почти в 3 раза меньше, чем саму программу. Что, в общем-то, говорит о многом ;D. В частности о том, что большинство людей, скачивая программу, за обновлениями не следят. Чего я добилась? Не знаю, я и не рассчитывала на что-либо, это был своего рода эксперимент: почувствовать себя в роли техподдержки свободнораспространяемого ПО. Спасибо людям, оставляющим свои отзывы о программе. Мне очень приятно знать, что она оказалась полезной еще кому-то кроме меня :)

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

Спасибо автору блога ochonline.ru и "соратнице по IT-цеху" Лорел за информационную поддержку :) Добавлены:

46 http://news.nofollow.ru/?page=addrss
47 http://catalog.gpmv.ru/add/1
48 http://www.rss-lenta.ru/rss/add
49 http://rssbiz.ru/add_src/
50 http://rssmonster.ru/index.php?module=rss&action=rss_submit
51 http://ay2.ru/index.php?module=rss&action=rss_submit


Саму программу тоже перезалила, включила в нее сразу конфиг с 51 агрегатором, чтобы не утруждать пользователей скачивать прогу, а потом отдельно конфиг )) Если вы скачали более раннюю версию, то можно обновить только конфиг.

Ссылка для скачивания самой свежей версии конфига указана в примечании к программе на файлообменнике. (Не выкладываю сюда эту ссылку, чтобы не заботиться об ее обновлении, ведь конфиги меняются время от времени).


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

вторник, 22 сентября 2009 г.

Примеры применения CURL. Парсинг ключевых слов (буржунет)

Приведу пример парсинга на php. С предыдущими темами пост будет связан способом получения содержимого страницы, то бишь получать ее будем с использованием библиотеки CURL.

Сначала о том, что парсить. А будем парсить ключевики. Но не с гугля, нет. Поищем в интернете какой-нибудь сторонний сервис. Первое, что я нашла, это http://freekeywords.wordtracker.com/. Изучаем страницу. На ней форма. Заполняем, отправляем.


Экспериментируем с adult-фильтром.


Данные передаются GET-запросом, то есть вообще никаких премудростей — просто используем функцию получения контента из предыдущего поста, а потом регуляркой добываем нужные нам данные. Шаблон GET-запроса получаем, введя данные непосредственно в форму на сайте, установив нужное нам значение adult-фильтра и нажав на кнопку. У меня получился следующий шаблон (значение фильтра — по умолчанию):

$url = 'http://freekeywords.wordtracker.com/?seed='.$keyword.'&adult_filter=remove_offensive&suggest=Hit+Me';


(Вместо $keyword будем подставлять ключевик.)

Следует обратить внимание, что если в анализируемом нами ключевике несколько слов, разделенных пробелом, то пробел заменяется на "+".

Вот такой у нас получится скрипт (в результате все 100 ключевиков я просто выведу на экран, но можно их записать в файл и т.д.):

<?php
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;
}

// ключевое слово
$keyword = 'finance';
// избавляемся от пробелов, если они есть
$keyword = strtr($keyword, ' ', '+');

$url = 'http://freekeywords.wordtracker.com/?seed='.$keyword.'&adult_filter=remove_offensive&suggest=Hit+Me';

$result = get_web_page( $url );
$err = 0;
if ( $result['errno'] != 0 )
{ //... ошибка: неправильный url, таймаут, зацикливание ... обработать по желанию
$err = 1;
}

if ( $result['http_code'] != 200 )
{ //... ошибка: нет страницы, нет прав ... обработать по желанию
$err = 1;
}

$page = $result['content'];

$a_pattern = '#<a\shref="(\?seed.*?)">(.*?)</a>#sm';
if (preg_match_all($a_pattern, $page,$a_matches, PREG_SET_ORDER))
{
foreach ($a_matches as $a)
echo $a[2].'<br>';
}
else echo 'Ничего не найдено!';
?>


Результат работы скрипта:


Если нам ключевиков надо не сто, а в разы больше, то придется модифицировать алгоритм для рекурсивного прохода по списку ключевиков, полученных на ранних этапах.

Зачем нам нужны эти ключевики? Применение можно найти. Об одном из способов применения расскажу в ближайшем посте.

Если вам понравилась статья, проголосуйте, пожалуйста, за нее на Sloger.net

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

Установка CURL на Denwer

CURLРаз уж решила здесь писать про CURL, то напишу и про установку. А конкретно — про установку на Denwer. В базовый пакет PHP, поставляемый с Denwer-ом, она не включена. Но это недоразумение легко устранить.

1) Cкачиваете пакет расширения "PHP5: дополнительные модули": http://www.denwer.ru/packages/php5.html
2) Устанавливаете его
3) Открываете в редакторе файл usr/local/php5/php.ini и снимаете комментарий со строчки:

;extension=php_curl.dll


4) Denwer Restart Servers

Вот и все. Чтобы убедиться, что все установилось нормально, можете проверить данные, сгенерированные функцией phpinfo(). Там должно быть cURL support enabled.

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

понедельник, 14 сентября 2009 г.

CURL. First steps

Как правило, изучение нового инструмента всегда откладывается: "Потом. Еще успеется. Надо собраться с мыслями и все такое". Именно так у меня когда-то откладывалось начало работы с библиотекой CURL (Client URL). Эта статья будет для новичков. Главная ее цель — показать, что ничего страшного здесь нет. И можно получить базовые представления за те 10 минут, которые уйдут на чтение статьи. Более того — вы сразу можете опробовать новые навыки в деле, используя листинги простейших функций, которые я буду приводить по мере раскрытия темы.

Прикинув приблизительный объем материала, я поняла, что в одну статью все не влезет, так что продолжение будет следовать :)

Для того, чтобы начать работать с библиотекой CURL, ее надо установить. Инструкций установки CURL в сети найти можно много, поэтому останавливаться на этом не буду.

Перейдем сразу к работе. Разберем простой пример использования библиотеки CURL для получения кода страницы. Этот пример вы можете часто встретить в листингах скриптов (я взяла его с какого-то форума).

function get_web_page( $url )
{
$uagent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.8";

$ch = curl_init( $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;
}


В эту функцию в качестве параметра передается URL, а возвращается ассоциативный массив, состоящий из заголовка и содержимого страницы. Функции CURL автоматически управляют редиректами, кукисами, декомпрессией файлов.

(Как хорошо, что в блоггере появилась простая возможность скрывать часть поста под кат! Сейчас, наконец, на главной не будет простыней листингов. А читателям, я думаю, будет не сложно один лишний раз кликнуть на "Читать далее")

четверг, 3 сентября 2009 г.

Работа с формами в TWebBrowser

Эта статья — своеобразное логическое продолжение статьи об OleObject в TWebBrowser, переведенная мной. В ней содержится информация о том, как получить доступ к данным о формах, расположенных на странице в TWebBrowser, и к элементам этих форм.

Основные вопросы, которые будут освещены:

Получение количества форм на странице
Обращение к форме по порядковому номеру
Получение имени формы
Получение доступа к форме по имени
Получение списка имен всех элементов формы
Получение value элемента формы (по name элемента)
Установка value элемента формы (по name элемента)
Сабмит формы


В листинге примеров, приведенных в этой статье, компонент TWebBrowser, в который загружаются страницы, на форме называется WebBrowser. Для работы с интерфейсами, приведенными в листингах, подключите библиотеку MSHTML.

среда, 2 сентября 2009 г.

Add to RSSAdder

Пост для тех, кто пользуется RSSAdder-ом.
В нашем полку прибыло. Встречайте еще 15 RSS-агрегаторов, включенных в список.
31 http://demohost.net/rssdirectory3/addfeed.html?catid=
32 http://www.leighrss.com/rss-add.html
33 http://www.2rss.com/
34 http://www.icerocket.com/c?p=addblog
35 http://www.aspin.com/func/addres/rss-support
36 http://www.weblogalot.com/Ping/
37 http://www.feedplex.com/add-url.php?email=
38 http://www.goldenfeed.com/AddFeed.aspx
39 http://www.jordomedia.com/RSS/l_op=Addrss.html
40 http://www.anse.de/rdfticker/addchannel.php
41 http://rss.ukrnews.net/reg.php
42 http://www.rssmicro.com/feedsubmit.web
43 http://www.readablog.com/AddFeed.aspx
44 http://www.redtram.com/pages/addsource/
45 http://www.realty-feeds.net/submitrss.php


Новый ini-файл можно скачать тут (как и в прошлый раз — замените данные о моем блоге на свои).
___

Для тех, кто RSSAdder-ом не пользуется, новостей пока нет, так как нахожусь в творческих метаниях :)

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

четверг, 27 августа 2009 г.

DOM vs. RegExp on PHP

dom regexp on php
Последние 2 недели было несколько заказов на написание парсеров на PHP. И я за них бралась, в основном за чисто символическую плату, чтобы повариться в PHP-шной кухне, попробовать разные способы парсинга и, заодним, подучиться не на чистом энтузиазме. ;)

Хочу рассказать о своих впечатлениях.

Парсер на PHP — это удобно и менее време- и ресурсозатратно для разработчика. Естественно, если скрипт предназначен только для скачивания какой-либо базы и последующей проверки обновлений. Ни тебе особых интерфейсов, ни тебе всяких ненужных классов: скачиваешь страницу, парсишь, в базу, по кругу. Для многофункционального ПО по-прежнему использую Delphi.

Скрипты удобны для удаленного запуска по расписанию (не забудьте установить set_time_limit(), чтобы скрипт не останавливался, не доработав до конца).

Когда-то я спрашивала читателей блога, чем удобнее парсить, регулярками или через библиотеку DOM. Тогда прозвучал только один конкретный ответ. Что ж, зато у меня был повод самостоятельно потестировать разные инструменты. Что могу сказать по поводу того, каким способом удобнее парсить html? Чаще всего я все-таки использовала регулярные выражения, потому что привыкла, наверное. Пару раз использовала комбинированный метод: сначала большой табличный кусок "добывала" регуляркой, а потом уже эти хорошо структурированные данные разбирала через DOM.

Чем неудобна библиотека DOM? Тем, что она все-таки не очень хорошо разбирает данные, если они невалидны. Если у вас включено отображение ошибок (php_value display_errors 1), вы можете легко убедиться в этом, взяв произвольный ресурс и попробовав загрузить его страницу в dom-объект

$dom->loadHTML($html);


Выдается список ошибок: то неизвестный тэг, то точка с запятой лишняя, то еще какая-нибудь фигня.

Так что мой выбор все-таки в пользу регулярок, а dom я оставляю для парсинга XML.

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

вторник, 25 августа 2009 г.

С прошедшим днем рождения, блог! :)

Оказывается, этому блогу уже год! Поздравляю себя от имени себя, что у меня хватило мужества его не забросить :) Первая запись была опубликована здесь 20 августа 2008 года. Думаю, что он проживет еще не один год. Задумок для публикации очень много, даже уже есть несколько заготовок, нуждающихся лишь в небольшом причесывании. Но сегодня новой публикации не будет — очень много работы навалилось.

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

И благодарю за информационную поддержку автора блога OnLife.me, который, кстати, собирает на своем блоге список блогов (сорри за масло масляное) для обмена постовыми. А так же авторов блогов news-i.net и Grand Блог. Спасибо-спасибо!

Еще немного запоздалое "спасибо" Владу. Начинающим "делфёрам-парсингёрам" обязательно читать его блог "Delphi в Internet"! :)

Если кто-нибудь еще ставил ссылки на RSSAdder — отметьтесь, пожалуйста, с меня ссылка ;) А то в блоггере нет надежного инструмента для мониторинга обраток.

Для меня этот опыт выкладывания в паблик небольшой бесплатной утилитки оказался очень полезным. Сделала для себя определенные выводы и все такое :)
___

На сегодня это все. Возвращаюсь к работе :)

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

четверг, 20 августа 2009 г.

Еще обновление списка RSS-каталогов

В конфиг добавлено еще 10 каталогов.
21 http://search.yahoo.com/mrss/submit
22 http://content.mail.ru/cgi-bin/list.cgi?action=rss_add
23 http://www.kanban.ru/add.asp
24 http://www.asteria.com.ua/kms_blog+add.html
25 http://www.arkadia-ko.com/kms_blog+add.html
26 http://www.seo-box.com/kms_blog+add.html
27 http://www.sevrealty.com/kms_blog+add.html
28 http://blogrider.ru/catalog/blogs/add
29 http://rss-farm.ru/catalog.aspx
30 http://elite-consult.com/index.php?nma=blog&fla=add


Спасибо Андрею за "информационную поддежку" и за предоставленный список. :) Я его (список) еще не просмотрела до конца, выловила пока только 10 ресурсов (некоторые из ресурсов уже не работали, да и дубли попадались... да и немного утомляет, если честно :)) ).

Решила не постить сюда обновление в текстовом виде, а просто выложить свежий конфиг (5 Кб), в котором все 30 ресурсов (когда скачаете, информацию о своей RSS возьмите из вашего INI-файла, а то там моя по умолчанию).
___

А сама я, тем временем, неторопливо готовлю статьи для новичков про cURL и парсинг на PHP (с примерами и все такое) :)
___

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

среда, 19 августа 2009 г.

Расширение списка RSS-агрегаторов

Выкладываю конфиги еще для 10 агрегаторов. Как вам их добавить? Просто открываете файл RSSAdder.ini любым текстовым редактором и дописываете их в конец файла (если вы самостоятельно ничего не добавляли к тем десяти агрегаторам, которые там были, — нумерация не нарушится).

вторник, 18 августа 2009 г.

Упс.

В RSSAdder-е был глюк, пропустила при тестировании :( Но он обнаружился и был исправлен. Извините, если уже скачали, — я нечаянно. :) Выложила новый вариант (там в INI добавила еще 2 RSS-каталога, сейчас их стало 10). В предыдущем посте ссылку тоже исправила.

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

понедельник, 17 августа 2009 г.

RSSAdder — добавление RSS в список RSS-агрегаторов

Хочу выложить свою простенькую программку, которая поможет вам упростить регистрацию RSS-ленты вашего блога в RSS-агрегаторах. Я когда-то уже писала о ней. А недавно извлекла из архивов, немного переработала и добавила несколько полезных функций.

Интерфейс программы сейчас выглядит следующим образом:


Все настройки хранятся в INI-файле: это и информация об RSS (если вы ее сохраните), и список RSS-агрегаторов. Список агрегаторов хранится в следующем виде:

[SITExx]
url=
field_url=
field_rss_url=
field_rss_name=
field_dsc=
field_keys=
field_email=
key_delimiter=


[SITExx] - RSS-агрегатор, где xx - номер по порядку
url - ссылка на страницу добавления RSS-ленты
field_url - поле "URL сайта"
field_rss_url - поле "URL RSS"
field_rss_name - поле "Название RSS/сайта"
field_dsc - поле "Описание сайта"
field_keys - поле "Ключевые слова"
field_email - поле "Email"
key_delimiter - разделитель ключевых слов

В качестве значений для полей формы можно указывать name поля или его id. Программа автоматически воспримет и то, и другое.

Сначала введите в шапке формы данные о своей RSS-ленте и о блоге.

Чтобы добавить RSS в каталог из списка, расположенного в левой части формы, надо дважды кликнуть на нужном элементе списка. В WebBrowser загрузится форма, в которой будут заполнены поля в соответствии с данными в шапке и данными о названиях/id полей в ini. Вам остается только довводить недостающие параметры (рубрики, капчу и т.д.) и нажать сабмит. Если регистрация в каталоге состоит из нескольких шагов, то после заполнения всех полей вам придется самостоятельно перейти к следующему шагу. Если на следующем шаге от вас требуется заполнение полей, предусмотренных в настройках, то вы можете просто нажать форму "Заполнить форму".

Еще из нововведений:
- рядом с каждым полем сделала кнопочку "Копировать в буфер", на всякий случай :)
- если вы редактируете список агрегаторов в INI-файле, то после его сохранения можно обновить данные в списке не закрывая программу, а просто нажав на "Обновить список из INI"
- добавила закладку HTML Form Viewer для удобства исследования новых RSS-каталогов. Если вы нашли новый каталог, то введите URL для добавления ресурса в строку в верхней части формы, нажмите "Загрузить". После этого загрузится форма, а внутри каждого инпута типа "text" и "textarea" будут подписаны их name и id.



Вам останется только впечатать эти названия в форму в нижней части и нажать "Сформировать код". И в memo появится запись, для добавления в INI-файл, составленная по шаблону. Не забудьте изменить "xx" в строке "[SITExx]" на номер по порядку в соответствии с ранее добавленными записями, а потом дописать полученный результат в свой INI-файл.

У меня к вам есть просьба. Я включила в INI-файл 51 RSS-агрегатор — это мой посильный вклад в формирование списка. Сейчас — ваша очередь. присылайте настройки для новых каталогов на емейл momkarla@gmail.com или оставляйте в комментах. Я буду собирать и выкладывать обновленный список у себя на блоге.

Скачать RSSAdder (1.1 Mb) (в комплектации — 51 RSS-агрегатор).
Редакция от 24.08.2010: Скачать последнюю версию INI-файла (82 агрегатора). На случай, если кто-то скачивал более раннюю версию программы и нужен только "свеженький" конфиг. После скачивания замените INI-файл в папке с программой, оставив свои данные о блоге.

У кого проблемы с Яндексом :) — качайте RSSAdder с зеркала.

Еще было бы очень здорово, если бы вы проанонсировали этот пост у себя на блогах. Ведь чем больше пользователей воспользуются программой — тем больше агрегаторов может появиться в списке :) Кто поставит ссылку — отписывайтесь в комментах, с меня ответная благодарственная ссылка :)
___

P.S.: У кого есть аккаунт на Sloger.net — проголосуйте, пожалуйста, за новость.
___

Отдельное спасибо за размещение ссылки на этот пост:
автору блога "Заметки молодого блоггера"
___

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

понедельник, 10 августа 2009 г.

Парсинг HTML на PHP

Потихоньку изучаю возможности PHP для создания парсеров. Я уже писала о том, как парсить xml на php с помощью SimpleXML. Сейчас расскажу об одном из способов парсинга html (он подойдет и для xml тоже, кстати). Повторю, что в php я не гуру, поэтому буду очень признательна, если вы оставите свои комментарии к поднятой теме.

Побродив по нашим и англоязычным форумам, поняла, что спор о том, лучше ли парсить html регулярными выражениями или использовать для этих целей возможности PHP DOM, является холиваром. Сама же я пришла к выводу, что все зависит от сложности структуры данных. Ведь если структура достаточно сложная, то с помощью регулярок приходится парсить в несколько этапов: сначала выделить большой кусок, потом разделить его на более маленькие и т.д.. В итоге, если данные сложные (или их очень много), то процесс парсинга может значительно затянуться. Ресурсоемкость в этом случае еще будет зависеть, конечно же, от самих регулярных выражений. Если в регэкспах много ".*" (они являются самыми ресурсоемкими, т.к. "прочесывают" исходный код с максимальной жадностью), то замедление будет заметным.

И вот как раз в этом-то случае как нельзя кстати приходится PHP DOM. Это удобный инструмент для парсинга как XML, так и HTML. Некоторые придерживаются мнения, что парсить html регэкспами вообще нельзя, и яростно защищают PHP DOM.

В свою очередь я ознакомилась с этим расширением, написав простенький скрипт. Который и привожу здесь, чтобы наглядно показать, как это все легко и просто. В примере разбирается html с частью карты сайта этого блога. Он присвоен переменной прямо внутри кода. В "боевых" же условиях исходные данные следует получать, например, через file_get_contents().


<?php
$html = '
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<head>
<title>Parsing-and-i.blogspot.com Map</title>
</head>
<body>
<h2>Последние темы блога</h2>
<!-- на 09.08.2009 -->
<table border="0">
<tbody>
<tr>
<td><a href="http://parsing-and-i.blogspot.com/2009/08/blog-post_06.html" title="Базы">http://parsing-and-i.blogspot.com/2009/08/blog-post_06.html</a></td>
<td>Базы</td>
</tr>

<tr>
<td><a href="http://parsing-and-i.blogspot.com/2009/08/mysql-delphi-express.html" title="MySQL и Delphi. Express-метод">http://parsing-and-i.blogspot.com/2009/08/mysql-delphi-express.html</a></td>
<td>MySQL и Delphi. Express-метод</td>
</tr>

<tr>
<td><a href="http://parsing-and-i.blogspot.com/2009/08/blog-post.html" title="Пост о том, что лучше сто раз проверить">http://parsing-and-i.blogspot.com/2009/08/blog-post.html</a></td>
<td>Пост о том, что лучше сто раз проверить</td>
</tr>

</tbody>
</table>

</body>
</html>
';
/** создаем новый dom-объект **/
$dom = new domDocument;

/** загружаем html в объект **/
$dom->loadHTML($html);
$dom->preserveWhiteSpace = false;

/** элемент по тэгу **/
$tables = $dom->getElementsByTagName('table');

/** получаем все строки таблицы **/
$rows = $tables->item(0)->getElementsByTagName('tr');

/** цикл по строкам **/
foreach ($rows as $row)
{
/** все ячейки по тэгу **/
$cols = $row->getElementsByTagName('td');
/** выводим значения **/
echo $cols->item(0)->nodeValue.'<br>';
echo $cols->item(1)->nodeValue.'<br>';
echo '<hr>';
}
?>


В результате после запуска скрипта получаем такую картину:
Parsing html with dom php


Upd: Без всякого сомнения, для более удобной работы со структурой HTML в PHP вам надо познакомиться с библиотекой PHP Simple HTML DOM Parser. Я бы отдала предпочтение именно ей.


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

четверг, 6 августа 2009 г.

Базы

В эту запись буду время от времени добавлять различные базки, которые я напарсила из интернета на добровольных началах.

База №1
База предприятий с hh.ru (название, описание, сайт, ссылка на профайл на хэдхантере). Формат — MySQL. Сразу напоминаю, база неполная. Сначала запустила парсер глобально, на все регионы, но выкачать всю не удалось — запустила только на Москву. В базе в поле REGION: 0 - предприятия, скачанные по запросу "Все" (в том числе частично и Москва), 1 — стопроцентная Москва (без дублей). Всего 35217 записей. companies.zip Размер: 13,3 Мб.

___

Предлагаю авторам тематических блогов обменяться постовыми. У моего блога ТИЦ20, PR3, DMOZ. Подписчиков на сегодня приблизительно 130. Рассмотрю все предложения с блогов с примерно такими же параметрами.
___

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

вторник, 4 августа 2009 г.

MySQL и Delphi. Express-метод

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

В Delphi на работе у меня не установлено ничего для работы с MySQL. Решила попробовать настроить доступ к данным MySQL через ODBC. Получилось довольно быстро и нехлопотно, поэтому решила написать инструкцию и выложить в блоге — вдруг кому пригодится.

1. Скачиваем драйвер ODBC с официального сайта MySQL. На данный момент ссылка на страницу продукта такая: http://dev.mysql.com/downloads/connector/odbc/5.1.html. Пришлось там у них зарегистрироваться, чтобы получить возможность скачать, что надо. Но это дело двух минут — не страшно.

2. Параллельно я создала в MySQL базу, с которой планировала работать (полей там больше, с расчетом на дальнейшую работу)


3. Устанавливаем драйвер
MySQL-ODBC install

4. Идем в Настройки / Панель управления / Администрирование / Источники данных (ODBC) / Системный DSN / Добавить

Настраиваем там подключение.
Congfig MySQL ODBC
Нажимаем на "Test" и Убеждаемся, что соединение проходит.
test config mysql odbc

5. В Delphi.
5.1. Кинуть на форму ADOConnection. Двойной клик на нем, настроить все, что надо. На первой закладке ("Поставщик данных") выбрать "Microsoft OLE DB Provider for ODBC Drivers". На второй закладке у меня получилось следующее (я подключалась к локалхосту :) ):
ADOConnection Config
Опять же, после настройки можно проверить подключение и убедиться, что все работает.
5.2. А дальше все элементарно, просто работать с базой через ADO. Например, поместить на форму ADOTable, назначив ей в свойстве Connection созданный ранее ADOConnection. И так далее.

База, состоящая из 35217 записей (в основном Москва, REGION=1), получилась 35 метров, в архиве 14. Это из-за того, что описания фирм там пространные, типа "мы, такие-то такие-то, ведущий производитель на мировом рынке"... Многовато по размеру, да. Но придумаю, куда выложить, и выложу.

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

понедельник, 3 августа 2009 г.

Пост о том, что лучше сто раз проверить

Подумала я давеча: "Раз уж парсер компаний с хедхантера написан, то почему бы его не запустить?" Взяла и запустила. Проработал он меньше суток, остановился. "Ну, - думаю, - чудненько! Так быстро отпахал!". Но когда посмотрела результаты, записанные в базу, — поняла, что что-то не то, так как данных было гораздо меньше, чем официально "обещали" на сайте. Залезла на хедхантер, полистала страницы вручную. Сначала все шло благополучно и ничто не предвещало беды. Но когда полезла вглубь, то заметила, что списки предприятий не отображаются. Например,
http://hh.ru/employersList.do?&areaId=113&companyWithoutVacancy=on&page=100



На последних страницах списки компаний опять нормально отображаются, как ни в чем не бывало (чтобы убедиться, достаточно поставить page=777). Соответственно, нужно внести корректировки и в алгоритм. Например, просматривать не весь список, а список по отдельным буквам алфавита или по регионам. Но я уже потеряла интерес к этому занятию и переписывать не буду. Единственно, что запустила парсер на компании Москвы, пусть парсит (добавляю в базу, в которой уже было 9 с лишним тысяч предприятий, спарсенных из "общего" списка). Парсинг закончится — выложу базу :)

Что касается таких ситуаций. Если честно, у меня уже второй раз так: начинаю парсить, а потом оказывается, что не учтены некоторые обстоятельства. Первый раз это было из-за того, что заказчик сам неточно составил задание и не учел один тип вывода данных, а я не перепроверила (база была большая, я посмотрела наугад страниц 20 - все удовлетворяли шаблону, а потом выяснилось, что есть группа исключений). Я к тому, что это не страшно, конечно, но на предварительном этапе лучше всегда проводить более тщательные исследования. И всегда быть готовым к "неожиданностям".

Удачных разработок!
___

А еще — попробую добавить свой блог в рейтинг блогов. :)
___

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

пятница, 31 июля 2009 г.

Особенности составления регулярных выражений для парсинга страниц некоторых сайтов

Привет всем!

Во-первых, поздравляю всех сисадминов с профессиональным праздником! ;)

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

На что еще следует обратить внимание при парсинге web-страниц. Регулярное выражение, составленное для содержимого страницы, полученного одним способом (например, через DownloadFile), может не подойти для поиска в содержимом страницы, полученном другим способом (например, через idHTTP.Get). Так что при составлении регулярного выражения используйте в качестве основы html-код, полученный тем же способом, каким вы планируете получать его в своем приложении. Или составляйте универсальное регулярное выражение. Это касается в большей степени разработки каких-нибудь многофункциональных универсальных приложений, в которых с ссылками приходится работать по несколько раз, хранить их в базе и т.д..

Приведу в качестве примера свое приложение для парсинга и автоматизации постинга сообщений на форумы. При скачивании страницы через idHTTP.Get в ссылках будет дописываться сессия (соответственно, с учетом этого все ссылки приходилось обрабатыввать, чтобы в базу сохранять "чистые" ссылки, удаляя "s=..."). Если бы мне потом взбрело в голову пытаться достать какие-нибудь данные из кода, скачанного с помощью DownloadFile, то ни в одной ссылке сессии не было бы и составленная для первого случая регулярка не подошла бы. А вот такая подошла бы в обоих случаях:

<a href="(showthread.php\?(?:|s=.*?&amp;)t=[0-9] )" id="thread_title_[0-9] ".*?>(.*?)</a>


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

/---

Чтобы научиться писать программы на персональном компьютере, нужно сначала этот самый персональный компьютер купить.

---/

среда, 29 июля 2009 г.

Пост для урегулирования потока писем

Здравствуйте, читатели блога! Извиняюсь, конечно, но таки отважилась это сказать вслух :) В последнее время мне приходит все больше и больше писем с просьбой о помощи в тех или иных вопросах программирования. По возможности я даже стараюсь отвечать. Но их становится МНОГО, реально много. И если я буду и дальше продолжать на все отвечать, то все мое свободное время будет уходить на разгребание почты. Так что прошу: по возможности, поищите сначала решение хотя бы в интернете, ведь там так много уже всего написано и о многом рассказано!

P.S. И уж если не нашли, написали письмо, и я вам ответила, и этот ответ вам помог — сделайте в свою очередь приятно и мне ;)))) Например, поставьте + на фрилансе, если у вас есть там аккаунт... %D

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

суббота, 25 июля 2009 г.

Парсинг для начинающих. Практическая работа. Часть II

В предыдущей части мы рассмотрели алгоритм обхода раздела сайта для сбора информации. Представленная ниже (вторая и последняя) часть инструкции будет напичкана кодом.

Классы для работы я описала еще в первой части. Небольшие пояснения относительно функций для класса списка компаний (TCompanies).

function IsNewCompany (sLink : string) : boolean;

- проверяет, есть ли уже в списке такая фирма. В качестве параметра — ссылка на информацию о фирме. Листинг:
function TCompanies.IsNewCompany(sLink: string): boolean;
var
i : integer;
tempC : TCompany;
begin
Result := true;
for i := 0 to Count-1 do
begin
tempC := TCompany(Items[i]);
if (tempC.CInfoURL = sLink) then
begin
Result := false;
break;
end;
end;
end;


procedure LoadFromQuery ( Query : TpFIBDataSet );

Листинг:
procedure TCompanies.LoadFromQuery(Query: TpFIBDataSet);
var
tempC : TCompany;
v : Variant;
begin
Query.First;
while (not Query.Eof) do
begin
tempC := TCompany.Create;
v := Query.FieldValues['N'];
tempC.N := Integer(v);
tempC.CName := Query.FieldValues['COMP_NAME'];
tempC.CInfoURL := Query.FieldValues['INFO_URL'];
Add(tempC);
Query.Next;
end;
end;

- для удобства, загрузка из выборки базы. Допустим, парсим всю информацию не за раз, а за несколько (объемы большие, как никак). После первого запуска напарсенные компании складываем в базу. Через день запускаем парсер снова. И, чтобы не парсить информацию о некоторых предприятиях повторно (выдача в список может меняться, простого указания страницы, с которой начинать, - недостаточно), мы загружаем в список уже присутствующие в базе компании. А потом после парсинга перед добавлением проверяем, есть ли компания в списке по InfoURL (с помощью IsNewCompany). После проведения парсинга записываем в базу только новые компании (их можно выделить по отсутствию у них внутреннего идентификатора). Я все записывала в фаербед, но вы с таким же успехом можете все писать в MySQL.

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

В программе будет две основных процедуры. Первая — обход списка предприятий по страницам и сбор названий предприятий и ссылок на более подробную информацию о них, вторая — сбор информации о каждом предприятии.

Принтскрин примерной формы, которая уменя получилась, я приводила в предыдущей части инструкции. Поясню только, что на форме у меня:

cxSpinEdit1 — с какой страницы начинать (нумерация с нуля)
cxSpinEdit3 — по какую страницу
cxSpinEdit2 — таймаут между запросами, в секундах
mLog - TMemo просто для отладки

procedure TMainF.StartParsingHH;
var
i, j, k : integer;
S,
ReqStr,
StrLink : string;
NeedNextPage : boolean;
v : OleVariant;
Doc : IHTMLDocument2;
DocAll,
DocA : IHTMLElementCollection;
DocElement : IHtmlElement;
idHttp1 : TidHttp;
tmpComp : TCompany;
begin
StatusBar.Panels[0].Text := 'Идет парсинг';
for i := 0 to 10 do
begin
Application.ProcessMessages;
Sleep(10);
end;

// цикл по страницам
i := cxSpinEdit1.Value;
NewVacCnt := 0;
NeedNextPage := true;
vStop := false;
while (NeedNextPage) and (not vStop) do
begin
// загрузка страницы
ReqStr := StringReplace(HHCompanyPageURL,'[PageNum]',IntToStr(i),[rfReplaceAll,rfIgnoreCase]);
sLog('log.txt','[HH] ReqStr = '+ReqStr);
NeedNextPage := true;
try
idHttp1 := TidHttp.Create(nil);
idHttp1.Request.UserAgent := 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.0.3) Gecko/2008092417 AdCentriaIM/1.7 Firefox/3.0.3';
idHttp1.Request.AcceptLanguage := 'ru';
idHttp1.Request.Referer := 'http://hh.ru';
idHttp1.ConnectTimeout := 5000;
idHttp1.ReadTimeout := 5000;
idHttp1.Response.KeepAlive := true;
idHttp1.HandleRedirects := true;
try
// опущу проверку EIDHttpProtocolException, хотя по правилам надо сделать
S := idHttp1.Get(ReqStr);
except on e : Exception do
begin
sLog('log.txt','[HH] Не удалось скачать страницу '+ReqStr);
sLog('log.txt','Error: '+e.Message);
vStop := true;
end;
end;
S := UTF8ToAnsi(S); // опытным путем мы заметили, что надо поменять кодировку
finally
idHttp1.Free;
end;
// будем работать с DOM
Doc := coHTMLDocument.Create as IHTMLDocument2;
if Doc = nil then
begin
ShowMessage('Ошибка создания IHTMLDocument2');
exit;
end;
v := VarArrayCreate([0,0],VarVariant);
v[0] := S;
Doc.write(PSafeArray(TVarData(v).VArray));

DocAll := Doc.all;
DocA := DocAll.Tags('A') as IHTMLElementCollection;
for k := 0 to DocA.length-1 do
begin
DocElement := DocA.Item(k, 0) as IHtmlElement;
if Pos('/employer/',LowerCase(DocElement.getAttribute('href',0))) > 0 then
begin
// обрабатываем каждую ссылку, убирая about:blank (я уже писала о такой особенности ссылок в IHTMLDocument2, созданном на основе html-кода)
StrLink := 'http://hh.ru'+StringReplace(LowerCase(DocElement.getAttribute('href',0)),'about:blank','',[rfReplaceAll,rfIgnoreCase]);
mLog.Lines.Add(StrLink);
// обрабатываем таблицу выводов результатов
if Companies.IsNewCompany(StrLink) then
begin
tmpComp := TCompany.Create;
tmpComp.CInfoURL := StrLink;
tmpComp.CName := DocElement.innerText;
ExtractHHCompanyInfo(StrLink, tmpComp);
Companies.Add(tmpComp);

if vStop then break;

for j := 0 to cxSpinEdit2.Value*100 do
begin
Sleep(10);
Application.ProcessMessages;
end;
end;
end;
end;
// останавливаем, если i >= cxSpinEdit3.Value
inc(i);
if (i > cxSpinEdit3.Value) and (chbTO.Checked) then
vStop := true;
end;
end;


Процедура сбора информации следующая. На входе - URL страницы с подробной информацией и объект класса TCompany, куда надо записать полученную информацию. Все уже поняли, надеюсь, что я описываю процесс парсинга так, как я привыкла его производить :) Может, кто-то привык по-другому.

procedure TMainF.ExtractHHCompanyInfo(InfoLink: string; vComp : TCompany);
var
S : string;
Doc : IHTMLDocument2;
v : OleVariant;
DocAll,
DocDIV : IHTMLElementCollection;
DocElement : IHTMLElement;
i : integer;
idHttp1 : TidHttp;
isError : boolean;
begin
// извлечение информации о компании
try
isError := false;
idHttp1 := TidHttp.Create(nil);
idHttp1.Request.UserAgent := 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.0.3) Gecko/2008092417 AdCentriaIM/1.7 Firefox/3.0.3';
idHttp1.Request.Referer := 'http://hh.ru';
idHttp1.Request.AcceptLanguage := 'ru';
idHttp1.Response.KeepAlive := true;
idHttp1.HandleRedirects := true;
try
try
// опущу проверку EIDHttpProtocolException, хотя по правилам надо сделать
S := idHttp1.Get(InfoLink);
except
sLog('log.txt','Не удалось загрузить страницу с адресом'+#13#10+InfoLink);
isError := true;
end;
S := UTF8ToAnsi(S);
finally
idHttp1.Free;
end;
if isError then exit;

Doc := coHTMLDocument.Create as IHTMLDocument2;
if Doc = nil then
begin
ShowMessage('Ошибка создания IHTMLDocument2');
exit;
end;
v := VarArrayCreate([0,0],VarVariant);
v[0] := S;
Doc.write(PSafeArray(TVarData(v).VArray));

DocAll := Doc.all;
DocDIV := DocAll.Tags('DIV') as IHTMLElementCollection;
for i := 0 to DocDIV.length-1 do
begin
DocElement := DocDIV.Item(i, 0) as IHtmlElement;

if (LowerCase(trim(DocElement.className)) = LowerCase('b-employerpage-url')) then
vComp.CURL := DocElement.innerText;

if (LowerCase(trim(DocElement.className)) = LowerCase('g-user-content b-employerpage-desc')) then
begin
vComp.CInfo := DocElement.innerText;
// почистим результат от всяких "левых" символов
vComp.CInfo := StringReplace(vComp.CInfo,'&ndash;','-',[rfReplaceAll,rfIgnoreCase]);
vComp.CInfo := StringReplace(vComp.CInfo,'&laquo;','"',[rfReplaceAll,rfIgnoreCase]);
vComp.CInfo := StringReplace(vComp.CInfo,'&raquo;','"',[rfReplaceAll,rfIgnoreCase]);
vComp.CInfo := StringReplace(vComp.CInfo,'&nbsp;',' ',[rfReplaceAll,rfIgnoreCase]);
vComp.CInfo := StringReplace(vComp.CInfo,'&bull;','-',[rfReplaceAll,rfIgnoreCase]);
end;
end;

except
end;

if not vStop then
for i := 0 to cxSpinEdit2.Value-1 do
begin
Application.ProcessMessages;
Sleep(1000);
end;
end;


Небольшие пояснения. Я здесь использовала DOM. Это быстро и удобно. Тем более, что парсить такую информацию регулярками - совсем неудобно. Ссылку на сайт компании можно легко получить, взяв innerText элемента div класса "b-employerpage-url" (он такой один на всю страницу всем нам на благо). А подробную информацию можно получить, взяв innerText элемента с классом "g-user-content b-employerpage-desc". Чистим результат от символов, встречающихся в результате. Например, заменяем '&ndash;' на '-', '&laquo;' и '&raquo;' на '"' и т.д.. Необходимость этого выявляется уже чисто на практике: пробуем парсить страничку, смотрим результаты и делаем выводы.

Вот, в принципе, и все.

Что касается меня — я вернулась из отпуска, порядком отдохнувшей, загорелой и соскучившейся по интернету :) Скоро вольюсь в привычный ритм работы и придумаю что-нибудь еще ;D

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

понедельник, 6 июля 2009 г.

Парсинг для начинающих. Практическая работа. Часть I

До начала второй части отпуска осталось 4 дня. Использую это время с пользой — напишу статью на блог. :)

Итак, долой теорию, переходим к практике парсинга. Соберем с какого-нибудь сайта структурированную информацию. Исходя из того, что для примера структура должна быть простой, я выбрала обредший небывалую популярность во время кризиса сайт по поиску работы hh.ru. Еще мой выбор обоснован тем, что с этим сайтом я довольно-таки давно и тесно "работаю" :D. Парсить в этот раз мы будем не вакансии, а информацию о предприятиях.

По ссылке http://hh.ru/employersList.do можно увидеть список компаний, имеющих на данный момент открытые вакансии. Отметив чекбокс "Показать компании, у которых нет открытых вакансий" и нажав на кнопку "Найти" получаем список всех предприятий (на момент написания статьи — 76 101). База немаленькая. Если парсить в один поток и делать перерыв между загрузками 2 секунды, то процесс займет около двух суток.

Рассмотрим структуру выдачи. Get-запрос будет следующим:

http://hh.ru/employersList.do?letter=&employerName=&areaId=113&companyWithoutVacancy=on&page=[PageNum]


[PageNum] — номер страницы. Нумерация начинается с нуля.

Необходимая нам информация о предприятии:
- название;
- ссылка на сайт;
- описание предприятия;
- ссылка на источник информации.

Последний пункт — URL страницы, с которой берется информация (ссылка вида http://hh.ru/employer/[ID]). Мы будем парсить постранично список с предприятиями, потом заходить по каждой ссылке и брать подробную информацию. "Почему такой способ, а не перебор всех ID по порядку в ссылке http://hh.ru/employer/[ID]?" — спросите вы. Отвечу: "Да потому, что это менее затратно". Идентификаторов гораздо больше, чем "действующих" ссылок (например, наберите http://hh.ru/employer/4 — увидите своими глазами, что предприятия с таким ID не существует).

Итак, алгоритм:
1) скачиваем первую страницу списка фирм;
2) находим все ссылки на предприятия;
3) если ссылки есть - заходим по каждой ссылке и получаем детальную информацию, записываем; если ссылок нет — заканчиваем работу;
4) загружаем следующую по счету страницу;
5) повторяем пункты 2 — 4.

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

Как находить ссылки на предприятия? Способов тоже несколько:
1) в содержимом страницы по регулярному выражению;
2) просмотреть весь массив ссылок, найденный через DOM, и отобрать ссылки, начинающиеся с "/employer/".

По каждой найденной ссылке зайти и найти информацию о компании + адрес сайта. Полученные результаты сохранить. Тут уж кто как привык. Можно сразу в базу. Мне, допустим, удобней и привычней записать сначала все в List, а потом уже с ним делать все, что угодно.

Для компаний я создала класс TCompany:

type
TCompany = class
N : integer;
CName,
CURL,
CInfoURL,
CInfo : string;
end;

TCompanies = class (TList)
function IsNewCompany (sLink : string) : boolean;
procedure LoadFromQuery ( Query : TpFIBDataSet );
destructor Destroy; override;
end;


TCompanies — класс для работы со списком предприятий.

Интерфейс получился такой:
parsing hh.ru

Продолжение следует.

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

вторник, 30 июня 2009 г.

TWebBrowser, OleObject и его свойства

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

Итак, свойства OleObject-а TWebBrowser-а.

OleObject — удобный инструмент для работы с "внутренними объектами" компонента TWebBrowser.

Все, доступное через свойство OleObject, является также доступным через свойство Document. В целом получение данных через Document является более сложным, поскольку оно связано с использованием других классов/интерфейсов, но этот путь лучше с точки зрения удобства выявления ошибок.

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

Итак, атрибуты объекта, о которых пойдет речь ниже:

WebBrowser.OleObject.Document
WebBrowser.OleObject.Document.All
WebBrowser.OleObject.Document.bgColor
WebBrowser.OleObject.Document.Body.Style.overflowX
WebBrowser.OleObject.Document.Body.Style.overflowY
WebBrowser.OleObject.Document.Body.Style.zoom
WebBrowser.OleObject.Document.cookie
WebBrowser.OleObject.Document.documentElement.innerHTML
WebBrowser.OleObject.Document.documentElement.innerText
WebBrowser.OleObject.Document.FileSize
WebBrowser.OleObject.Document.Forms
WebBrowser.OleObject.Document.Frames
WebBrowser.OleObject.Document.Images
WebBrowser.OleObject.Document.LastModified
WebBrowser.OleObject.Document.Links
WebBrowser.OleObject.Document.Location.Protocol
WebBrowser.OleObject.Document.ParentWindow
WebBrowser.OleObject.Document.ParentWindow.ScrollBy(iX: Integer; iY: Integer)
WebBrowser.OleObject.Document.selection
WebBrowser.OleObject.Document.Title
WebBrowser.OleObject.Document.URL


WebBrowser.OleObject.Document

Обеспечивает доступ к отображаемому документу.

Эквивалентная запись:
WebBrowser.Document as IHTMLDocument2

Примечание:
* Если документ не был загружен, то WebBrowser.Document as IHTMLDocument2 будет равно nil. Для того, чтобы предотвратить возникновение исключения, перед использованием нужно проверить значение, например:
var
document: IHTMLDocument2;
begin
document := WebBrowser.Document as IHTMLDocument2;
if Assigned(document) then
...
...

* В дальнейшем в этой статье для краткости такая проверка будет опускаться, но при разработке приложений надо о ней помнить.


WebBrowser.OleObject.Document.All

Массив всех элементов/объектов документа. Он включает в себя изображения, ссылки, текст и т.д.
Таблица свойств массива:
















.LengthВозвращает количество элементов в массиве.
.Item(0)Возвращает первый элемент документа.
.Item(n).InnerTextЧтение/запись текста между начальными и конечным тегами конкретного элемента.
.Item(n).ScrollIntoView (bAlignToTop: Boolean)Выполняет прокрутку документа, содержащего n-ый элемент, пока верхний или нижний край элемента не окажется выровненным с окном документа. bAlignToTop = true выравнивает элемент с верхним краем окна, а bAlignToTop = false — с нижним краем окна.

Листинг части кода, демонстрирующий работу с элементами документа (в частности — получение доступа к первому элементу):

var
document: IHTMLDocument2;
docAll: IHTMLElementCollection;
firstElement: IHTMLElement;
begin
document := WebBrowser.Document as IHTMLDocument2;
if Assigned(document) then
begin
docAll := document.all;
firstElement := docAll.Item(0,'');




WebBrowser.OleObject.Document.bgColor

Устанавливает (или возвращает) цвет фона документа. Например, чтобы установить белый цвет фона, достаточно написать:
WebBrowse.OleObject.Document.bgColor := '#FFFFFF';



WebBrowser.OleObject.Document.Body.Style.overflowX

Чтение/установка строкового значения, определяющего свойство горизонтальной прокрутки. Значения параметра могут быть следующими:


















visibleЗначение параметра по умолчанию. Без скроллбара. Отображаемый документ обрезается до видимой области.
scrollПрокрутка всегда видна. Независимо от того, требуется она или нет.
hiddenПрокрутки нет. Содержание вне зоны видимости скрыто.
autoСодержимое обрезается и прокрутка появляется в том случае, если это необходимо.




WebBrowser.OleObject.Document.Body.Style.overflowY

Параметр аналогичен WebBrowser.OleObject.Document.Body.Style.overflowX, только относится к вертикальной прокрутке.


WebBrowser.OleObject.Document.Body.Style.zoom

Устанавливает или возвращает коэффициент масштабирования, используемый для отображения документа. По умолчанию этот значение этого паараметра равно единице. Чтобы документ отобразися вполовину "нормального", необходимо установить значение 0,5. Чтобы масштаб отображаемого документа был в 2 раза больше, нужно, соответственно, установиль данный параметр равным двум.


WebBrowser.OleObject.Document.cookie

Данное свойство возвращает строку, содержащую все кукисы браузера.

Эквивалент:
var
document: IHTMLDocument2;
cookies: String;
begin
document := WebBrowser.Document as IHTMLDocument2;
if Assigned(document) then
cookies := document.cookie;


Кукисы представлены в строке в виде:
name = value


Примечание:

* Если имеется больше одной пары "имя=значение", то эти пары разделяются между собой точкой с запятой (';').
* Некоторые символы могут быть "экранированы".


WebBrowser.OleObject.Document.documentElement.innerHTML

Для документов HTML возвращается все содержимое, включая HTML-теги.


WebBrowser.OleObject.Document.documentElement.innerText

Returns the text content of the document - without any (HTML) formatting. Возвращает текстовое содержанимое документа - без какого-либо (HTML) форматирования.


WebBrowser.OleObject.Document.FileSize

Возвращает размер HTML-документа в байтах.

Эквивалентно конструкции:
(WebBrowser.Document as IHTMLDocument2).FileSize


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


WebBrowser.OleObject.Document.Forms

Возвращает коллекцию форм на странице.









.LengthВозвращает количество форм в документе.
.Item(0)Возвращает первую форму.


Эквивалентный код:
var
htmlDoc: IHTMLDocument2;
allForms: IHTMLElementCollection;
firstForm: IHTMLFormElement;
begin
htmlDoc := WebBrowser.Document as IHTMLDocument2;
allForms := htmlDoc.Forms;
firstForm := allForms.Item(0,'') as IHTMLFormElement;


См. также: как работать с элементами формы (Eng).


WebBrowser.OleObject.Document.Frames

Массив фреймов в документа.

















.LengthВозвращает количество фреймов в документе.
.Item(0)Возвращает первый фрейм.
.Item(0).DocumentВозвращает документ, представленный в первом фрейме.
.Item(0).Document.URLВозвращает адрес первого фрейма документа.


Эквивалентная запись:
(WebBrowser.Document as IHTMLDocument2).Frames


Пример получения информации о фрейме (через IHTMLWindow2 или IHTMLDocument2)
var
document: IHTMLDocument2;
ole_index: OleVariant;
doc_all: IHTMLElementCollection;
frame_dispatch: IDispatch;
frame_win: IHTMLWindow2;
frame_doc: IHTMLDocument2;
begin
document := WebBrowser.Document as IHTMLDocument2;
ole_index := 0;
frame_dispatch := document.Frames.Item(ole_index);
if frame_dispatch <> nil then
begin
frame_win := frame_dispatch as IHTMLWindow2;
frame_doc := frame_win.document;
...
...



WebBrowser.OleObject.Document.Images

Массив изображений, содержащихся в документе.













.LengthВозвращает количество изображений в документе.
.Item(0)Возвращает первое изображение.
.Item(0).SrcURL первого изображения.



WebBrowser.OleObject.Document.LastModified

Возвращает дату последней модификации (в виде строки). Официально — в формате "MM/DD/YY hh:mm:ss", но реально может быть возвращена в формате в соответствии с местными региональными настройками.

Эквивалентный код:
var
htmlDoc: IHTMLDocument2;
dateString: String;
begin
htmlDoc := WebBrowser.Document as IHTMLDocument2;
if Assigned(htmlDoc) then
dateString := html_doc.LastModified;



WebBrowser.OleObject.Document.Links

Массив всех ссылок.

















.LengthВозвращает количество ссылок в документе.
.Item(0)Возвращает первую ссылку.
.Item(0).hrefВозвращает адрес первой ссылки.
.Item(0).Document.TagNameВозвращает тэг первого элемента. Для ссылок это всегда "A".


Эквивалентная запись:
var
htmlDoc: IHTMLDocument2;
allLinks: IHTMLElementCollection;
firstLink: IHTMLElement;
url: String;
begin
htmlDoc := WebBrowser.Document as IHTMLDocument2;
allLinks := htmlDoc.Links;
firstLink := allLinks.Item(0,'') as IHTMLElement;
url := firstLink.toString;



WebBrowser.OleObject.Document.Location.Protocol

Возвращает строку, характеризующую протокол URL-а. Протокол может быть одним из следующих:













































Protocol Value
file: Локальные или сетевые файлы.
ftp: FTP
gopher: Gopher session.
http: HTTP
https: HTTPS
javascript: JavaScript-код.
mailto: Client e-mail.
news: Newsgroup.
res: Resource file.
telnet: Telnet terminal login.




WebBrowser.OleObject.Document.ParentWindow

Возвращает (только чтение) ссылку на контейнер окна.


WebBrowser.OleObject.Document.ParentWindow.ScrollBy(iX: Integer; iY: Integer)

Прокрутка окна по горизонтали на 'iX' пикселей: отрицательное значение параметра приведет к прокрутке влево, положительное — вправо. Прокрутка на 'iY' пикселей по вертикали: отрицательное значение — прокрутка вверх, положительное — вниз.

Эквивалентная запись:
var
document: IHTMLDocument2;
begin
document := webBrowser.Document as IHTMLDocument2;
if Assigned(document) then
document.parentWindow.scrollBy(iX,iY);


Примечание:
1. Окно не будет прокручиваться вверх/вниз, если оно уже достигло своей обычной верхней/нижней границы. Точно так же не будет происходить прокрутки влево/вправо, если левая/правая граница достигнута. Так, например, когда документ впервые загружен, попытка запустить вышеописанную процедуру с параметрами (-1, -1) ни к чему не приведет.
2. Тут имеются особенности относительно документов, содержащих фреймы. Прокрутка будет осуществляться только для документа "верхнего уровня", а не для фреймов, из которых он состоит. Следующая процедура показывает, как делать прокрутку для любого документа или фрейма, в том числе встроенных фреймов:
procedure ScrollBrowserWindowBy(const window: IHTMLWindow2; iX:Integer; iY:Integer);
var
index: Integer;
oleIndex: OleVariant;
frameDispatch: IDispatch;
childWindow: IHTMLWindow2;
document: IHTMLDocument2;
begin
if Assigned(window) then
try
window.scrollBy(iX,iY);
// If there are any frames then try scrolling them.
document := window.Document as IHTMLDocument2;
if Assigned(document) then
for index := 1 to document.Frames.Length do
begin
oleIndex := index-1;
frameDispatch := document.Frames.Item(oleIndex);
if Assigned(frameDispatch) then
begin
childWindow := frameDispatch as IHTMLWindow2;
ScrollBrowserWindowBy(childWindow,iX,iY);
end;
end;
except
on E: Exception do begin end;
end;
end;


Еще один пример вызова:
var
document: IHTMLDocument2;
begin
document := webBrowser.Document as IHTMLDocument2;
if Assigned(document) then
ScrollBrowserWindowBy(document.parentWindow,5,10);

3. При попытке вызова scrollBy для фрейма со страницей с другого сайта, это вызовет исключение "Access Denied", поэтому в приведенном выше примере надо использовать "try .. except".


WebBrowser.OleObject.Document.selection

Обеспечивает доступ к выбранной части документа.

Например, для доступа к выделенному в данный момент тексту:
var
document: IHTMLDocument2;
selectionObj: IHTMLSelectionObject;
selectionRange: IHtmlTxtRange;
selectedText: String;
begin
document := WebBrowser.Document as IHTMLDocument2;
selectionObj := document.selection;
selectionRange := selectionObj.CreateRange as IHtmlTxtRange;
selectedText := selectionRange.text;
...


Примечание: приведенный выше пример должен быть изменен для документов с фреймами.


WebBrowser.OleObject.Document.Title

Название текущего документа. Оно будет пустым, если в HTML-документе не указан title.


WebBrowser.OleObject.Document.URL

Адрес текущего документа. Это то же самое, что свойство LocationURL.

___

Переводила наспех. Возможно, где-то слажала или пропустила что-нибудь. Если заметите что-нибудь — напишите, исправлю.


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

воскресенье, 28 июня 2009 г.

Cookies в TWebBrowser

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

ShowMessage(WebBrowser1.OleObject.Document.cookie);

cookies in TWebBrowser

Результатом WebBrowser1.OleObject.Document.cookie является строка, в которой пары кукисов "имя=значение" разделены ";" (точкой с запятой). Это позволяет легко распарсить строку и получить доступ к значениям кукисов по имени.

Update: ответ на вопросы комментаторов — Как удалить куки из TWebBrowser.
___

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

вторник, 26 мая 2009 г.

Пример авторизации на сайте с помощью idHTTP.Post

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

Сегодня расскажу, как использовать idHTTP.Post для авторизации на сайте. Я возьму для примера сайт LiveJournal.com.

Немного теории для начинающих. Итак, вызов метода Post компонента idHTTP отличается от вызова Get-а только тем, что помимо URL-а необходимо передать параметры. Параметры можно передавать в виде StringList-а, или каких-нибудь Stream-ов, или чего-нибудь еще подходящего.)

Пример Post-процедуры (параметры передаются в виде StringList-а):

procedure TForm1.Button1Click(Sender: TObject);
var
LoginInfo: TStringList;
Response: TStringStream;
begin
try
LoginInfo := TStringList.Create;
Response := TStringStream.Create('');
LoginInfo.Add('username=MyName');
LoginInfo.Add('password=MyPass');
IdHTTP1.Post('http://mywebsite.xxx/login.php',LoginInfo,Response);
Showmessage(Response.DataString);
finally
begin
Response.Free;
LoginInfo.Free;
end;
end;
end;


Пример Post-функции (параметры передаются в виде IdMultiPartFormDataStream-а):

uses IdMultipartFormData;
{ .... }

procedure TForm1.Button1Click(Sender: TObject);
var
data: TIdMultiPartFormDataStream;
begin
data := TIdMultiPartFormDataStream.Create;
try
// добавляем нужные параметры
data.AddFormField('param1', 'value1');
data.AddFormField('param2', 'value2');
// для примера выводим в мемо все, что вернулось
Memo1.Lines.Text := IdHTTP1.Post('http://localhost/script.php', data);
finally
data.Free;
end;
end;


Сейчас попробуем применить полученные знания. Идем на LiveJournal.com, включаем сниффер, логинимся на сайте и смотрим, какие параметры надо передавать ('mode=login', 'user=логин', 'password=пароль'). Авторизация не произойдет, если на стороне клиента не будут сохранены кукисы. Для сохранения кукисов среди компонентов Indy существует TidCookieManager. IdCookieManager подключается к idHTTP через свойство CookieManager.
idHttp.CookieManager := IdCookieManager;

В этом случае при запросах в заголовок добавляются кукисы, автоматически сохраненные в IdCookieManager. IdCookieManager можно найти на закладке Indy Misc или создать динамически.
idCookieManager

Поместим на форму 2 TEdit-а, TMemo и кнопку, на которую повесим следующий работающий код авторизации:

procedure TForm1.Button1Click(Sender: TObject);
var
Http : TidHttp;
CM : TidCookieManager;
Data : TStringList;
StrPage, UserID, UserName : String;
i : integer;
begin
try
Http := TIdHTTP.Create(Self);
Data := TStringList.Create;
CM := TidCookieManager.Create(Http);
Http.AllowCookies := true;
Http.CookieManager := CM;
Http.HandleRedirects := true;

Http.Request.Host:='livejournal.com';
Http.Request.UserAgent:='Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10';
Http.Request.Accept:='text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
Http.Request.AcceptLanguage:='ru,en-us;q=0.7,en;q=0.3';
Http.Request.AcceptCharSet:='windows-1251,utf-8;q=0.7,*;q=0.7';
Http.Request.Referer:='http://www.livejournal.com/';

Data.Add('mode=login');
Data.Add('user=' + Edit1.Text);
Data.Add('password=' + Edit2.Text);
StrPage := Http.Post('http://www.livejournal.com/login.bml?ret=1', Data);
finally
Data.Free;
CM.Free;
Http.Free;
end;

if Pos('<input class="logoutlj_hidden" id="user" name="user" type="hidden" value="'+Edit1.Text,StrPage) <> 0 then
ShowMessage('Авторизация прошла успешно')
else
ShowMessage('Авторизация провалилась');

Memo1.Lines.Text := StrPage;
end;


Возвращенные заголовки (после ответа сервера) можно посмотреть так:
idHttp.Response.RawHeaders.GetText;


Сохраненные в CookieManager-е кукисы можно посмотреть так:
for i := 0 to Http.CookieManager.CookieCollection.Count - 1 do
StrPage := StrPage + CM.CookieCollection.Items[i].CookieText + #13#10;


Вот что записал туда LiveJourmal.com:
Cookies LJ
Да, ЖЖ, мы тоже love you a lot :)
___

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

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

вторник, 12 мая 2009 г.

Для начинающих работать с компонентом idHTTP в Delphi

Найти компонент idHTTP можно на вкладке Indy Clients.

idHTTP

После того, как поместите его на форму, посмотрите его параметры и свойства в Object Inspector-е. Вы увидите, что работать с протоколом HTTP с помощью этого компонента достаточно просто. Для "составления" правильных заголовков запросов к серверу будем плотно работать с TIdHTTP.Request. Многие поля вам уже знакомы (я упоминала о них в вводной статье о протоколе HTTP и HTTP-заголовках). Там же есть возможность привязки к объекту idHTTP компонента для удобной работы с кукисами (компонент idCookieManager) и настройка работы через прокси.

Рассмотрим способы отправки запросов Get и Post. С такими названиями существуют и функции, и процедуры. Я уже упоминала про Get в вводной статье об Indy. Непосредственно перед отправкой запроса следует настроить свойства вышеупомянутого IdHTTP.Request-а, если есть необходимость. Например, UserAgent в idHTTP по умолчанию Mozilla/3.0 (compatible; Indy Library), и, чтобы не палиться, следует его заменить на что-нибудь более безобидное :) А если вы создаете экземпляры компонента idHTTP динамически для парсинга страниц какого-нибудь большого сайта, то юзерагента можно вообще брать рандомом из заранее подготовленного списка.

Пример использования процедуры idHTTP.Get без дополнительных настроек:

var
mStream: TMemoryStream;

mStream := TMemoryStream.Create;
try
idHttp := TIdHTTP.Create(nil);
{ тут следует "настроить" параметры idHTTP }
{ ... }
try
idHttp.Get(URL, mStream);
finally
idHttp.Free;
end;
finally
mStream.Free;
end;


Или с помощью функции получить содержимое страницы в строковую переменную:

Str := idHttp.Get(URL);


С POST-запросом дела обстоят аналогично, с одним отличием: при отправке POST-запроса передаются еще и параметры. Думаю, что более подробно рассмотрю POST-запрос в следующем посте, с примером авторизации на сайте (чтобы не пихать слишком много информации в одну запись).

Какие еще аспекты работы с idHTTP следует отметить?
После посылки запроса проверить ответ сервера можно, посмотрев содержимое свойства

idHTTP.Response.ResponseText


Стандартым ответом о том, что все прошло удачно, является HTTP/1.0 200 OK.

Бывают и другие ответы сервера (про коды ответов я упоминала в одной из предыдущих записей). Остановлю внимание только на двух наиболее часто встречающихся группах:
2хх: Успешно. Сигнал был успешно принят, понят и принят к обработке.
Зхх: Перемаршрутизация. Необходимо предпринять определенные действия, чтобы выполнить запрос.

Исключения, возникающие при работе компонентов Indy (тип EIdHTTPProtocolException), классифицируются особым образом.

Except
on E:EIdHTTPProtocolException do
ShowMessage(E.ErrorMessage);


Классификация исключений может пригодиться. У меня был случай, когда написанный парсер работал без проблем, но спустя какое-то время отказался работать. Помог анализ ответа сервера: на сайте временно поставили перенаправление (Код 302 Found: HTTP_FOUND — Запрошенный ресурс был временно перемещен на новый URI), возможность которого я в начальной версии не предусмотрела. С тех пор во всех своих парсерах я проверяю код ответа: не 302 ли случаем? (Если код ответа 301 (Moved Permanently: HTTP MOVED PERMANENTLY — Запрошенный документ был перенесен на новый URI), то информация берется без проблем, если у idHTTP свойство HandleRedirects := true, а вот с 302 это не прокатывает. Так же отдельно я обрабатываю код 404). Еще у компонента надо грамотно настроить все таймауты, чтобы, если обнаружится исключительная ситуация, запрос не "подвисал".

Пример кода для обработки исключений разного типа:
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.Clear;
try
Memo1.Lines.Add(IdHTTP1.Get(Edit1.Text));
except on e : EIDHttpProtocolException do
Begin
if e.ErrorCode = 302 then
begin
try
// получаем новый адрес - адрес перенаправления
Memo1.lines.add(idhttp1.Get(IdHTTP1.Response.Location));
except on e:Exception do
// предусматриваем, что исключение может возникнуть и тут
ShowMessage('Ошибка при получении нового адреса.'+e.Message);
end;
end
else
//http 404, 501 и так далее
ShowMessage('Ошибка другого вида, не 302:'+e.Message);
end;
on e:Exception do
ShowMessage('Ошибка: ' + e.Message);
end;
end;



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

А еще, если вам интересно программирование на Delphi, можете подписаться на RSS-трубу Delphi, о которой подробнее можно прочитать здесь.

Поделиться