Cheerio занял 2,9 секунды на HTML-странице 10 МБ в Node

Последнее обновление: August 19, 2026
Cheerio занял 2,9 секунды на HTML-странице 10 МБ в Node
AI-сводка
На одной машине cheerio в Node разобрал 10-мегабайтный синтетический HTML-документ и извлёк заголовки вместе с href за 2 927,89 мс. selectolax, запущенный через CPython с парсером на C-основе, выполнил то же извлечение полей за 158 мс. Отсортированный текст заголовков и хэши href совпали. Это сравнение полного стека между средами выполнения, а не вердикт по одному лишь алгоритму парсера. На странице размером 10 КБ разница составляет 2×, и никто бы этого даже не заметил. Вопрос в том, где ваши страницы находятся на этой кривой. Используйте cheerio, если вы работаете в Node, API в стиле jQuery вам удобен, а реальные страницы по размеру близки к протестированным значениям до 1 МБ.

На одной машине cheerio в Node разобрал и извлёк заголовки и href из синтетического HTML-документа объёмом 10 МБ за 2 927,89 мс. selectolax, запущенный через CPython с C-backed парсером, выполнил ту же выборку полей за 158 мс. Отсортированные тексты заголовков и хэши href совпали. Это сравнение end-to-end по разным runtime, а не изолированная оценка самого алгоритма парсера.

На странице размером 10 КБ разница — всего 2×, и никто бы этого не заметил. Вопрос в том, где именно ваши страницы находятся на этой кривой.

Что такое cheerio

cheerio — это HTML-парсер для Node с синтаксисом jQuery, и в своей экосистеме он считается стандартным выбором не просто так: 30 449 звёзд на GitHub, лицензия MIT и push в репозиторий накануне моего теста. Проверенная версия: 1.2.0.

Официальная справка: официальное введение в Cheerio.

import * as cheerio from "cheerio";
const $ = cheerio.load(html);
const titles = $("h3.title").map((_, e) => $(e).text()).get();
const hrefs = $("a").map((_, e) => $(e).attr("href")).get();

Если вы когда-либо писали на jQuery, API уже вам знаком. Именно эта привычность во многом и помогла ему выиграть в своей экосистеме.

Под капотом это не один парсер, а целый стек: htmlparser2 и parse5 для парсинга, domhandler и domutils для дерева, cheerio-select для селекторов, а также undici, encoding-sniffer и другие — 11 прямых зависимостей, которые после разрешения превращаются в 22 пакета верхнего уровня и 9,0 МиБ на диске. Эти пакеты дают возможности парсинга и определения кодировок, но одновременно увеличивают и зависимостный след. В этом обзоре проверялась работа на битом HTML, но не точность обработки кодировок и не сравнивались отдельно оба backend парсинга.

Как проводилось измерение и почему ему можно доверять

В этой базе уже был бенчмарк парсеров: пять размеров страниц от 1 КБ до 10 МБ, 50 итераций, три независимых прогона и — самое важное — контроль паритета, который хэширует извлечённый контент: отсортированные заголовки плюс отсортированные href сравниваются с эталонным парсером. Парсер, который молча пропускает часть работы, не может показать честно быстрый результат.

Чтобы добавить туда cheerio, перед любым числом пришлось сделать две проверки.

Эталон оказался там же, где и раньше? selectolax был заново запущен в той же сессии на тех же фикстурах. Его content hash совпал на 5 из 5 размеров, а p50 оказался в пределах 0,989×–1,079× от опубликованной цифры. Значит, это та же машина, что и у исходной таблицы.

cheerio выдал те же оценочные поля? Его content hash — рассчитанный в Node по тому же правилу, SHA-256 от отсортированного текста заголовков и отсортированных href — совпадал с эталоном на 5 из 5 размеров. Это подтверждает паритет именно для этих отсортированных полей на данных фикстурах, но не форму DOM, не порядок документа, не атрибуты, не нормализацию текста и не обработку ошибок.

Только после этого timing-цифры вообще имеют смысл.

Размер страницыselectolaxlxmlPyQuerycheerio (Node)cheerio vs selectolax
1 КБ0,0286 мс0,05080,04560,1147 мс4,0×
10 КБ0,1725 мс0,18020,17280,3490 мс2,0×
100 КБ1,4855 мс1,41451,40933,8399 мс2,6×
1 МБ14,97 мс15,0314,9659,37 мс4,0×
10 МБ158,10 мс165,25162,862 927,89 мс18,5×

p50 в миллисекундах, медиана из трёх прогонов. parser-bench.json. Три Python-парсера запускались в одном процессе; cheerio работал в Node 22, а это уже граница runtime, а не только библиотеки — см. ниже.

