Skip to content

varrivoda/YTComments

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 

Repository files navigation

YTComments 0.0.4

Утилита для парсинга html Ютуба и скачивания комментариев к видео. Здесь будет история написания в обратном хронологическом порядке.

Суть

Программа эмулирует поведение браузера в части загрузки комментариев. А именно, тот момент, когда мы пролистываем очередные прогруженные 20 комментов, или нажимаем "показать ответы". В этот момент делается ajax-post-запрос на эндпойнт "/youtubei/v1/next", этот запрос содержит в себе контекст текущей сессии и некий хеш-идентификатор текущей операции "пролистывания" (в YT API этот "объект пролистывания" называется continuation). Получаем ответ, который содержит собственно, очередные <=20 комментариев, а также массив этих самых continuation'ов, на каждый из которых мы тоже будем делать такой же ajax-запрос, после того как спарсим комментарии. И так пока не перестанут приходить новые continuation'ы.

Кстати, первичные "континуэйшены", а также первые 20 комментов (или меньше) мы берем из html страницы, которая изначально к нам пришла. Оттуда же берём данные для контекста сессии (поиск по "INNERTUBE_CONTEXT":"), а также api-токен "INNERTUBE_API_KEY" (но кстати почему-то всё работает и без него)

0.0.5 show transcript

Рефактор снова откладыввается: мне некогда, но вернулся сюда, потому что понадобились субтитры.

На странице при нажатии на show transcript отсылается ajax-запрос на эндпойнт "/get_transcript?prettyPrint=false", с параметрами context: {сюда идет наш INNERTUBE_CONTEXT} и params:{сюда хэш унакальной операции,который можно взять из того же DATA по JsonPath "$.engagementPanels.engagementPanelSectionListRenderer.content.continuationItemRenderer.continuationEndpoint.getTranscriptEndpoint" }

впринципе моржем использовать тот же ajaxRequest(),но название проперти отличается, не будем ломать старый метод и скопируем в новый с params вместо continuation в body запроса. Ну и эндпоинт поменяем

в ответ приходит куча ненужной инфы, нас интересует JsonPath до массива $.actions .updateEngagementPanelAction .content .transcriptRenderer .content .transcriptSearchPanelRenderer .body .transcriptSegmentListRenderer .initialSegments[]

там мы берем JsonArray и для каждого элемента берем JsonPath $.transcriptSegmentRenderer .snippet .runs[1] .text

Так. оказывается, если автор добавил разделы с таймкодами (например, "введение", "часть 1" и т.д),то к ним в ответе немного другой путь,но все они лежат в том же самом массиве. Разобъём JsonPath на 2 части - до массива и после, и вторая часть будет отличаться. итерируемся по массиву, проверяем keySet на containsKey, выводим заколовки и сырой текст. Работает!

Но теперь проект еще больше нуждается в рефакторинге: тут-то я вообще всё в main() накидал :/

0.0.4 Попытки оптимизировать обработку ответа

Косяков много, например сразу бросается в глаза повторный перебор элементов json, чтобы найти одельно все "reloadContinuationItemsCommand" и "appendContinuationItemsAction". Хорошо бы каждый ajax-ответ парсить только один раз. И вообще этот jsonSearch() надо упразднить, заменив его тремя узкоспециальными методами, которые будут брать данные по заранее известному пути в ответе, вместо того чтобы три и более раз делать поиск по огромному json'у в 15тыс строк. Сделаем отдельный метод, который будет вызываться один раз после того, как придёт ajax-ответ, чтобы его распарсить и наполнить всё что нам надо - actions и mutations.

Можно найти еще с десяток подобных проблем. Но на самом деле это не главное. Основная проблема производительности в том, как делаются ajax-запросы. Большое кол-во запросов (этого мы изменить не можем) и долгое ожидание ответа очень сильно вилияют перфоманс. Янаписал небольшой колхозный бенчмарк, и вот результаты по выполнению фофч-запросов при разных условиях подключения к интернету (и для сравнения результаты ping до сайта ютуба)

                    |Моб интернет   |wi-fi оптика   |оптика ethernet
ping youtube.com    |   180-900     |   125         |  на 2мс меньше..
мин время           |   294         |   171         |  - 
макс время          |   2052        |   841         |  - 
сред время          |   536         |   213         |  - 
кол-во запросов = 111

В общем, надо пойти путём многопоточности запросов, чтобы не ждать пока придет ответ, а к примеру, спустя 50мс отправлять следующий запрос в отдельном потоке, и в нем же обрабатывать ответ, который придет еще спустякакое-то время. а мы уже ктому времени 10 новых тредов создали и 10 запросов отправили,вот так.. Вообще для этого есть webFlux, но чтобы им пользоваться, надо сперва переехать на Спринг. Ничего против я не имею, особенноесли в будущем это будет сервисом.

Видимо, для этого надо еще довольно много порефакторить. Например, вынести запрос с параметрами в отдельный класс, чтобы webFlux создавал его инстансы. Возможно даже начать новый Спринг-бутовый проект?

на данный момент я закомментил вызов методов sortComments() и printComments().

Чтобы прочитать комментарии, их надо раскомментировать обратно

Надо бы порефакторить и подготовить побольше бинов, но вот встал такой интересный вопрос: Вот у нас цикл while(continuations), и в каждой итерации мы делаем ajaxRequest(), но перед этим делаем continuations.remove() Вопрос в том, если axajRequest() станет многопоточным - сможем ли мы адкеватно итерироваться по continuations? На вервый взгляд кажется, что в данном виде нет. Но надо бы поисследовать. Но исследовать в таком виде сложно, потому что по прежнему гоняем jsonы и ищем по ключам,довольно сложно все это удержать в голове. Надо сделать как можно больше абстракций. Самое простое - Continuation....ладно,это всё еще надо подумать.

0.0.3 Улучшение кода

На даннный момент скорость оставляет желать лучшего :/ Ёще бы - гоняем туда-сюда json'ы по нескольку раз. По-хорошему, надо выделить сразу все константы из html и первоначальные данные, оптимизировать поиск по json (особенно ответ на ajax-запрос парсится много раз), потом переписать всё на Мапы, но начнём с другого. Чтобы было удобнее улучшать код, сперва порефакторим. Очень хочется, раз уж пишем новый класс, написать всё сразу правильно и оптимизированно, но надо быть последовательными, и не сломать код, пока не написаны тесты. Так что, скрепя сердце, вооружимся Ctrl+C и Ctrl+V...., ограничась небольшими модификациями.

Вместо статического main сделаем "умный" объект Downloader, и начнем рефакторить с двух концов.

На конце Downloader'а сделаем дата-класс Comment. Поля нам уже известны - пoрядковый номер в нашем алгоритме (который выдает их по новизне, вроде бы), имя автора, текст комментария, isReply и commentId. в итерациях по commentEntityPayload будем добавлять в итоговый лист новые объекты с заполненными полями. На начале - переместим все подготовительные операции в конструктор, да передадим в него произвольный url. Экстрактим методы, сложное пока не трогаем. Переместим кое-что кое-куда. Добавим таймер в main().

28 секундна видео с 500 комментариями, много! будем оптимизировать.

0.0.2 Группировка по ответам, читаемый вывод

В исходном скрипте не реализована группировка, то есть если коммент вляется ответом, то непонятно, на что конкретно это ответ. Строго говоря, есть 2 вида "ответов" - список ответов к комментарию верхнего уровня, и ответ пользователю с обращернием к нему по имени в тексте сообщения. Пока поработаем только с первым типом ответов. В выводе есть только boolean reply = true и айдишник коммента, который состоит из двух частей. Попробуем проанализировать айдишники, надо составить таблицу

|id оригинала               | id ответа                                         | текст для поиска
+---------------------------+---------------------------------------------------+-------------------------------------
|Ugz25cqFRKgIIVYpzZp4AaABAg |                                                   |Земля плоская! Электричества не существует!
|                           |Ugz25cqFRKgIIVYpzZp4AaABAg.A547Lcuvd9AA548QuyCbBn  |Три слона уже устали
|                           |Ugz25cqFRKgIIVYpzZp4AaABAg.A547Lcuvd9AA549Chp7qJJ  |Настоящий бог Колбас!

|UgwSZa9HUSBkMsloDBh4AaABAg |                                                   |Тргда может бахнем ?
|                           |UgwSZa9HUSBkMsloDBh4AaABAg.A544MKu4LeBA549aCz_x0K  |Может и бахнем , но позже

|UgyhTnBOZUlAKdKPKhV4AaABAg |                                                   |Посмотрим какой это фейк, когда бахнут
|                           |UgyhTnBOZUlAKdKPKhV4AaABAg.A543zZxY-FlA54ErR7cyE2  |Не бахнут,ато фейк расскроется

Ну, тут всё очевидно: root-комментарий имеет короткий id, а ответы на него имеют такой же id, но с добавлением точки и дополнительного айдишника. Напрашивается отличный простой способ отобразить ветки ответов. Добавим в наш вывод поля reply и id, и отсортируем по ним конечный список, для этого напишем анонимный компаратор по двум пропертям - первая часть айдишника , и порядковый номер в изначальном списке выдачи.

YTComments 0.0.1

Утилита для парсинга html Ютуба и скачивания комментариев к видео. Переписываю аналогичный код на Питоне, пока всё подряд накидываю в Мейне, потом порефакторю. В перспективе будет сервис.

Алгоритм

Сначала получаем html, оттуда берем конфигурацию, часть которой будем использовать в ajax-запросах

находим эндпойнты, характерные для прокрутки и дальнейшей подгрузки, для каждого (они называются continuations) делаем ajax-запрос и парсим ответ. Если в ответе находим другие эндпойнты для следующих ajax-запросов, их тоже добавляем в список continuations

Вроде работает, но количество выданных комментариев немного совпадает со счетчиком, который отображает ЮТ над блоклм комментов. При ручном подсчете вышло расхождение в 1 коммент, так что скорее всего дело в скрытых или удаленных модератором. Для моих целей это хороший результат.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages