Comments 36
Если Вам не сложно, сравните, пожалуйста с парсером HTML::TreeBuilder (Perl). Я пользуюсь исключительно им.
Присылайте код — добавлю c удовольствием.
Добавить очень просто — см github.com/seriyps/html-parsers-benchmark#how-to-add-my-platformname-to-benchmark-set
Добавить очень просто — см github.com/seriyps/html-parsers-benchmark#how-to-add-my-platformname-to-benchmark-set
jsdom использует упомянутый htmlparser непосредственно для парсинга страницы, более того он на выходе дает полноценный DOM, а не AST. ИМХО, я бы его выкинул из сравнения, т.к. по сути это не совсем чистый парсер
Спасибо за пояснение. Тем не менее, кто-то jsdom использует и для простого парсинга — habrahabr.ru/qa/29711/
1. Емнип, сами парсеры разной сложности, из-за чего может быть разброс по памяти скорости и т.п. То есть если какой-то парсер в процессе еще жнет, шьет и на дуде играет, то… Но я не спец по этим парсерам, поэтому ничего не смогу сказать
2. Erlang порадовал, сам не ожидал, но, не глядя в код, можно сделать следующие предположения:
— файл считывается, как binary, после чего вычленение нужных участков становится во-первых легким (google: erlang bit syntax), а во-вторых эффективным (см. пункт 3)
3. Про «копирование всего и вся» и память
— из-за немутабельности данных сборщик мусора с одной стороны простой, с другой — эффективный. Никаких stop-the-world и прочего, неиспользованные данные убираются из памяти весьма оперативно
— Для большинства данных в пределах одного процесса копируется только указатель на структуру, содержащую данные, а сами данные упакованы весьма эффективно. При передаче из процесса в процесс данные копируются
— В случае двоичных данных больше определенного размера эти данные существуют в одном экземпляре на ноду (не процесс, а именно ноду), и повсюду передается только указатель на эти данные.
Поэтому потребление памяти может быть весьма маленьким по сравнению с другими участниками
4. Оверхед на запуск — есть такое, потому что запускается много придожений и подгружается много кода. Можно сделать минимальную систему, запускающую только полтора приложения и т.п., но в реальном приложении это не так критично, потому что VM запускается один раз, а потом работает до скончания времен
5. Есть еще sax-парсеры для Эрланга (как и для других языков), по идее они должны смочь обработать невалидный HTML
2. Erlang порадовал, сам не ожидал, но, не глядя в код, можно сделать следующие предположения:
— файл считывается, как binary, после чего вычленение нужных участков становится во-первых легким (google: erlang bit syntax), а во-вторых эффективным (см. пункт 3)
3. Про «копирование всего и вся» и память
— из-за немутабельности данных сборщик мусора с одной стороны простой, с другой — эффективный. Никаких stop-the-world и прочего, неиспользованные данные убираются из памяти весьма оперативно
— Для большинства данных в пределах одного процесса копируется только указатель на структуру, содержащую данные, а сами данные упакованы весьма эффективно. При передаче из процесса в процесс данные копируются
— В случае двоичных данных больше определенного размера эти данные существуют в одном экземпляре на ноду (не процесс, а именно ноду), и повсюду передается только указатель на эти данные.
Поэтому потребление памяти может быть весьма маленьким по сравнению с другими участниками
4. Оверхед на запуск — есть такое, потому что запускается много придожений и подгружается много кода. Можно сделать минимальную систему, запускающую только полтора приложения и т.п., но в реальном приложении это не так критично, потому что VM запускается один раз, а потом работает до скончания времен
5. Есть еще sax-парсеры для Эрланга (как и для других языков), по идее они должны смочь обработать невалидный HTML
1. Да, всё верно. Например jsdom делает много дополнительной работы, а тот же mochiweb_html или cheerio строят простейшее дерево. Но в контексте бенчмарка это не важно. Цель как раз показать, что использовать слишком навороченные парсеры для простых задач не стоит.
2. Да, mochiweb_html парсит всё в бинарном виде, дерево тоже состоит из таплов и бинарей (листы только для списков атрибутов и дочерних тегов). В процессе парсинга в контексте передается текущая позиция парсера как сдвиг в исходном HTML binary, т.е. он делает примерно так
3. Мне вот интересно, Erlang парсер в итоге строит структуру наподобие
4. Да, я упомянул оверхед на запуск мельком просто потому что были все нужные данные чтобы его посчитать, так это не особо интересная информация.
5. Да, SAX несомненно будет и быстрее и экономнее по памяти (mochiweb_html например в 2 этапа парсит — сперва строит чем-то вроде SAX парсера плоский список токенов, а на втором этапе генерирует из него вложенное дерево). Я хотел померять именно DOM парсеры.
2. Да, mochiweb_html парсит всё в бинарном виде, дерево тоже состоит из таплов и бинарей (листы только для списков атрибутов и дочерних тегов). В процессе парсинга в контексте передается текущая позиция парсера как сдвиг в исходном HTML binary, т.е. он делает примерно так
S=#decoder{offset=O}
...
<<_:O/binary, C, _/binary>> when ?IS_WHITESPACE(C) ->
tokenize_attributes(B, ?INC_CHAR(S, C), Acc);
3. Мне вот интересно, Erlang парсер в итоге строит структуру наподобие
{<<"html">>, [{<<"lang">>, <<"ru-RU">>}], [
{<<"head">>, [], []},
{<<"body">>, [], [<<"Hello, World!">>]}
]}
Интересно все вот эти мелкие бинари на самом деле являются ссылками на участки исходного большого бинари (бинари слайс это вроде называется), или создаются отдельные маленькие копии участков памяти?4. Да, я упомянул оверхед на запуск мельком просто потому что были все нужные данные чтобы его посчитать, так это не особо интересная информация.
5. Да, SAX несомненно будет и быстрее и экономнее по памяти (mochiweb_html например в 2 этапа парсит — сперва строит чем-то вроде SAX парсера плоский список токенов, а на втором этапе генерирует из него вложенное дерево). Я хотел померять именно DOM парсеры.
> Интересно все вот эти мелкие бинари на самом деле являются ссылками на участки исходного большого бинари
Таки на участки исходного binary. См. erlanger.ru/ru/page/2431/erlang-binaries-i-sborka-musora-i-tyazhelyj-vzdoh
«Более того, Erlang проводит некоторые дополнительные оптимизации. При использовании erlang:split_binary/2 или при извлечении данных при помощи сопоставления с образцом результат не обязательно является новым binary. Вместо этого VM создает новый тип под названием sub-binary, который на деле является ссылкой на оригинальные данные — на heap binary или на refc-binary.»
Из-за этого, видать, потребление памяти относительно низкое — все лежит в одном большом двоичном куске и расходы только на указатели
Таки на участки исходного binary. См. erlanger.ru/ru/page/2431/erlang-binaries-i-sborka-musora-i-tyazhelyj-vzdoh
«Более того, Erlang проводит некоторые дополнительные оптимизации. При использовании erlang:split_binary/2 или при извлечении данных при помощи сопоставления с образцом результат не обязательно является новым binary. Вместо этого VM создает новый тип под названием sub-binary, который на деле является ссылкой на оригинальные данные — на heap binary или на refc-binary.»
Из-за этого, видать, потребление памяти относительно низкое — все лежит в одном большом двоичном куске и расходы только на указатели
Это здорово. Недавно наткнулся на другой бенчмарк XML парсеров, в котором с огромным отрывом побеждал парсер на языке D (бенчмарк пояснения). Так вот, он выигрывал как раз в основном за счет zero-copy — т.е. не копировал куски исходного HTML, а ссылался на них (array-slicing).
Но он не раскодировал XML entities, так что
В эрланге это можно оптимизировать, используя io_lists наподобие
Но он не раскодировал XML entities, так что
<
не превращается в <
. Если бы не это допущение, то пришлось бы автору парсера придумывать какие-то хаки, чтобы оставаться zero-copy.В эрланге это можно оптимизировать, используя io_lists наподобие
[<<"бинари до entity">>, <<"<">>, <<"бинари после entity">>]
, хотя работать с таким уже менее удобно.> Вот так сюрприз! PyPy всем сливает.
Не понимаю, что вас удивило — PyPy является интерпретатором Python написанный на Python, откуда там взяться производительности?
Не понимаю, что вас удивило — PyPy является интерпретатором Python написанный на Python, откуда там взяться производительности?
Есть предположение, что тормоза и аппетиты нативных реализаций на python являются следствием immutable типов данных, которые копируются при каждом изменении. А ведь к ним, применимо к парсингу текста, относятся строки и кортежи.
И я рад за наличие у python такого качественного libxml2 биндинга.
И я рад за наличие у python такого качественного libxml2 биндинга.
Присылайте свои реализации! Это очень просто!На гитхабе уже прислали реализации для ruby и golang. Обновляйте пост!
К сожалению, пока не успеваю добавить на графики, но кому интересно — вот результаты для 100 итераций для perl, Go и Ruby
На подходе Java и Haskell
Скрытый текст
$ PLATFORMS="golang perl ruby" ./run.sh 100 =============== golang =============== ****************************** parser:./bin/bench_exp_html file:../page_google.html 0.270294 s real:0.27 user:0.27 sys:0.00 max RSS:4540 ****************************** parser:./bin/bench_gokogiri file:../page_google.html 0.315391 s real:0.32 user:0.28 sys:0.03 max RSS:78196 ****************************** parser:./bin/bench_h5 file:../page_google.html 5.436318 s real:5.43 user:5.42 sys:0.00 max RSS:6716 ****************************** parser:./bin/bench_exp_html file:../page_habrahabr-70330.html 5.826405 s real:5.83 user:5.78 sys:0.02 max RSS:50492 ****************************** parser:./bin/bench_gokogiri file:../page_habrahabr-70330.html 6.471175 s real:6.56 user:5.89 sys:0.65 max RSS:1943176 ****************************** parser:./bin/bench_h5 file:../page_habrahabr-70330.html 96.829145 s real:96.83 user:96.51 sys:0.02 max RSS:62356 ****************************** parser:./bin/bench_exp_html file:../page_habrahabr-index.html 0.293615 s real:0.29 user:0.29 sys:0.00 max RSS:4828 ****************************** parser:./bin/bench_gokogiri file:../page_habrahabr-index.html 0.361011 s real:0.36 user:0.34 sys:0.02 max RSS:97308 ****************************** parser:./bin/bench_h5 file:../page_habrahabr-index.html 4.111917 s real:4.11 user:4.09 sys:0.00 max RSS:5472 ****************************** parser:./bin/bench_exp_html file:../page_wikipedia.html 0.370002 s real:0.37 user:0.36 sys:0.00 max RSS:5100 ****************************** parser:./bin/bench_gokogiri file:../page_wikipedia.html 0.343510 s real:0.35 user:0.31 sys:0.04 max RSS:106832 ****************************** parser:./bin/bench_h5 file:../page_wikipedia.html 4.674953 s real:4.67 user:4.64 sys:0.01 max RSS:6564 =============== perl =============== ****************************** parser:mojo_parser.pm file:../page_google.html 3.89669394493103 s real:4.02 user:3.98 sys:0.01 max RSS:8096 ****************************** parser:mojo_parser.pm file:../page_habrahabr-70330.html 75.7632319927216 s real:75.81 user:75.56 sys:0.01 max RSS:36384 ****************************** parser:mojo_parser.pm file:../page_habrahabr-index.html 3.66110587120056 s real:3.70 user:3.68 sys:0.00 max RSS:8288 ****************************** parser:mojo_parser.pm file:../page_wikipedia.html 4.00335907936096 s real:4.04 user:4.01 sys:0.01 max RSS:8404 =============== ruby =============== ****************************** parser:nokogiri_parser.rb file:../page_google.html 0.343421056 s real:0.39 user:0.37 sys:0.01 max RSS:13124 ****************************** parser:nokogiri_parser.rb file:../page_habrahabr-70330.html 8.438090464 s real:8.49 user:8.45 sys:0.00 max RSS:36876 ****************************** parser:nokogiri_parser.rb file:../page_habrahabr-index.html 0.363908843 s real:0.40 user:0.40 sys:0.00 max RSS:14352 ****************************** parser:nokogiri_parser.rb file:../page_wikipedia.html 0.378928282 s real:0.41 user:0.41 sys:0.00 max RSS:14776 $ PLATFORMS="c-libxml2" ./run.sh 100 # добавил просто для сравнения =============== c-libxml2 =============== ****************************** parser:libxml2_html_parser.c file:../page_google.html 0.306867 s real:0.31 user:0.28 sys:0.00 max RSS:2244 ****************************** parser:libxml2_html_parser.c file:../page_habrahabr-70330.html 6.887496 s real:6.89 user:6.80 sys:0.06 max RSS:24292 ****************************** parser:libxml2_html_parser.c file:../page_habrahabr-index.html 0.291910 s real:0.29 user:0.28 sys:0.00 max RSS:2412 ****************************** parser:libxml2_html_parser.c file:../page_wikipedia.html 0.374730 s real:0.37 user:0.37 sys:0.00 max RSS:2504
На подходе Java и Haskell
> Проверять качество работы парсера в плане полноты восстановления поломанных документов не будем.
А зря, как мне кажется. Html5 включает обработку любых ошибок в документе, поэтому строго говоря, любой текст — валидный html5-документ и должен одинаково разбираться всеми парсерами.
А зря, как мне кажется. Html5 включает обработку любых ошибок в документе, поэтому строго говоря, любой текст — валидный html5-документ и должен одинаково разбираться всеми парсерами.
Решил проверить Dart. Замена же JS как ни как. Может я его не умею готовить, но результаты какие-то совсем печальные.
Скрытый текст
$ PLATFORMS="dart" ./run.sh 100
===============
dart
===============
******************************
parser:html5lib_parser.dart file:../page_google.html
38864
real:39.13 user:38.76 sys:0.27 max RSS:73740
******************************
parser:html5lib_parser.dart file:../page_habrahabr-70330.html
686300
real:687.00 user:679.05 sys:5.72 max RSS:269588
******************************
parser:html5lib_parser.dart file:../page_habrahabr-index.html
34617
real:34.88 user:34.48 sys:0.26 max RSS:80804
******************************
parser:html5lib_parser.dart file:../page_wikipedia.html
36756
real:37.01 user:36.53 sys:0.33 max RSS:76560
И так, текущий список платформ:
# были изначально
pypy
python
c-libxml2
erlang
nodejs
# добавились
golang (3 шт)
haskell
java
perl
php + tidy
ruby
dart
Спасибо тем, кто присылал свои варианты парсеров!
В ближайшее время потестирую добавленные парсеры и обновлю статью (или даже вторую часть напишу).
# были изначально
pypy
python
c-libxml2
erlang
nodejs
# добавились
golang (3 шт)
haskell
java
perl
php + tidy
ruby
dart
Спасибо тем, кто присылал свои варианты парсеров!
В ближайшее время потестирую добавленные парсеры и обновлю статью (или даже вторую часть напишу).
Сделал небольшой апдейт — прикрепил результаты для новых парсеров и платформ. К сожалению, пока только как архив с CSV и графиками.
Я смотрю php + tidy весьма позитивно выглядит в тестах, ожидал более плохого результата.
Чем хорош PHP это тем, что если используемые библиотеки хорошо написаны, в данном случае на C/C++, то и скорость будет хорошей, оверхед на вызов самих функций минимальный. Совсем другое дело, когда какая-то тяжелая либа написана на самом интерпретаторе, то тогда да — туши свет, покупай апгрейд CPU…
Надо бы другие варианты еще потестить. Мне кажется, он может еще быстрее :)
Думаю можно утилизировать возможности libxml как то так php.net/manual/en/domdocument.loadhtml.php в комбинации с php.net/manual/en/class.domdocument.php#domdocument.props.recover и php.net/manual/en/class.domdocument.php#domdocument.props.stricterrorchecking
Что то вроде
(сам не проверял)
Что то вроде
$doc = new DomDocument();
$doc->recover = TRUE;
$doc->stricterrorchecking = FALSE;
$doc->loadHTML($html_string);
(сам не проверял)
Кстати, когда пробовал запустить php+tidy парсер на top1000 Alexa (тест на зависимость от размера HTML файла), получил 100500 сообщений об ошибках / Warnings, пришлось php для этого теста убрать. Если посоветуете как это поправить — будет здорово.
и т.п. Причем остальные парсеры с этим нормально справляются как то.
parser:tidy_simplexml.php file:../pages/page_10086.cn.html PHP Warning: simplexml_load_string(): Entity: line 2: parser error : PEReference in prolog in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): "%20http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): ^ in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 ****************************** parser:tidy_simplexml.php file:../pages/page_163.com.html PHP Warning: simplexml_load_string(): Entity: line 1192: parser error : Char 0xDF71 out of allowed range in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): ���<U+DF71>2012���<U+05FD>����ѡ</a> in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): ^ in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): Entity: line 1192: parser error : PCDATA invalid Char value 57201 in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 ****************************** parser:tidy_simplexml.php file:../pages/page_addthis.com.html PHP Warning: simplexml_load_string(): namespace error : Namespace prefix addthis for userid on a is not defined in /home/seriy/workspace/html_parser_bench/php -tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): addthis:userid="AddThis"></a> in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): ^ in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): namespace error : Namespace prefix addthis for userid on a is not defined in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): addthis:userid="addthis"></a> in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): ^ in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): namespace error : Namespace prefix addthis for usertype on a is not defined in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): addthis:usertype="company" addthis:userid="167173"></a> in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43 PHP Warning: simplexml_load_string(): ^ in /home/seriy/workspace/html_parser_bench/php-tidy/tidy_simplexml.php on line 43
и т.п. Причем остальные парсеры с этим нормально справляются как то.
Скачал архив с «Save link as» в FireFox — выдает ошибку прираспаковке, но tar вроде нормальный
$ bunzip2 html_parser_bench.tar.bz2
bunzip2: html_parser_bench.tar.bz2: trailing garbage after EOF ignored
$ bunzip2 html_parser_bench.tar.bz2
bunzip2: html_parser_bench.tar.bz2: trailing garbage after EOF ignored
Попробуйте ещё раз скачать (обновил конфиг Nginx). Возможно были проблемы с конвертацией переносов строк или ещё чем-то (хотя у меня через Save as в FF нормально распаковывал в Linux)
Отлично! Возможно для статьи пригодится ссылка на goquery: github.com/opesun/goquery
Jquery style selector engine for HTML documents, in Go.Использует exp/html для парсинга HTML, но не знаю, модифицированный или нет.
Sign up to leave a comment.
Бенчмарк HTML парсеров