Skip to content

Instantly share code, notes, and snippets.

@mokevnin
Last active December 10, 2015 00:29
Show Gist options
  • Select an option

  • Save mokevnin/4351374 to your computer and use it in GitHub Desktop.

Select an option

Save mokevnin/4351374 to your computer and use it in GitHub Desktop.
-module(grabber).
-export([start/1]).
-export([downloader/2, storage/1]).
start(Host) ->
inets:start(),
StoragePid = spawn(grabber, storage, [Host]),
StoragePid ! {links, [Host]}.
storage(Host) ->
receive
{links, Links} ->
%lists:map(fun(Link) -> io:format("~s~n", [Link]) end, Links),
lists:map(fun(Url) -> spawn(grabber, downloader, [self(), Url]) end, get_internal_links(Links, Host)),
storage(Host)
end.
get_internal_links(Links, Host) ->
get_internal_links(Links, Host, []).
get_internal_links([Head | Tail], Host, Lists) ->
case string:str(Head, Host) of
0 ->
get_internal_links(Tail, Host, [Host ++ Head | Lists]);
1 ->
get_internal_links(Tail, Host, [Head | Lists]);
_ ->
get_internal_links(Tail, Host, Lists)
end;
get_internal_links([], _, Lists) ->
Lists.
downloader(StoragePid, Url) ->
case get_links_by_url(Url) of
{ok, Links} -> StoragePid ! {links, Links};
{error, Status} -> io:format("~s ~w~n", [Url, Status])
end.
get_links_by_url(Url) ->
case httpc:request(Url) of
{ok, {_, _, Body}} ->
io:format("~s~n", [Url]),
{ok, get_links_by_body(Body)};
{Status, _} ->
{error, Status}
end.
get_links_by_body(Body) ->
case re:run(Body, "href=\"([^\"]+)\"", [global]) of
{match, Matches} ->
lists:map(fun([_, {Start, Length}]) -> string:substr(Body, Start + 1, Length) end, Matches);
nomatch -> []
end.
@vlm
Copy link

vlm commented Dec 21, 2012

  1. Лучше оформлять такие вещи под OTP. То есть, лучше erl -man gen_server, чем spawn_link(), а spawn_link() лучше, чем spawn(). Например, storage() — это типичный gen_server, который принимает работу, диспетчеризует её и хранит новое состояние. Сейчас любой code reload убьёт граббер, а если оформить через gen_server, то появится возможность и код добавлять туда, и новые функции, получить интроспекцию (например, посмотреть на текущее состояние процесса, sys:get_status()).
  2. Нет лимитирования concurrency: может быть экспоненциальный взрыв прохода по ссылкам. Надо какое-то ограничение поставить. А для этого нужно иметь какой-то процесс, контролирующий concurrency. Либо главный процесс (получающий ссылки и запускающий воркеров) является gen_server'ом, или там сразу целый оркестр разных процессов. Для последнего этого может иметь смысл сразу всё оформлять в erl -man application, который с супервизором на несколько процессов. Как говорил Махоткин в своё время, «книжка у них была, но прочитали они её ровно до половины» ;) — в смысле, что надо не только примитивами языка пользоваться, но и OTP.
  3. Завязывайте с KingdomOfNouns (google it), функции должны называться :download(), а не downloader(), например. Эта простая вещь поможет быстрее вникнуть в механику FP.
  4. Чуть более идиоматично:
    lists:map(fun(Url) -> spawn(grabber, downloader, [self(), Url]) end, get_internal_links(Links, Host)),
    =>
    [ feed_grabber(Url) || Url <- get_internal_links(Links, Host) ]
  5. Независимо от FP, функция get_internal_links — очень слабая и хрупкая. Надо переписать понадёжнее.
  6. Лесенка downloader->get_links_by_url->get_links_by_body неидиоматична в FP. Надо как-то так:
links_by_body(Body) -> [Link]
body_by_url(Url) -> [Body]
download(Pid, Url) ->
    Links =  [ Link ||
                Body <- body_by_url(Url),
                Link <- links_by_body(Body) ],
    Pid ! Links.

Какие плюсы это даёт: увеличивается тестируемость: вместо запуска функции get_links_by_url() и ожидания самого-самого последнего результата цепочки функций, мы просто берём отдельные функции типа URL->Body; Body->Links и нанизываем их друг на друга. Обрати внимание, появляется возможность протестировать body_by_url независимо от links_by_body.

  1. Лучше использовать какой-то HTML-парсер, например, входящий в состав MochiWeb. Как минимум, потому что http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment