Golang, PHP, Кинопоиск и Telegraph — Что их объединяет? +18

- такой же как Forbes, только лучше.

Периодически, чтобы не покрыться пылью, я стараюсь создавать интересные штуки, которые смогли бы облегчить чью-то жизнь. Я стремлюсь к тому, чтобы они были, полезнее, чем социальная сеть для кошек. Один из недавних примеров — Телеграм-бот, который позволяет в указанных координатах найти известные Wi-Fi-точки и посмотреть пароли к ним.

Этот раз не стал исключением и я задумал создать бота, который позволял бы с наибольшим комфортом и минимумом усилий смотреть любимые фильмы и сериалы, да еще и предоставлял контент в нескольких вариантах озвучки. Сказано — сделано. И теперь, когда железный друг человека радостно раздает пользователям их любимые шоу, я бы хотел поговорить о том, что сопутствовало созданию бота, какие проблемы вставали на моем пути и как они были решены. В первой главе я расскажу о Go глазами PHP-разработчика, во второй главе о поиске дзена для парсинга Кинопоиска, а в третьей — о недокументированной фиче Telegraph.

image

1. $alexander->useLanguage(GOLANG);


Меня зовут Александр, мне 21 год. Я занимаюсь веб-разработкой и чаще всего пишу на PHP.

Хлоп-хлоп
image

Не могу сказать, что PHP — язык мечты. У него, как и у любого другого языка есть сильные и слабые стороны. Однако, я стал замечать, что устаю от PHP — постепенно надоедает разработка на этом языке, его детские болячки вроде похожих функций, которые принимают похожие аргументы, но в разном порядке, не всегда предсказуемого поведения и, конечно, слабой типизации. Таким образом, для очередного продукта я решил использовать Golang. На момент, когда я начинал, я знал о нем вот что:

  • Строгая типизация
  • Не очень много ключевых слов
  • Горутины — это удобная параллельность из коробки
  • Говорят, что язык прост и предсказуем

Так же, я когда-то от скуки листал Golang-book. Сначала все было весьма непривычным… Ну, первые 3-5 часов. Да, вход в язык очень прост. Отсутствие магии и обилия ключевых слов, а так же, предсказуемое поведение делают свое дело — если вы уже знакомы с каким-либо языком программирования, наверняка погружение в Go не займет много времени. Тут важная ремарка: Если вы три года пишете одностраничники, и на этом опыт заканчивается, я забираю свои слова обратно. Предсказуемость языка и строгая типизация позволяют писать очень большое количество кода не компилируя бинарник для запуска и проверок. Безусловно, есть и ошибки рантайма, но после PHP это глоток свежего воздуха — понимаешь, сам ошибся, да и ошибка не очевидная.

C организацией кода в Golang все просто: «Вот тебе директория, заодно это и неймспейс, кстати. Держи все здесь». И… Это работает. Это настолько просто в разработке и поддержке, что слезы счастья наворачиваются сами собой. Если честно, я не знаю, насколько большой проект можно создать с таким подходом. Я заглядывал в репозитории нескольких крупных библиотек — выглядит вменяемо, но про поддержку рассказать не могу. Субъективно, одинаковую по размеру кодовую базу на PHP поддерживать сложнее, чем на Go.

Справедливости ради, удобная и очевидная работа с массивами (слайсами) — это не про Go:

//...
s.KeyBoard = [][]string{}
s.KeyBoard = append(s.KeyBoard, []string{})
s.KeyBoard[0] = append(s.KeyBoard[0], s.text.GetAction(locale, "view-prev"))
//...

С точки зрения Golang все выглядит логично, но с человеческой точки зрения — слегка странно. Подробнее эта тема раскрыта здесь.

Так же, для параллельной работы в Golang используются горутины (потоки), в то время, как в PHP принято использовать форки (процессы). В моем проекте не так много мест, где я смог применить горутины. Однако, там, где они используются, это выглядит настолько логично и просто, что возвращаться к форкам совсем не хочется. Так как форки — это независимые друг от друга процессы, для их общения между собой обычно используют третью сторону: Redis или Memcache. Аналогичная проблема в Golang решается с помощью каналов — части языка, которая доступна из коробки. Только вдумайтесь! Параллельная работа из коробки, да еще и с поддержкой синхронизации. Раньше мне такое даже не снилось. Я не считаю, что требую от PHP слишком многого, ведь задачи параллельной работы в современной backend-разработке — обычное дело. Так же, я не хочу сказать, что Golang является панацеей от всех проблем человечества, но после опыта разработки на PHP, решение аналогичных задач на Go кроме результата доставляет еще и удовольствие.

2. Alexander.NeedInfo()


В какой-то момент, API, которым я пользовался для получения информации о фильмах с Кинопоиска закончился.

И, судя по всему, навсегда.
image

Было принято решение писать собственный парсер Кинопоиска (ребята из команды Кинопоиска, не кидайтесь тапками, лучше сделайте публичный API).

v1 — Одинокий герой


Первая реализация была простой и в лоб — в проекте поселился одинокий PHP-скрипт, который при обращении к нему доставал из очереди адрес случайного прокси-сервера и через него отправлялся за фильмом на Кинопоиск. Сам разбор страницы происходил тоже на PHP. Из-за того, что одинокий герой не использовали куки, Кинопоиск банил (начинал показывать капчу) каждый адрес после единственного запроса, а ведь еще не все прокси-сервера были быстрыми.

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

v2 — Полноценный клиент


Следующая версия парсера представляла собой веб-сервер на Go, который по GET-запросу запускает PhantomJS с нужными параметрами и переданным ID фильма. Это работало. Мне больше не были нужны прокси-сервера, я ходил на Кинопоиск прямо со своего IP. У меня была поддержка сессий, полноценный браузер и, в целом, все было удобно. Но это было очень медленно. PhantomJS честно ждал, пока загрузится вся статика и выполнится весь необходимый JS-код. Кроме того, что это было медленно, это было очень дорого по ресурсам. На разбор одной страницы уходило 100-150mb RAM. Поводом для выстрела в голову этой версии послужила прожорливость PhantomJS и его нестабильная работа — например, его процессы не всегда завершались, оставаясь висеть в запущенных и не освобождая после себя память. Я пробовал разные версии PhantomJS, я пробовал завершать процессы за ним с помощью веб-сервера, который инициирует его запуск, но итог всегда был одним: Да, работает, но прожорливо и нестабильно, хотя, конечно, удобно.

v99


В процессе поиска Святого Грааля для парсинга Кинопоиска я сбился со счету, сколько версий разных парсеров и их модификаций я успел создать. В итоге, очередную версию я назвал девяносто девятой. Девяносто девятая версия была написана на PHP. Я использовал Guzzle (HTTP-клиент для PHP), поддерживал сессии и старался быть максимально похожим в своем поведении на браузер пользователя. От поддержки JS я отказался. Капчи, конечно, показываются, но намного реже, чем в первой версии парсера и, в принципе, этот вариант можно назвать комфортным. На этой версии я и остановился.

Так же, мне известно, что по запросу Кинопоиск может предоставить доступ к своему API, но я не рассматривал этот вариант: даже если бы мне открыли доступ, это могло стать потенциальной точкой отказа, ведь доступ в любой момент можно отобрать.

3. Video.Publish()


После войны с Кинопоиском я оказался в ситуации, когда я был готов отдать пользователю ссылку на видео, а воспроизвести его было негде: Telegram Bot API не предоставляет удобного функционала для показа видео по ссылке, а регистрировать домен, хостить что-то кроме бота с парсером и заниматься разработкой фронта я совершенно не хотел.

Что же делать?


Будем публиковать видео где-то. Немного подумав я решил, что Телеграф может вполне сойти за «где-то». Сайт, который де-факто используется для публикации статей из Телеграм? То, что надо! Одна беда — нельзя публиковать видео по ссылке (кроме YouTube или Vimeo).

А если поискать?


Глядя на то, как легко и динамично создаются блоки на странице, а по нажатию лишь одной кнопки публикуется статья, невольно задумываешься: А как это работает? Особенно, если ищешь место для публикации контента. Я решил это узнать.

