LWP (сокращение от "Library for WWW in Perl") это очень
популярная группа модулей языка Perl для доступа к данным в сети
Internet. Как и большинство модулей языка Perl, на каждый модуль из
состава LWP присутствует документация с полным описанием его
интерфейса. Однако, в LWP есть множество модулей, для которых
довольно сложно найти документацию по, казалось бы, простейшим
вещам.
Введение в использование LWP, очевидно, должно занять целую
книгу, -- книгу, которая только вышла из печати, и названную: Perl & LWP.
Эта статья предлагает Вам несколько примеров, которые помогут Вам в
организации обычных вещей с LWP.
Доступ к страничкам с помощью LWP::Simple
Если Вы всего лишь хотите получить документ, находящийся по
определённому адресу(URL), то самый простой способ сделать это -
использовать функции модуля LWP::Simple.
В Perl-скрипте Вы можете сделать это, вызвав функцию
get($url). Она попытается вытянуть содержимое этого
URL. Если всё отработает нормально, то функция вернёт это
содержимое; но если произойдёт какая-нибудь ошибка, то она вернёт
undef.
my $url = 'http://freshair.npr.org/dayFA.cfm?todayDate=current';
# Всего лишь для примера: новые записи на /Fresh Air/
use LWP::Simple;
my $content = get $url;
die "Couldn't get $url" unless defined $content;
# Далее что-нибудь делаем с $content, например:
if($content =~ m/jazz/i) {
print "They're talking about jazz today on Fresh Air!\n";
} else {
print "Fresh Air is apparently jazzless today.\n";
}
Более удобный вариант функции get - это
getprint, который удобен для простмотра содаржимого
страниц через Perl. Если функция getprint может
"достать" страничку, адрес которой Вы задали, то она отправляет
содержимое в STDOUT; в противном случае, в роли
жалобной книги выступает STDERR.
Это URL простого текстового файла. В нём содержится список новых
файлов на CPAN за последние две недели. Вы легко можете сделать
shell-команду, которая, например, будет высылать Вам список новых
модулей Acme:::
% perl -MLWP::Simple -e "getprint 'http://cpan.org/RECENT'" \
| grep "/by-module/Acme" | mail -s "New Acme modules! Joy!" $USER
В модуле LWP::Simple существует ещё несколько
довольно полезных функций, включая функцию для выполнения
HEAD-запроса для URL (полезна для проверки ссылок или
получения даты последней корректировки документа) и две функции для
сохранения и зеркалирования URL в локальный файл. Смотрите документацию
по LWP::Simple для более детальной информации, или Главу 2, "Web
Основ" Perl & LWP для большего количества
примеров.
Основы классовой модели LWP
Функции LWP::Simple удобны только для простых
случаев, но эти функции не поддерживают теневых посылок(далее
cookies) и проверки подлинности(далее authorization); Они также не
позволяют устанавливать какие-либо параметры HTTP запроса; и
главное, они не позволяют считывать строки заголовка в HTTP ответе
(особенно полный текст сообщения в случае HTTP ошибки( HTTP error
message)). Для доступа ко всем этим возможностям, Вы должны
использовать весь набор классов LWP.
LWP содержит множество классов, но главные два, которые Вы должны
понимать - это LWP::UserAgent и
HTTP::Response. LWP::UserAgent это класс
для "виртуальных броузеров", кторыми Вы будете пользоваться для
выполнения запросов. HTTP::Response это класс для
ответов (или сообщений об ошибке), которые Вы получаете обратно,
после запроса.
Основное выражение при работе с LWP: $response =
$browser->get($url), или полностью:
use LWP 5.64; # Загружаем все нужные LWP классы, и удостовериваемся
# в достаточной свежести версии модуля.
my $browser = LWP::UserAgent->new;
...
# Используется ниже, тот URL, которому и будет сделан запрос:
my $url = 'http://freshair.npr.org/dayFA.cfm?todayDate=current';
my $response = $browser->get( $url );
die "Can't get $url -- ", $response->status_line
unless $response->is_success;
die "Hey, I was expecting HTML, not ", $response->content_type
unless $response->content_type eq 'text/html';
# или другой content-type, который Вам подходит
# В противном случае, производим обработку содержимого:
if($response->content =~ m/jazz/i) {
print "They're talking about jazz today on Fresh Air!\n";
} else {
print "Fresh Air is apparently jazzless today.\n";
}
В этом примере было включено два объекта, в сравнении с
предыдущим примером: $browser, который содержит объект
класса LWP::UserAgent, и объект $response,
который из класса HTTP::Response. Обычно Вам надо не
более одного объекта $browser; но каждый раз как Вы делаете запрос,
Вы получаете назад новый объект HTTP::Response, который
содержит несколько интересных методов:
Status code(Код состояния), который показывает успех либо
неудачу запроса (Вы это можете проверить так:
$response->is_success).
HTTP status line(строка состояния), которая, я думаю, будет
довольна информативна в случае ошибки (её Вы можете увидеть,
используя $response->status_line, она возвращает
что-то вроде: "404 Not Found").
MIME content-type, например "text/html", "image/gif",
"application/xml", и т.д., который Вы можете увидеть, используя
$response->content_type
Собственно содержимое запрашиваемого документа в
$response->content. В случае с HTML, здесь будет
HTML код; если - GIF, то $response->content вернёт
бинарные данные GIF.
А также множество удобных и более специфических, которые
описаны в документации по HTTP::Response, и его
суперклассам, HTTP::Message и
HTTP::Headers.
Добавление других заголовков HTTP запроса
Вот наиболее часто используемый синтаксис для запросов
$response = $browser->get($url), но, честно говоря,
Вы можете добавлять собственные строки HTTP заголовков к запросу,
добавлением списка пар ключ-значение после URL, например:
Если Вы собираетесь изменить только 'User-Agent'-параметры, Вы
можете изменить стандартную установку объекта $browser
"libwww-perl/5.65" (или что-то подобное) на всё что Вы хотите,
используя соответствующий метод объекта LWP::UserAgent:
$browser->agent('Mozilla/4.76 [en] (Win98; U)');
Включение Cookies(Теневых посылок)
Обычно объект LWP::UserAgent работает как броузер с
отключённой поддержкой cookies. Существует несколько путей для того,
чтобы включить такую поддержку, используя метод
cookie_jar. "cookie jar" - это объект, который, если
можно так сказать, олицетворяет собой маленькую БД со всеми HTTP
cookies, о которых может знать броузер. "БД" может быть сохранена на
диск (так работает Netscape, используя файл cookies.txt), или
"висеть" в памяти, при этом весь набор cookies будет потерян, как
только программа завершит свою работу.
Для того, чтобы создать пустой объект cookie jar в памяти,
вызовите cookie_jar метод следующим образом:
$browser->cookie_jar({});
Для того, чтобы делать копии cookies в файл на диске, который
будет содержать весь набор cookies, с которыми работал броузер,
после завершения программы, вызовите cookie_jar метод
следующим образом:
use HTTP::Cookies;
$browser->cookie_jar( HTTP::Cookies->new(
'file' => '/some/where/cookies.lwp',
#файл обмена
'autosave' => 1,
#по завершении, сохранять ли файл
));
Этот файл будет в специфическом формате LWP. Если Вы хотите
получить доступ к cookies из вашего Netscape-cookies файла, Вы
можете использовать следующий метод:
HTTP::Cookies::Netscape:
use HTTP::Cookies;
$browser->cookie_jar( HTTP::Cookies::Netscape->new(
'file' => 'c:/Program Files/Netscape/Users/DIR-NAME-HERE/cookies.txt',
# откуда читать куки
));
Вы можете добавить строку 'autosave' => 1 , как
мы делали ранее, но в момент записи существует вероятность того, что
Netscape может отказать в записи некоторых cookies обратно на диск.
Отправка данных форм методом POST
Многие HTML формы отправляют данные на сервер, используя запрос
HTTP POST, который вы можете осуществить следующим образом:
Например, следующая программа осуществляет поисковый запрос на
AltaVista (отправкой некоторых данных форм, используя метод HTTP
POST), и извлекает из теста ответа количество совпадений:
use strict;
use warnings;
use LWP 5.64;
my $browser = LWP::UserAgent->new;
my $word = 'tarragon';
my $url = 'http://www.altavista.com/sites/search/web';
my $response = $browser->post( $url,
[ 'q' => $word, #поисковая фраза
'pg' => 'q', 'avkw' => 'tgz', 'kl' => 'XX',
]
);
die "$url error: ", $response->status_line
unless $response->is_success;
die "Weird content type at $url -- ", $response->content_type
unless $response->content_type eq 'text/html';
if( $response->content =~ m{AltaVista found ([0-9,]+) results} ) {
#Подстрока будет вида: "AltaVista found 2,345 results"
print "$word: $1\n";
} else {
print "Couldn't find the match-string in the response\n";
}
Передача данных форм методом GET
Некоторые HTML формы передают данные не отправкой методом POST, а
совершением обыкновенного GET запроса с определённым
набором данных в конце URL. Например, если Вы пойдёте на imdb.com и запустите поиск по
фразе Blade Runner, то URL, который Вы увидите, будет
следующим:
Для запуска такого поиска при помощи LWP, надо сделать следующее:
use URI;
my $url = URI->new( 'http://us.imdb.com/Tsearch' );
# создаёт объект, представляющий URL
$url->query_form( # Здесь пары ключ => значение:
'title' => 'Blade Runner',
'restrict' => 'Movies and TV',
);
my $response = $browser->get($url);
Смотрите Главу 2, "Формы" книги Perl & LWP
для более подробного изучения HTML форм, также как и главы с шестой
по девятую для подробного изучения извлечения данных из HTML.
Преобразование относительных в абсолютые ссылки
URI класс, который мы рассмотрели только что, предоставляет
множество всевозможных функций для работы с различными частями URL
(такие как определение типа URL - $url->scheme,
определение на какой хост он ссылается - $url->host,
, и так далее на основании документации по
классам URI. Тем не менее, наиболее интересными являются метод
query_form, рассмотренный ранее, и теперь метод
new_abs для преобразования относительной
ссылки("../foo.html") в
абсолютную("http://www.perl.com/stuff/foo.html"):
use URI;
$abs = URI->new_abs($maybe_relative, $base);
Например, рассмотрим эту программку, которая выбирает ссылки из
HTML-странички сновыми модулями на CPAN:
use strict;
use warnings;
use LWP 5.64;
my $browser = LWP::UserAgent->new;
my $url = 'http://www.cpan.org/RECENT.html';
my $response = $browser->get($url);
die "Can't get $url -- ", $response->status_line
unless $response->is_success;
my $html = $response->content;
while( $html =~ m/<A HREF=\"(.*?)\"/g ) {
print "$1\n";
}
При запуске она начинает выдавать что-то вроде этого:
См. Главу 4, "URLs", книги Perl & LWP для
большей информации об объектах URI.
Конечно, использование regexp для выделения адресов является
слишком прмитивным методом, поэтому для более серьёзных программ
следует использовать модули "грамматического разбора HTML" подобные
HTML::LinkExtor или HTML::TokeParser, или,
даже может быть, HTML::TreeBuilder.
Другие свойства броузера
Объекты LWP::UserAgent имеют множество свойст для
управления собственной работой.Вот некоторые из них:
$browser->timeout(15): Этот метод устанавливает
максимальное количество времени на ожидание ответа сервера. Если
по истечении 15 секунд(в данном случае) не будет получено ответа,
то броузер прекратит запрос.
$browser->protocols_allowed( [ 'http', 'gopher']
): Устанавливаются типы ссылок, с которыми броузер будет
"общаться"., в частности HTTP and gopher. Если будет осуществена
попытка получить доступ к какому-то документу по другому протоколу
(например, "ftp:", "mailto:", "news:"), то не будет даже попытки
соединения, а мы получим ошибку 500, с сообщением подобным:
"Access to ftp URIs has been disabled".
use LWP::ConnCache;
$browser->conn_cache(LWP::ConnCache->new()):
После этой установки объект броузера пытается использовать
HTTP/1.1 "Keep-Alive", который ускоряет запросы путем
использования одного соединения для нескольких запросов к одному и
тому же серверу.
$browser->agent( 'SomeName/1.23 (more info here
maybe)' ): Определяем как наш броузер будет
идентифицировать себя в строке "User-Agent" HTTP запросов. По
умолчанию, он отсылает"libwww-perl/versionnumber", т.е.
"libwww-perl/5.65". Вы можете изменить это на более информативное
сообщение:
push @{ $ua->requests_redirectable }, 'POST':
Устанавливаем наш броузер на то, чтобы выполнять переадресацию на
POST запросы (так делает большинство современных броузеров(IE, NN,
Opera)), хотя HTTP RFC говорит нам о том, что это вообще-то не
должно осуществляться.
Для большей информации читайте полную
документацию по LWP::UserAgent.
Написание учтивых роботов
Если Вы хотите убедится, что Ваша программа, основанная на LWP,
обращает внимание на файлы robots.txt и не делает слишком
много запросов за короткий период времени Вы можете использовать
LWP::RobotUA вместо LWP::UserAgent.
LWP::RobotUA - это почти
LWP::UserAgent, и Вы можете использовать его также:
use LWP::RobotUA;
my $browser = LWP::RobotUA->new(
'YourSuperBot/1.34', 'you@yoursite.com');
# Your bot's name and your email address
my $response = $browser->get($url);
Но HTTP::RobotUA добавляет следующие возможности:
Если robots.txt на сервере, на который ссылается
$url, запрещает Вам доступ к $url, то
тогда объект $browser(учтите, что он принадлежит
классу LWP::RobotUA) не будет запрашивать его, и мы
получим в ответ ($response) ошибку 403, содержащую
строку "Forbidden by robots.txt". Итак, если Вы имеете следующую
строчку:
die "$url -- ", $response->status_line, "\nAborted"
unless $response->is_success;
тогда программа должна завершится сообщением:
http://whatever.site.int/pith/x.html -- 403 Forbidden
by robots.txt
Aborted at whateverprogram.pl line 1234
Если $browser увидит, что общался с этим сервером
не так давно, то тогда он сдлеает паузу(подобно
sleep) для предотвращения осуществления большого
количества запросов за короткий срок. Какова будет задержка? В
общем-то, по умолчанию, это - 1 минута, но Вы можете
контролировать это путём изменения атрибута
$browser->delay( minutes ).
Например:
$browser->delay( 7/60 );
Это означает, что броузер сделает паузу, когда это будет нужно,
пока со времени предыдущего запроса не пройдёт 7 секунд.
Использование прокси-серверов
В некоторых случаях Вы хотите или Вам необходимо использовать
прокси-сервера для доступа к определённым сайтам или для
использования определённого протокола. Наиболее часто такая
необходимость возникает, когда Ваша LWP-программа запускается на
машине, которая находится "за firewallом".
Для того, чтобы броузер использовл прокси, который определён в
переменных окружения(HTTP_PROXY), вызовите
env_proxy перед какими-то запросами. В частности:
use LWP::UserAgent;
my $browser = LWP::UserAgent->new;
#И перед первым запросом:
$browser->env_proxy;
HTTP Authentication(идентификация)
Многие сайты ограничивают доступ к своим страницам используя
"HTTP Authentication". Это не просто форма, куда Вы должны ввести
свой пароль для доступа к информации, это особый механизм, когда
HTTP серверпосылает броузеру сообщение, которое гласит: "That
document is part of a protected 'realm', and you can access it only
if you re-request it and add some special authorization headers to
your request"("Этот документ является частью защищённой 'области' и
Вы можете получить доступ к нему, если Вы ещё раз сделаете запрос,
добавив некоторые специфичные заголовки к Вашему запросу").
Например, администраторы сайта Unicode.org ограничивают доступ
для программ сбора emailов к их архивам электронных рассылок,
защищая их при помощи HTTP Authentication, существует общий логин и
пароль для доступа(на
http://www.unicode.org/mail-arch/)--логин - "unicode-ml" и
пароль - "unicode".
Например, рассмотрим этот URL, который является частью защищённой
области Веб-сайта:
Ели Вы попытаетесь загрузить эту страничку броузером, то получите
инструкцию: "Enter username and password for
'Unicode-MailList-Archives' at server 'www.unicode.org'", или в
графическом броузере что-то наподобие этого:
В LWP, если Вы запустите следующее:
use LWP 5.64;
my $browser = LWP::UserAgent->new;
my $url =
'http://www.unicode.org/mail-arch/unicode-ml/y2002-m08/0067.html';
my $response = $browser->get($url);
die "Error: ", $response->header('WWW-Authenticate') ||
'Error accessing',
# ('WWW-Authenticate' is the realm-name)
"\n ", $response->status_line, "\n at $url\n Aborting"
unless $response->is_success;
То тогда получите ошибку:
Error: Basic realm="Unicode-MailList-Archives"
401 Authorization Required
at http://www.unicode.org/mail-arch/unicode-ml/y2002-m08/0067.html
Aborting at auth1.pl line 9. [or wherever]
потому что $browser не знает логина и пароля для
области ("Unicode-MailList-Archives") на хосте("www.unicode.org").
Наипростейший метод дать узнать броузеру логин и пароль -
использовать метод credentials. Синтаксис следующий:
В большинстве случаев порт номер 80 - является TCP/IP портом по
умолчанию для HTTP; и Вы можете использовать метод credentials
до каких-либо запросов. Например:
Итак, если мы добавим следующее сразу после строки $browser
= LWP::UserAgent->new; :
$browser->credentials( # add this to our $browser 's "key ring"
'www.unicode.org:80',
'Unicode-MailList-Archives',
'unicode-ml' => 'unicode'
);
и запустим, то запрос пройдёт.
Accessing HTTPS URLs
Когда Вы хотите получить доступ к странице через HTTPS, то всё
будет работать как и в случае, если бы мы имели дело с обыкновенным
HTTP протоколом, если Ваш LWP имеет поддержку HTTPS (через
соответствующую Secure Sockets Layer library). Например:
use LWP 5.64;
my $url = 'https://www.paypal.com/'; # Yes, HTTPS!
my $browser = LWP::UserAgent->new;
my $response = $browser->get($url);
die "Error at $url\n ", $response->status_line, "\n Aborting"
unless $response->is_success;
print "Whee, it worked! I got that ",
$response->content_type, " document!\n";
Если Ваш LWP не имеет поддержки HTTPS, тогда ответ будет не
удачным и Вы получите следующую ошибку:
Error at https://www.paypal.com/
501 Protocol scheme 'https' is not supported
Aborting at paypal.pl line 7. [or whatever program and line]
Если Ваш LWP имеет поддержку HTTPS, тогда ответ должен быть
удачным, и Вы должны отработать с $response как и с
клюбым обыкновенным HTTP-ответом.
Для получения информации по установке поддержки HTTPS для LWP
прочитайте файл README.SSL, который входит в дистрибутив
libwww-perl.
Получение больших документов
Когда Вы запрашиваете большой(или потенциально большой) документ,
возникает проблема со стандартными действиями с методами запросов
(подобно $response = $browser->get($url)) с тем, что
весь объект ответа должен храниться в памяти. Если ответом является
30-мегабайтный файл, то это, мягко говоря, не очень хорошо для Вашей
оперативной памяти и размером Вашего процесса в ней.
Хорошей альтернативой является сохранение файла на диск, а не в
память. Синтаксис следующий:
Когда Вы используете опцию:content_file, объект
$response будет иметь все нормальные заголовки, однако
$response->content будет пустым.
Отмечу, что опция ":content_file" не поддерживалась старыми
версиями LWP, поэтому Вы должны принять это во внимание,
добавив use LWP 5.66;для проверки версии LWP, если Вы
считаете, что Ваша программа может быть запущена на системах с более
старыми версиями LWP.
Если Вы хотите, чтобы программа была совместима с более старыми
версиями LWP, тогда используйте синтаксис, который позволяет сделать
тоже самое:
use HTTP::Request::Common;
$response = $ua->request( GET($url), $filespec );
Помните, что эта статья - это всего лишь самое первое введение в LWP.