Previous Entry Share
Многопоточная качалка на Erlang
what_me
Ну вот, моя первая программа на эрланге. https://github.com/onokhov/erlang_web_crawler/

Язык занятный. Параллелить и устраивать межпроцессное общение просто. Но остальное писать неудобно, думаю, что просто с непривычки. И документацию я ещё не прочитал. Осилил только getting_started, описания модулей string, list, re, httpc и ещё некоторых.
Сторонние модули использовать не хотелось, поэтому то, что в штатных модулях не нашел, делал сам.

Сравнил по производительности с перловой качалкой, эрланговская получилась немного быстрее. За минуту с форума glav.su перловая вытянула 26МБ, а эрланговская 26,7МБ.

Хотелось бы критики по коду от людей эрланг знающих.

Update: Закоммитил правки по комментариям. Заменены паттерны, ++ заменен на [|], использованы list comprehensions.
Update 2: Отказ от prespawn. И правки по стилю
Update 3: Багфикс в receive_text_data/2. Правки по стилю
Tags: ,

"ostaljnoje" eto shto?
jesli na erlange pisatj parser, to on vygljadit odin v odin kak BNF -- odna funkcija odno sostojanije -- net nichego luche Erlanga dlja pisanija parserov.

a parallelitj NIKTO KROME Erlanga ne mozhet nikak -- go, c++, i prochije KOSTYLI eto vsjo gavno i xujeta

а вот замена тренарному оператору "?:" какая?

# Хочу выкинуть порт, если он совпадает с дефолтным для схемы
PortPart = ( Scheme eq 'http' &&  Port == 80 ) ? '' : Port;


Я накорябал
if_not_empty(Test, Result) -> % не хватает в эрланге тренарного оператора ? : 
    case Test of
        [] ->
            [];
        _ ->
            Result
    end.

filter_default_port({Scheme, Port}) ->
    case {Scheme, Port} of
        {http, 80} ->
            [];
        {https, 443} ->
            [];
        {ftp, 21} ->
            [];
        _ ->
            Port
    end.

PortPart = if_not_empty(filter_default_port({Scheme, Port}), ":" ++ integer_to_list(Port)).


Как это улучшить?

Edited at 2017-01-08 07:16 am (UTC)

(no subject) (Anonymous) Expand
lists:foreach(fun(Pid) -> exit(Pid, stop) end, FreeWorkers)

nu zachem foreach!!!!

[ exit(Pid, stop) || Pid <- FreeWorkers ]

Спасибо. До list comprehension я ещё не дочитал.

crawler(... FreeWorkers ...) when ... length(FreeWorkers) > 0 ->
[Worker | WorkersLeft] = FreeWorkers,
...

eto nado perepejsatj tak:

crawler(... [Worker | WorkersLeft] ...) ->
...

[Worker] ++ Workers

"++" eto zlo!

[Worker|Workers]

lists:any(fun({Header, Value}) ->
case Header of
"content-type" ->
string:str(Value, "text/") == 1;
_ ->
false
end
end, Headers).


"fun()->end" eto ochenj neudobnaja zapisj kotoruju (darom chto suka samaja mjakotka mashiny) nado izbegatj.


length([ ok || {Header, Value} <- Headers, Header =:= "content-type", string:str(Value, "text/") == 1 ])


Edited at 2017-01-08 12:55 am (UTC)

crawler(Url, NumWorkers) when NumWorkers >= 1 ->

will cause an error "undefined function" if called with NumWorkers < 1
you may want to avoid that:

crawler(Url, NumWorkers) when NumWorkers < 1 -> ok; % or report an error
crawler(Url, NumWorkers) -> ...

Re: pojexali ponovoj

угу. Я тут думал так. Поскольку это запуск программы, то меня удовлетворит стандартный ответ undefined function при вызове с неверными аргументами. Главное, чтоб в функцию правильные попали.

io:format("Resume download session~n"),
crawler([], UrlsRestored, FreeWorkerPids, SeenUrlsRestored, [], Host);
_ ->
crawler([], [Url], FreeWorkerPids, [], [], Host)