И что же я увидел?
[{
    "tag": "p",
    "children": ["Story"]
  }, {
    "tag": "p",
    "children": [{
        "tag": "br"
      }
    ]
  }, {
    "tag": "figure",
    "children": [{
        "tag": "div",
        "attrs": {
          "class": "figure_wrapper"
        },
        "children": [{
            "tag": "img",
            "attrs": {
              "src": "/file/a2e8087fbc53679c14fa1.jpg"
            }
          }
        ]
      }, {
        "tag": "figcaption",
        "children": ["Pff"]
      }
    ]
  }, {
    "tag": "p",
    "children": [{
        "tag": "br"
      }
    ]
  }
]

POST-запрос на публикацию содержит JSON, который подозрительно похож на HTML-разметку. А давайте попробуем добавить тег video, согласно структуре, которую имеем? А давайте. Немного терпения и получаем такую…

Структуру
[{
    "tag": "p",
    "children": ["Story"]
  }, {
    "tag": "p",
    "children": [{
        "tag": "br"
      }
    ]
  }, {
    "tag": "figure",
    "children": [{
        "tag": "div",
        "attrs": {
          "class": "figure_wrapper"
        },
        "children": [{
            "tag": "img",
            "attrs": {
              "src": "/file/a2e8087fbc53679c14fa1.jpg"
            }
          }
        ]
      }, {
        "tag": "figcaption",
        "children": ["Pff"]
      }
    ]
  }, {
    "tag": "p",
    "children": [{
        "tag": "video",
        "attrs": {
          "src": "https://www.w3schools.com/html/mov_bbb.mp4"
        }
      }
    ]
  }
]

Если выполнить POST-запрос на редактирование с приведенной выше структурой, то, в публикацию добавится произвольное видео по ссылке. То, что надо.

Не тут то было


Все работает и с этим проблем нет. Беда в том, что большинство атрибутов не поддерживаются, а это значит, что о субтитрах, или, например, постере для видео можно забыть. То есть, получилось решение из разряда «скажи спасибо, что вообще есть». Недолго думая, я решил использовать XSS для того, чтобы иметь возможность настраивать плеер. Наверное, где-то в этом месте нормальная разработка заканчивается, но отступать было некуда: нужно было организовать публикацию видео. Я пробовал разные варианты внедрения стороннего кода в страницу, даже через картинку, но все было тщетно и Телеграф героически выстоял. Впрочем, я и не эксперт в области ИБ. Возможно, если бы я потратил больше времени, то нашел рабочий вариант XSS для Телеграф, который я бы использовал исключительно для кастомизации плеера, однако, я оставил эту затею. Я пробовал еще несколько площадок для публикации своего контента, но везде чего-то не хватало или что-то не работало. Таким образом, я все-таки реализовывал плеер для видео на своей стороне…

Впрочем, это уже совсем другая история
image