Как читать эту таблицу честно

Измеренная диаграмма результатов: время парсера в зависимости от размера страницы

Строка 1 КБ — это шум. Среди трёх Python-парсеров разброс на этом размере составил 77,6%, а отдельные прогоны сильно пересекались — например, selectolax на трёх прогонах показал от 0,0267 до 0,0404 мс. При 28 микросекундах доминируют точность таймера и планирование задач. Я бы не ранжировал ничего на 1 КБ, включая cheerio.

Середина таблицы ничем не выделяется. От 10 КБ до 1 МБ разница составляет 2×–4×. Для парсера, который обрабатывает несколько сотен страниц, это 45 мс на страницу вместо 15, и вы этого никогда не увидите.

Строка 10 МБ — не шум. Три прогона cheerio дали 2 839, 2 928 и 2 954 мс — стабильно и далеко от соседних значений. Конечный результат на 10 МБ резко выбивается из общей картины для меньших размеров. Пяти точек недостаточно, чтобы установить асимптотическую сложность или понять, какой именно слой — runtime, парсер, селектор, выделение памяти или GC — дал скачок.

Он попадает в диапазон BeautifulSoup. В опубликованном бенчмарке на той же 10 МБ фикстуре измерили ещё четыре парсера, и именно сравнение cheerio с их результатами даёт самый полезный вывод в этой статье:

Парсер (10 МБ)p50
selectolax (lexbor)159,93 мс
lxml172,93 мс
parsel231,85 мс
selectolax (modest)247,95 мс
BeautifulSoup + lxml2 261,56 мс
BeautifulSoup + html.parser2 788,75 мс
cheerio2 927,89 мс

Четыре строки на Python — это опубликованные значения из bench_parse.json; значение cheerio — из этого прогона. Эталонный парсер воспроизвёлся в пределах 0,989×–1,079× между двумя запусками, так что различия меньше примерно 8% стоит считать укладывающимися в эту погрешность — cheerio против html.parser у BeautifulSoup (разница 5%) попадает туда, а cheerio против selectolax (18×) — нет.

BeautifulSoup — это библиотека, к которой обращаются ради удобства, сознательно принимая её медлительность; именно её обычно советуют заменить в любом обсуждении производительности Python. На документе в 10 МБ cheerio находится внизу того же диапазона, а не рядом с C-backed парсерами, с которыми его обычно ставят в один ряд.

Вопрос о замене на стороне Node в этой статье остаётся открытым. Более новые альтернативы для Node не тестировались, поэтому этот результат не доказывает, что переход на другую библиотеку невозможен, и не объявляет их менее зрелыми. Он лишь показывает измеренный путь cheerio по сравнению с перечисленными Python-стеками.

Это сравнение не только библиотек, но и runtime. Миллисекунды cheerio — это Node с его JIT и сборщиком мусора; у остальных — CPython, вызывающий C-backed парсеры. Content hash доказывает, что выполнена одна и та же работа, а оба числа — это именно то, что ощущает разработчик, выбирая стек. Но никто не должен читать это как «алгоритм cheerio в 18× хуже, чем у selectolax». Это то, что произошло на этой машине в нативном runtime каждой библиотеки.

Что с окружением и настройкой

БиблиотекаПакетыДискЛицензияЗвёздыПоследний push
cheerio22 (npm)9,0 МиБMIT30 4492026-08-11
PyQuery3 (pip)20,1 МиБBSD2 3802026-07-27

Официальная справка: документация по настройке Cheerio.

metadata-snapshot.json, полученный в день написания.

npm install cheerio занял меньше двух секунд и подтянул 9,0 МиБ. Холодный импорт в отдельном прогоне-конвертере на той же машине занял 0,056 с.

11 прямых зависимостей — это много для парсера, и об этом стоит помнить, если вы проверяете дерево зависимостей: htmlparser2, parse5, parse5-htmlparser2-tree-adapter, parse5-parser-stream, domhandler, domutils, dom-serializer, cheerio-select, encoding-sniffer, undici и whatwg-mimetype. Там поставляются сразу две полноценные реализации парсера, потому что cheerio может использовать любую из них в зависимости от запроса.

Тридцать тысяч звёзд и свежий push накануне тестирования — это очень хороший сигнал о живой поддержке в этой категории.

Память и то, что делает с ней битый HTML

Потребление памяти и поведение на некорректном HTML влияют на развёртывание и обработку ошибок, поэтому здесь они измеряются отдельно.

Более широкий контекст стресс-теста есть в сравнении памяти и обработки битого HTML у десяти библиотек.

Пиковая resident memory, через /usr/bin/time -l, по одному новому процессу на каждую ячейку — нижняя граница импорта показывает, сколько библиотека стоит уже загруженная и простаивающая, а пики включают документ.

БиблиотекаRuntimeНижняя граница импортаПик на 226 КБПик на 10 МБ
html2textpython3.1418,719,971,2
pyquerypython3.1430,333,9172,5
resiliparsepython3.1420,525,1225,1
markdownifypython3.1423,928,9278,5
goose3python3.1444,152,4398,5
cheerionode2266,876,5398,5
justextpython3.1430,336,6431,2
newspaper4kpython3.1452,661,8668,5
trafilaturapython3.1452,564,8927,1
turndownnode2247,868,42947,1

memory-results.json. Базовые показатели Python и Node нельзя напрямую сравнивать между собой; интерпретатор присутствует в обоих случаях.

У cheerio самая высокая нижняя граница импорта в этой смешанной таблице — 66,8 МиБ, включая runtime Node и зависимости. Процесс достигал пика 398,5 МиБ на 10 МБ фикстуре. Остальные строки включают парсеры, конвертеры и извлекатели статей, выполняющие другую основную работу, так что используйте их как контекст по footprint процесса, а не как рейтинг производительности между равными. Строка turndown в том же runtime достигла куда более высокого пика, но она выполняет конвертацию, а не измеренный для cheerio контракт выбора заголовков и ссылок.

Битый HTML. Двенадцать документов, каждый из которых ломает ровно одну вещь — незакрытые теги, неправильная вложенность inline-элементов, атрибуты без кавычек и пробелами, лишние закрывающие теги, отсутствие <html> вообще, дублирующиеся атрибуты, обрыв документа посреди тега, битые сущности, незакрытый <script>, ложное объявление charset, комментарий с разметкой и 600 уровней вложенности — плюс два корректных control-документа сопоставимого размера, потому что «ничего не вернул» говорит о некорректности только если библиотека не молчит и на чистом документе того же размера.

cheerio выбросил исключение в 0 из 14 случаев и ни разу не вернул пустой результат, восстановив 11/22 оцениваемых маркеров на битых фикстурах (malformed-results.json). Для парсеров scorer проверяет контрольные маркеры заголовков и ссылок на одиннадцати оцениваемых malformed-документах; он не оценивает paragraph sentinel, а фикстура с незакрытым <script> исключена. Без baseline с тем же контрактом в этом разделе показатель 11/22 не является рейтингом качества. Корректный вывод такой: cheerio вернул непустой результат без исключений на всех 14 входах с битым HTML и control-документах, восстановив при этом половину оцениваемых маркеров.

Плюсы и минусы

Плюсы. Знакомый синтаксис jQuery. MIT. Живой сигнал поддержки: 30 449 звёзд и активность репозитория накануне теста. Два backend-парсера и пакеты, связанные с кодировками, присутствуют, хотя восстановление backend'ов и точность кодировок здесь не изолировались. Отсортированный hash по заголовкам и href совпал с эталоном на всех размерах фикстур.

Минусы. На документе 10 МБ в 18,5× медленнее selectolax и в 4× медленнее на 1 МБ. 11 прямых зависимостей, включая две полноценные реализации парсера. Только Node. И в документации нет намёка на размер, после которого он перестаёт быть очевидным выбором.

Кому он подойдёт, а кому нет

Используйте cheerio, если вы в Node, API в стиле jQuery для вас важен, а типичные страницы похожи на тестовые размеры вплоть до 1 МБ. Один мегабайт — это максимальная протестированная точка перед резким скачком на 10 МБ; статья не устанавливает границу между ними и не утверждает, какая доля веба находится ниже неё.

Протестируйте перед использованием, если речь о очень больших HTML-документах — например, сгенерированных отчётах, выгрузках каталога или длинных списках. Поведение XML sitemap не тестировалось. На 10 МБ HTML-файле 2,9 секунды на документ — это уже ощутимая стоимость, которая будет накапливаться.

Если вы в Python, этот тест говорит другое: selectolax, lxml и PyQuery по скорости практически равны начиная с 10 КБ (разница в пределах 0,5%–5,4%, а диапазоны прогонов пересекаются), так что выбирать стоит по API, а не по скорости. Интересное число здесь — именно отрыв cheerio от всех трёх, а не различия между ними.

Где здесь место managed API

cheerio парсит HTML, который у вас уже есть. Он не скачивает страницы, не рендерит JavaScript и не решает антибот-защиту — а на многих реальных ресурсах именно это и есть самая трудная часть.

Managed-сервис для получения, рендеринга и извлечения данных, включая наш собственный Thunderbit, находится на другой границе ответственности. Thunderbit в этом тесте не бенчмаркался. Важное различие здесь такое: парсинг уже переданного HTML с селекторами против аутсорса получения, рендеринга и извлечения. Эта статья не даёт сравнения по одной и той же метрике качества, задержки или стоимости.

Честная формулировка такая: если HTML у вас уже есть и вы знаете свои селекторы, cheerio бесплатен и приятен в использовании. Если же вы собираете страницы в масштабе или предпочитаете описывать данные, а не DOM, это уже совсем другая покупка.

Для более широкого обзора смотрите наш обзор API для web scraping, а также пул open-source scraper'ов для self-hosted решений. Если результат парсинга пойдёт в модель, статья о преобразовании HTML в Markdown в Python показывает, где теряется точность.

Попробуйте Thunderbit для извлечения веб-данных

Стоит ли использовать cheerio?

Да, в Node — когда важен удобный API и типичные документы остаются в диапазоне от небольших до 1 МБ.

Знакомство с API и текущие сигналы поддержки — вполне нормальные критерии выбора. Но бенчмарк не доказывает, что для вас существует конкретный ответ службы поддержки, и не подтверждает, что распределение размеров страниц в тесте совпадает с продакшен-корпусом.

Главное число, которое стоит запомнить, — это 10 МБ. Где-то между 1 МБ и 10 МБ стоимость cheerio перестаёт следовать за другими и начинает множиться — 4× превращается в 18,5×. Если в вашем корпусе есть такие документы, обязательно сделайте собственный бенчмарк до внедрения, потому что сама библиотека вас об этом не предупредит.

Попробуйте Thunderbit для извлечения веб-данных Get Started Free

Частые вопросы

Справедливо ли сравнивать cheerio с Python-парсерами? Это сравнение стеков, а не алгоритмов. Все четыре варианта выдали одинаковые отсортированные тексты заголовков и href по правилу хэширования на 5 из 5 размеров страниц. Это не доказывает полную эквивалентность парсеров. В timing cheerio входит поведение Node runtime, а в остальные — вызовы CPython к C-backed парсерам; сравнение описывает именно эти end-to-end решения.

Почему строка 1 КБ не ранжируется? Потому что при 28 микросекундах измерение почти полностью определяется шумом. На трёх прогонах Python-парсеры разошлись на 77,6%, а отдельные результаты перекрывались. Любое упорядочивание на таком размере было бы артефактом. Начиная с 10 КБ данные уже достаточно стабильны для чтения.

Что вызывает скачок на 10 МБ? Этот тест этого не говорит. Он лишь показывает, что скачок реален и это не шум: три прогона cheerio дали 2 839, 2 928 и 2 954 мс, и эти значения заметно отделены от остальных, тогда как разница на 1 МБ составляла 4×. Чтобы выделить причину, пришлось бы профилировать backend-парсеры cheerio отдельно, а это выходило за рамки исследования.

Сколько у него на самом деле зависимостей? 11 прямых, 22 пакета верхнего уровня после разрешения, 9,0 МиБ на диске. Две из них — полноценные реализации парсера: htmlparser2 и parse5, потому что cheerio может использовать любую. Это цена за поддержку и forgiving, и spec-compliant parsing, и об этом полезно помнить при аудите дерева зависимостей.

Что здесь не тестировалось? Тесты покрывали пиковую память процесса на одном документе 226 КБ и одном документе 10 МБ, а также набор из 14 входов с битым HTML и control-документами, где cheerio нигде не выбросил исключение, везде вернул непустой результат и восстановил 11/22 оцениваемых маркеров. Не тестировались потоковая обработка через parse5-parser-stream, корректность кодировок, восстановление, зависящее от backend'а, точка скачка производительности между 1 и 10 МБ, XML-парсинг и более новые альтернативы для Node.

Ke
Ke
Технический директор в Thunderbit | Senior Data Scientist и эксперт по ML Имея почти десятилетний опыт в машинном обучении и data science, Кэ Шэнь — выпускник Колумбийского университета и бывший Senior Data Scientist в Walmart Labs. Обладая глубокой, признанной коллегами экспертизой в Python, R, Java и статистике, он делится проверенными на практике наблюдениями о том, как переводить сложные AI-алгоритмы из теории в production-grade архитектуру.
Содержание
Thunderbit · AI-агент для веб-данных

Извлекай данные с любой страницы за 1 клик

Нам доверяют более 250 000 пользователей
доступен бесплатный тариф
От веб-страницы к таблице
Опиши, что тебе нужно, — AI-агент Thunderbit соберет это и экспортирует в Excel, Google Sheets, Airtable или Notion. Старт бесплатный.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week