Очередная поделка на Erlang: парсим выдачу Google

5 декабря 2012

В свободное время я продолжаю (см один, два и три) писать небольшие поделки на Erlang. Это занятие видится мне прекрасным способом изучения языка. Почти наверняка в этой заметке будет изрядное количество ляпов, ну да я надеюсь, что более опытные коллеги меня поправят.

Для установки сторонних библиотек в Perl используется утилита cpan, в Python — pip, в Haskell — cabal. В мире Erlang есть нечто подобное под названием Agner, но обычно все же используется rebar. В нем применяется другой подход, не такой, как в cpan или cabal. Идея заключается в том, чтобы указать ссылки на git-репозитории требуемых библиотек (кстати, не обязательно git) и перед сборкой приложения подтянуть исходники этих библиотек в каталог с кодом приложения.

Соответственно, поиск библиотек осуществляется с помощью поиска GitHub’а или какого-то другого хостинга проектов. Большинство, если не абсолютно все, Erlang-библиотеки хостятся именно на GitHub’е. По моему опыту Google ищет по GitHub’у лучше самого GitHub’а. Таким образом, библиотеки можно искать прямо в гугле по запросу site:github.com erlang ключевые слова.

В общем, все это напоминает использование Maven из мира Java или, например, связку perlbrew, cpanm и Makefile.PL из мира Perl. Для меня этот подход не очень привычен, но отчего бы ему не иметь права на жизнь? Как минимум, Erlang-библиотеки хранятся как бы очень распределенно, а распределенность — это круто! Вон у программистов на C/C++ даже такого инструмента, кажется, нет, и ничего, живут ведь как-то. Может, менеджеры пакетов самой ОС используют?

Чтобы получить rebar, нужно собрать его из исходников:

git clone git://github.com/basho/rebar.git
cd rebar
make

Полученный исполняемый файл rebar можно положить в ~/bin, прописав этот путь в $PATH. Часто rebar кладут прямо в репозиторий с кодом проекта и создают Makefile-обертку вокруг него, чтобы проект можно было собрать и установить традиционным make && make install.

Попробуем воспользоваться rebar на практике. Создадим новый Git-репозиторий.

Создадим новое приложение:

rebar create-app appid=gsearch

Затем добавим в репозиторий файл rebar.config следующего содержания:

% требуемая версия Erlang/OTP
{require_otp_vsn, "R14|R15"}.

% куда складывать зависимости
{lib_dirs, ["deps"]}.

% список зависимостей
{deps, [
  {ibrowse, ".*",
    {git, "git://github.com/cmullaparthi/ibrowse.git",
      {tag, "v4.0.1"}}}
]}.

На самом деле rebar способен не только тянуть зависимости. Он также умеет прогонять модульные тесты, поддерживает хуки, плагины, и многое другое, так что rebar.config может быть намного сложнее. См например rebar.config.sample в репозитории rebar’а, а также список команд, поддерживаемых rebar’ом: rebar -c.

Теперь создадим файл src/gsearch.erl:

-module(gsearch).
-export([main/0, main/1]).

-include("deps/ibrowse/include/ibrowse.hrl").

main(List) ->
  Query = lists:foldl(
      fun(X, Sum) ->
        Sum ++ " " ++ atom_to_list(X)
             end, "", List ),
  EncodedQuery = http_uri:encode(Query),
  SearchUrl = "http://www.google.com/search?q=" ++ EncodedQuery,
  io:format("Fetching ~s ...~n", [SearchUrl]),
  ibrowse:start(),
  case ibrowse:send_req(SearchUrl, [], get) of
  { ok, "200" , _Headers, Data } ->
    parse_serp(Data);
  Rslt ->
    io:format("Request failed: ~p~n", [Rslt])
  end.
main() ->
  io:format("Usage: gsearch query~n").

parse_serp(Data) ->
  Regex = "<a href=\"/url\\?q=(.*?)&amp;sa=U",
  case re:run(Data, Regex, [ global, {capture, [1], list}]) of
  { match, MatchList } ->
    lists:foreach(
      fun ([Url|_]) ->
        DecodedUrl = http_uri:decode(Url),
        io:format("~s~n", [DecodedUrl])
      end,
      MatchList );
  NoMatch ->
    io:format("Nothing found: ~p~n", [NoMatch])
  end.

Здесь мы просто шлем HTTP-запрос Google с помощью библиотеки ibrowse и парсим выдачу регулярными выражениями. Для работы с регулярными выражениями (при помощи PCRE) используется модуль re, для работы с urlencode — модуль http_uri. Эти модули, как и lists или io, идут вместе с Erlang. Кстати, вместо ibrowse мы также могли использовать стандартный модуль httpc, но тогда не пришлось бы использовать rebar!

Тянем зависимости:

rebar get-deps

Собираем приложение:

rebar compile

Для удобства создадим скрипт gsearch.sh:

#!/bin/sh

erl -noshell \
  -pa ebin ./deps/*/ebin \
  -s gsearch main $@ \
  -s init stop

Проверяем, все ли работает:

chmod u+x gsearch.sh
./gsearch.sh записки программиста

У меня программа честно ходит в Google и выводит в консоль результаты поиска. Надеюсь, что и у вас тоже.

Вот, собственно, и все, о чем я хотел сегодня поведать. В ближайшее время я планирую осилить модульное тестирование с помощью eunit и meck. Там все выглядит довольно просто. Кстати, я насчитал как минимум три инструмента для модульного тестирования в Erlang — это EUnit, Common Test и QuickCheck. Зачем их нужно так много на данном этапе остается для меня загадкой.

Дополнение: См также заметку про распаковку gzip в ibrowse.

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.