P.S. Если эту статью читают разработчики Телеграф: Пожалуйста, добавьте публикацию видео по ссылке в интерфейс, раз такой функционал доступен.

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (38):

  1. alex-1917
    /#10528690

    А готовый код когда ждать, халявщики негодуе…

    • Narrator69
      /#10528846

      Если речь об исходниках парсера, то позже оформлю и выложу

      • alex-1917
        /#10529048

        Хотя бы одна положительная новость в понедельник!

  2. xobotyi
    /#10528710

    Меня сейчас гоферы заминусят, но, если честно, пытался сделать заход в сторону GO — не вышло, обливаясь слезами я побежал обнимать свой PHP 7.1…
    В GO все вроде бы круто, статическая типизация, рутины и прочее, но этот язык упорно превращает работу с массивами в кромешный ад. Да, я понимаю что это, в общем-то, плата за статичность, но, ведь, задумываясь, в большинстве случаев веб — это про массивы.

    • therhino
      /#10528748

      Согласен о массивах. Но, все-таки веб — это про конкатенацию строк :))

      • skoder
        /#10528916

        Собирать строки в массив, а потом делать его конкатенацию. Обычно так)

    • avost
      /#10528758

      большинстве случаев веб — это про массивы

      (икая от удивления) правда?!? Хорошо, что у меня какой-то другой веб!

      • xobotyi
        /#10528962

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

        • arvitaly
          /#10529664 / -1

          Строгая типизация предполагает, что не должно существовать абстрактных массивов. В Go это скорее объекты для размещения в памяти. Создавайте собственные коллекции. Мало того, не нужны все методы для любой коллекции, создавайте/наследуйте только действительно необходимые этой коллекции методы.
          Для работы с базой есть 2 подхода: ORM или генерация кода.

          P.S. Да если уж приспичило, в конце концов, написать wrapper для map дело пяти минут, да и понаписано их уже прилично.

        • OnYourLips
          /#10529728

          Модели — типизированные объекты.
          В контроллеры приходят типизированные объекты-запросы.
          По шине бегают типизированные объекты-DTO.
          Запросы — тоже многоуровневые объекты.
          Все это с валидацией и 100% статической типизацией.

          И это делает приложение более стабильным, менее забагованным, и на множество порядков более поддерживаемым.

        • avost
          /#10535316

          Да очень просто пишу — не используя похапе :) (ой, щас религиозные фанатики налетят). Я, конечно, понимаю, что если у вас в руках молоток, то всё на свете кажется гвоздём похапе, то всё на свете кажется массивом. Но на самом деле это не так. Собственно, это даже с похапе не так. Я на нём последний раз, конечно, аж лет 10 назад писал, но даже тогда не было необходимости в низкоуровневой манипуляцией с массивами. Не думаю, что за эти годы там всё поломали.
          Ибо, ну, серьёзно, что вы такое делаете, что вам приходится постоянно что-то делать с массивами? Человече, на дворе скоро будет заканчиваться второе десятилетие двадцать первого века, принципы ООП и ООД придумали чёрт-те сколько лет назад, а вы всё с массивами. Оно, конечно, можно и так, но зачем?
          ЗЫ. Однажды давно, во времена оны, два кадра в конторе, где я тогда работал, решили сделать специфическую подсистему разрабатываемого продукта. И мыслили они так же массивами. И сделали всё на массивах. После чего решили, что их программерская квалификация ускакала до небес и ушли в другую контору с повышением. А мы потом полезли в их систему… Поразительно, что она работала, но там был такой ужос-ужос с постоянными копированиями этих самых массивов из одного места в другое, вырезанием из них каких-то подмассивов, слайсов и необходимостью помнить что за хрень лежит в пятнадцатом элементе массива params… В общем, я никогда не встречал более неподдерживаемой системы. В неё немного потыркали палочкой и… всю полностью выбросили на свалку. :) И, в качестве бальзама для пехапешников — они умудрились сделать это не на пхп, а на вполне оошной яве :)

          • anjensan
            /#10535372

            ООП и ООД придумали чёрт-те сколько лет назад, а вы всё с массивами
            А не развернете ли мысль, как использование ООП/ООД избавляет от необходимости работы с массивами/векторами/списками/слайсами/тп (в разных ЯП по-разному)?

  3. anador
    /#10528724

    Да, недокументированные фичи очень часто оказываются полезными. Причем зачастую создается ощущение, что разработчики начали их введение, но на полпути решили «позабыть» про них

  4. SirEdvin
    /#10528752

    C организацией кода в Golang все просто: «Вот тебе директория, заодно это и неймспейс, кстати. Держи все здесь». И… Это работает. Это настолько просто в разработке и поддержке, что слезы счастья наворачиваются сами собой.

    Я, кстати, не понимаю, чем всем нравится такой вариант. То есть я понимаю преимущества по сравнению с c++, но причин отсутствия менеджера зависимостей и какого-то формата пакетов и так и не понимаю. И не то, что бы это сложно для go, тот же dep есть и вполне работает, вот только не все используют его.

    • iliyaisd
      /#10528918

      Что вы подразумеваете под менеджером зависимостей?
      По факту, все инклюды в Go сводятся к двум кейсам: либо у вас какой-то частично обособленный кусок проекта лежит в подпапке, и единственная точка пересечения у вас с ним — это канал и вызов одной-двух публичных процедур. Важным является то, что вы почти безболезненно можете это дело вынести в отдельный сервис, когда его понадобится крутить на отдельной машине. Либо (что тоже самое) это либа, загружаемая из VCS репозитория.
      Я кстати про инклюды вообще не думаю, у меня Gogland, он сам за меня всё что надо добавляет, стоит только начать писать например sqlx.Get(, и sqlx уже добавился.

      • SirEdvin
        /#10529084

        Это когда каждая дополнительная библиотека выглядит все-таки как нормальная библиотека с версией, которую где-то можно узнать, а еще их как-то двигать.


        Что-то похожее предлагает dep, вот только проблема в том, что далеко не все даже релизами пользуются. Например, с какого-то перепугу ими перестал пользоваться moby.


        Иными словами, позволив разработчикам творить фигню и вообще не парится о версионности каких-то пакетов авторы Go обрекли нас на версионность по коммитам. Очень сочувствую тем ребятам, которым это нравится.

        • JekaMas
          /#10529136

          пробовали разное в большом проекте. в итоге glide пока наиболее стабильное решение, простое и удобное. порядка 2х лет в продакшене на примерно 60 микросервисов работает без нареканий.

          • SirEdvin
            /#10529144

            Как уже говорили в чатике телеграма: к сожалению, неважно, насколько вам нравится glide, dep уже стал почти официальным менеджером зависимостей, так что с glide пора валить и тем более не начинать на нем проекты. Даже автор glide так говорит в readme.

            • JekaMas
              /#10529170

              Это так, но есть старое-доброе «работает — не трожь», и хотелось бы хотя бы полгодика посмотреть на dep после prod release перед тем, как затевать переходы.
              Пока есть ряд опасений насчет него, особенно касаемо политики разработчиков golang «ты не хочешь эту фичу».

    • FyvaOldj
      /#10529788

      Менеджеры есть. И полно. Начиная с примитивного встроенного go get.


      Вы имеете ввиду что среди них нет некого и крутого и стандартного одновременно?


      Если вам для своей разработки — выберите какой нибудь из имеющихся и используйте

      • SirEdvin
        /#10529792

        Я имел ввиду, что среди них нет стандарта. Нет стандарта, а значит все положили болт на какую либо версионность. Разумеется, далеко не все, но довольно приличное количество людей.

  5. KosToZyB
    /#10528950

    Горутины — это удобная параллельность из коробки

    тут слово параллельность лучше заменить на конкурентность, достаточно разные вещи.

  6. hav0k
    /#10528952

    Добрый день. На выходных востанавливал работоспособность гема, зашёл на хабр а тут такое. Через апи нет таких проблем с парсингом, алгоритм подписи можете подсмотреть в коде(ребята из команды Кинопоиска, не кидайтесь тапками, лучше сделайте публичный API).

    • Narrator69
      /#10528982

      Я действительно находил решения для работы с их мобильным API (возможно, именно, ваше). Однако, я отказался от этого пути в пользу парсинга сайта и вот почему:

      • В случае изменений на API, надо отлаживать трафик приложения, трафик сайта отладить проще
      • В случае изменений на API, доступ к информации, пропадет полностью, в то время, как при изменении сайта парсер может не найти какой-то информации, но большую часть разберет

      • hav0k
        /#10529014

        Не спорю, что всё не официально и работает на честном слове, но все-таки изменить быстро АПИ они не смогут, иначе старые версии надо принудительно обновлять, а для этого нужно время. Если поддерживать и следить за новыми версиями приложениями, то будет работать относительно стабильно.

        Я немного забросил этот гем т.к. не было нужды в нём, но на выходных решил сделать. Пока все тесты проходят(travis дергает каждые сутки), теперь я буду знать когда лавочку прикроют :).

        • esemi
          /#10529268 / +1

          Помню, когда встала задача слить БД кипоноиска я посматривал на то самое уже закрытое неофициально апи (оно кстати переродилось в аналогичный вашему модуль для пхп) и решил парсить сам. Как показало время — решение было верное.
          Задавал этот вопрос и владельцу старого апи, задам и вам — не проще ли спарсить сразу всю БД и актуализировать её в фоне, а запросы собственно делать к своим данным?
          Это будет и надёжнее и быстрее, ну и разделить ответственность позволит. Судя по запросам в вашем геме там на всё про всё выйдет 2-3кк запросов к КП для начала ну и на актуализацию сколько то, в зависимости от периодичности.

          • hav0k
            /#10530736

            Лишних данных не бывает, надо делать отдельный сервис.

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

  7. bro-dev
    /#10528986

    Для ноды есть zombieJS аналог газла тока еще более глубокий, он даже js выполняет на странице, без запуска хеадлес браузеров.
    А вообще из всех аналогов запускающих браузер мне больше понравился puppeteer, он специальзируется исключительно на хроме, из плюсов в нем поддержка всех видов проксей, с другими я очень много возился чтобы хоть какие то запустить.
    Да еще хотелось бы взглянуть на вашу очередь проксей, и вопрос к сообществу есть ли какие то готовые менеджеры таких очередей, чтобы загружаешь и оно само все проверяет и выдает тока самые актуальные которые недавно проверялись.
    Сейчас в своем столкнулся с тем что, анонимность прокси невозможно проверить если она не работает для российских ип, так как она проверяется тока направляя на себя. Тоесть через неё можно работать но до меня она не конектит.

    • pbatanov
      /#10530806

      Только зобми и есть безголовый браузер (https://github.com/assaf/zombie), поэтому он и зомби, я полагаю )

  8. noize
    /#10529208

    Golang хорош ровно до тех пор, пока на нём не приходится писать REST API(по моему мнению). Вот где начинается дикая жесть, попытки городить костыли чтобы обойти ограничения языка.
    Хотя сам язык мне нравится, писать на нём удобно и приятно. Но дико не хватает дженериков и enum'ов

    • KirEv
      /#10529280 / +1

      странное дело, удалось несколько REST-сервисов написать, для начало «ради фана», в результате понравилось, правда без фреймворков, брал стандартные пакеты, за исключением для работы mysql…

      какие ограничения имеются в виду? Интересно узнать

      • noize
        /#10529390

        Под каждый ресурс необходимо генерить свой type struct. Например, если есть объекты user(s), record(s), etc, под каждый из них по-хорошему должен иметься

        type User struct{}
        type Record struct{}

        Потом, когда нам приходит запрос на вход(POST, PUT), мы должны валидировать эти запросы. Для этого, по-хорошему, входящие JSON-структуры нужно засовывать во что-то типа map[string]interface{}, чтобы работать с сырыми данными. И вот в этом случае мы приходим к пакету reflect, с его крутыми возможностями, но, в то же время, с дичайшим оверхедом в коде, т.к. приходится вручную ползать по этим срезам с интерфейсами и смотреть, что же мы получили на входе.

        • FyvaOldj
          /#10529820

          Это язык со статической типизацией — да конечно делать структуры.


          Рефлект вам не нравится? Так ведь языки с динамической типизацией так же примерно как рефлект и делают. Причем все. Вообще все.


          Попробуйте RawMessage из стандартного пакета encoding/json, может в вашем случае поможет

          • noize
            /#10529888 / +1

            reflect замечателен, дело не в этом. Дело в том, что приходится городить слишком много ручного кода для обработки кортежа интерфейсов.

            • FyvaOldj
              /#10531356

              Если использовать паттерны из динамических языков в языках статических — да.

              Разумеется динамические языки требуют меньше кода в таких ситуациях, потому они и широко распространены. Но статические требуют меньше проверок.

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

        • KirEv
          /#10530996

          обычно для валидации входящих делаю unmarshal для json, и если json размаршился валидно, проверяю нужные элементы структуры… емейл должен быть емейлом, user.username не должен быть пустым и т.п.,

          тоже самое с REST-api на php: данные получил, корректность полученных данных проверил, не вижу особой разницы в подходе… можно конечно валидаторы внедрять, типо:

          bodyStruct := struct {
          FirstField string `json:"first_field" validate:"string,require"`
          SecondField string `json:"second_field" validate:"int"`
          }{}
          
          bodyByte, _ := ioutil.ReadAll(request.Body)
          json.Unmarshal(bodyByte, &bodyStruct)
          validate(&bodyStruct)
          


          и паниковать если валидация не проходит, или errors.New() выплевывать… все ограничивается фантазией :)

          структуры\классы в любом случае удобней ассоциативных массивов, и в ИДЕ сразу выплевывает возможные свойства, и меньше возможности сделать опечатку и дебажить потом целый день «почему юсернейм не сохраняется».

  9. Big_Shark
    /#10530366

    А почему вы вообще решили парсить кинопоиск, а не работать с api themoviedb.org, где у большинства фильмов есть русское описание и постеры. Я думаю это сэкономило бы уйму времени.

    • Narrator69
      /#10530744

      Потому что у themoviedb нет рейтингов IMDb и Кинопоиска, их собственный рейтинг никому не нужен. Так же, у Кинопоиска есть информация о малоизвестных фильмах и сериалах, да и сама информация полнее.