you ignore Url when restoring. it may prove very inconvenient in real usecases. you should never ignore the request of the user

you may either
issue a warning and let the user decide about the old session.
or
ADD the given Url to the session and continue the old job.

Да, но это уже не эрланговый вопрос. Такого рода недочеты в программе есть ещё. Дело в том, что эта утилита не предназначена для использования. Это упражнение.

[ exit(Pid, stop) || Pid <- FreeWorkerPids ], % посылаем сигналы остановки воркерам

ty otkuda eti Pidy vzjal?
IZ FAJLA.
cikl zhizni fajla ne sovpadajet s ciklom zhizni pidov!
na moment zapuska etoj komandy eti Pidy mogut oznachatj vsjo chto ugodno! ty riskujesh poubivatj processy kotoryje nikakogo otnoshenija k tebe ne imejut.

Нет, пиды не сохраняются в файле. Они на каждый запуск свои, и убиваются именно живые. А в файле только урлы

lists:append/2 is identical to ++
use ++

Host ne nado zapisyvatj v fajl sessii -- ty kogda fajl chitajesh UZHE ZNAJESH kakoj eto Host.

Это наследие от прошлых версий осталось. Изначально файл с состоянием был фиксированного имени "crawler.state", и мне нужен был хост внутри, чтоб понять, продолжаем ли мы прерванную, или скачиваем другой сайт. После того как хост попал в имя файла, внутри он перестал быть нужен, да. Остался дискуссионный вопрос - должен ли идентифицировать загрузку стартовый урл, или достаточно хоста из стартового урла. Я выбрал идентификацию по хосту, а ты в комментариях выше где-то писал о том, что нельзя игнорировать пользовательский ввод. При идентификации по хосту он учитывается, но не в точности, а грубо.

ja ne znaju eto ty sam pridumal ili podskazal kto
{worker, Pid, urls_to_fetch, NewUrls, url_fetched, Url}

v principe ostroumno, no zachem?
jesli kortezh korotkij, to i tak ponjatno, a jesli dlinnyj, to s etimi "imenami-atomami" stanet sovsem gromozdko...
a jesli eto ne malenjkaja programma a chto-to zhutko intensivnoje, to zachem rasxodovatj pamjatj vpustuju?

ja by ogranichil sja KOMMENTARIJEM...
no jestj ljudi kotoryje ljubjat records (eto takije osobyje imenovanyje kortezhi)

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

[UrlsInProgress|SeenUrls] -- eto fataljnaja oshibka!!!

[ head | tail ]
tail = [ head ]

tip pervogo argumenta eto ELEMENT mnozhestva predstavlennogo vtorym argumentom.

[[1,2] | [3,4]] == [[1,2], 3, 4]

if you do NOT prespawn workers then:
(1) the entire function worker/1 simply vanishes
(2) the list of worker pids and the necessary routine simply vanishes
(3) the messages get shorter by this Pid

all you need to monitor is merely A LIMIT of allowed workers -- one integer number. then you spawn workers WHEN NEEDED, and let them die naturally when they finish their job.

no ty uchti, ja na sratom laptope spawnil 10^7 processov i vsjo rabotalo, takchto limit eto chisto chtoby ne bezkonechnostj.

a prespawn lishon smysla potomuchto porozhdenije processa eto TRIVIALJNAJA OPERACIJA -- Erlang ozhidajet chto ty budesh spawnitj i ubivatj processy v neverojatnyx kolichestvax, process kotoryj zhdjot nikakoj cennosti ne imejet, potomuchto cena zapuska menjshe chem cena ozhidanija.

Edited at 2017-01-10 06:48 am (UTC)

угу, переделаю алгоритм на spawn для каждого урла, с ограничением максимального количества одновременно работающих. Тогда хранить пиды воркеров и убивать их не надо.

novaja iteracija uluchshanija

BEHOLD!!!

is_text_headers(Headers) ->
[] =/= [ ok || {"content-type", [$t,$e,$x,$t,$/|_]} <- Headers ].


Re: novaja iteracija uluchshanija

+

?

Log in

No account? Create an account