Автор: Sean M. Burke
Перевод: Дмитрий Николаев
Август 20, 2002, http://perl.com
Введение
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.
% perl -MLWP::Simple -e «getprint ‘http://cpan.org/RECENT'»
Это 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, который содержит несколько интересных методов:
Добавление других заголовков HTTP запроса
Вот наиболее часто используемый синтаксис для запросов $response = $browser->get($url), но, честно говоря, Вы можете добавлять собственные строки HTTP заголовков к запросу, добавлением списка пар ключ-значение после URL, например:
$response = $browser->get( $url, $key1, $value1, $key2, $value2, … );
Вот как отправить Netscape-подобные заголовки:
my @ns_headers = ( ‘User-Agent’ => ‘Mozilla/4.76 [en] (Win98; U)’, ‘Accept’ => ‘image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*’, ‘Accept-Charset’ => ‘iso-8859-1,*,utf-8’, ‘Accept-Language’ => ‘en-US’, ); … $response = $browser->get($url, @ns_headers);
Если Вы не будете использовать этот массив в дальнейшем, Вы можете поступить следующим образом:
$response = $browser->get($url, ‘User-Agent’ => ‘Mozilla/4.76 [en] (Win98; U)’, ‘Accept’ => ‘image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*’, ‘Accept-Charset’ => ‘iso-8859-1,*,utf-8’, ‘Accept-Language’ => ‘en-US’, );
Если Вы собираетесь изменить только ‘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, который вы можете осуществить следующим образом:
$response = $browser->post( $url, [ formkey1 => value1, formkey2 => value2, … ], );
Или, если Вам нужно посылать HTTP заголовки:
$response = $browser->post( $url, [ formkey1 => value1, formkey2 => value2, … ], headerkey1 => value1, headerkey2 => value2, );
Например, следующая программа осуществляет поисковый запрос на 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: $1n»; } else { print «Couldn’t find the match-string in the responsen»; }
Передача данных форм методом GET
Некоторые HTML формы передают данные не отправкой методом POST, а совершением обыкновенного GET запроса с определённым набором данных в конце URL. Например, если Вы пойдёте на imdb.com и запустите поиск по фразе Blade Runner, то URL, который Вы увидите, будет следующим:
http://us.imdb.com/Tsearch?title=Blade%20Runner&restrict=Movies+and+TV
Для запуска такого поиска при помощи 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 «$1n»; }
При запуске она начинает выдавать что-то вроде этого:
MIRRORING.FROM RECENT RECENT.html authors/00whois.html authors/01mailrc.txt.gz authors/id/A/AA/AASSAD/CHECKSUMS …
Но, если Вы хотите получить список абсолютных ссылок Вы можете использовать метод new_abs, изменив цикл while следующим образом:
while( $html =~ m/<A HREF=»(.*?)»/g ) { print URI->new_abs( $1, $response->base ) ,»n»; }
($response->base модуля HTTP::Message используется для определения базового адреса для преобразования относительных ссылок в абсолютные.)
Теперь наша программа выдаёт то, что ндо:
http://www.cpan.org/MIRRORING.FROM http://www.cpan.org/RECENT http://www.cpan.org/RECENT.html http://www.cpan.org/authors/00whois.html http://www.cpan.org/authors/01mailrc.txt.gz http://www.cpan.org/authors/id/A/AA/AASSAD/CHECKSUMS …
См. Главу 4, «URLs», книги Perl & LWP для большей информации об объектах URI.
Конечно, использование regexp для выделения адресов является слишком прмитивным методом, поэтому для более серьёзных программ следует использовать модули «грамматического разбора HTML» подобные HTML::LinkExtor или HTML::TokeParser, или, даже может быть, HTML::TreeBuilder.
Другие свойства броузера
Объекты LWP::UserAgent имеют множество свойст для управления собственной работой.Вот некоторые из них:
Для большей информации читайте полную документацию по LWP::UserAgent.
Написание учтивых роботов
Если Вы хотите убедится, что Ваша программа, основанная на LWP, обращает внимание на файлы robots.txt и не делает слишком много запросов за короткий период времени Вы можете использовать LWP::RobotUA вместо LWP::UserAgent.
LWP::RobotUA — это почти LWP::UserAgent, и Вы можете использовать его также:
use LWP::RobotUA; my $browser = LWP::RobotUA->new( ‘YourSuperBot/1.34’, ‘ Этот e-mail адрес защищен от спам-ботов, для его просмотра у Вас должен быть включен Javascript ‘); # Your bot’s name and your email address my $response = $browser->get($url);
Но HTTP::RobotUA добавляет следующие возможности:
Для большей информации читайте полную документацию по LWP::RobotUA.
Использование прокси-серверов
В некоторых случаях Вы хотите или Вам необходимо использовать прокси-сервера для доступа к определённым сайтам или для использования определённого протокола. Наиболее часто такая необходимость возникает, когда Ваша LWP-программа запускается на машине, которая находится «за firewallом».
Для того, чтобы броузер использовл прокси, который определён в переменных окружения(HTTP_PROXY), вызовите env_proxy перед какими-то запросами. В частности:
use LWP::UserAgent; my $browser = LWP::UserAgent->new; #И перед первым запросом: $browser->env_proxy;
Для большей информации о параметрах прокси читайте документацию по LWP::UserAgent, в частности обратите внимание на методы proxy, env_proxy и no_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, который является частью защищённой области Веб-сайта:
http://www.unicode.org/mail-arch/unicode-ml/y2002-m08/0067.html
Ели Вы попытаетесь загрузить эту страничку броузером, то получите инструкцию: «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 $urln 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. Синтаксис следующий:
$browser->credentials( ‘servername:portnumber’, ‘realm-name’, ‘username’ => ‘password’ );
В большинстве случаев порт номер 80 — является TCP/IP портом по умолчанию для HTTP; и Вы можете использовать метод credentials до каких-либо запросов. Например:
$browser->credentials( ‘reports.mybazouki.com:80’, ‘web_server_usage_reports’, ‘plinky’ => ‘banjo123’ );
Итак, если мы добавим следующее сразу после строки $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 $urln «, $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-мегабайтный файл, то это, мягко говоря, не очень хорошо для Вашей оперативной памяти и размером Вашего процесса в ней.
Хорошей альтернативой является сохранение файла на диск, а не в память. Синтаксис следующий:
$response = $ua->get($url, ‘:content_file’ => $filespec, );
Например,
$response = $ua->get(‘http://search.cpan.org/’, ‘:content_file’ => ‘/tmp/sco.html’ );
Когда Вы используете опцию: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— для более глубокого изучения LWP и задач, связанных с LWP, Вам стоит прочитать следующие материалы:
Copyright ©2002, Sean M. Burke. Translation into Russian by Dmitry Nikolayev.
You can redistribute this document and/or modify it, but only under the same terms as Perl itself.
Источник: