Web crawler с использованием Python и Chrome

Добрый день, дорогие друзья.

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

В моей голове всплыли такие интересные штуки, как Selenium, PhantomJS, Splash и всякое подобное. Все эти штуки были мне немного втягость. Вот какие причины я выявил:

  • Дело в том, что я хотел бы писать на своем любимом питоне, потому что очень не люблю JavaScript, а это уже означает, что большая часть уже не работала бы (или пришлось их как-то склеивать, что тоже отстой).
  • Еще эти безголовые браузеры обновляются как когда.
  • Но вот Selenium очень милая штука, но я не нашел, как там отслеживать загрузку страниц, или хотя бы адекватного способа выдрать куку или задать её. Слышал, что многие любители селениума инжектят в страничку JavaScript, что для меня дико, потому что где-то полгода назад я делал сайтик, который отрывал любые JavaScript вызовы с сайта и потенциально мог определять моего паука. Мне бы очень не хотелось таких казусов. Хочется чтобы мой паук выглядел как браузер максимально точно.

Собственно, к делу. Недавно вышел Headless Chrome. Это означает, что теперь мы аж использовать хром, как кравлер (но это неточно). Однако, я не нашел нормальных утилит для использования его в качестве кравлера. Нашел только chrome-remote-interface из списка сторонних клиентов (остальные были крайне скучными и совсем непонятными на первый взгляд).

Пробежавшись по документации и готовому проекту, и убедившись что никто толком не реализовал клиент под Python, я решил сделать свой клиент.

Протокол у Chrome Remote Debug достаточно простой. Для начала нам надо запустить Chrome вот с такими параметрами:

chrome --incognito --remote-debugging-port=9222 --headless

image

Теперь у нас есть API, доступное, по адресу http://127.0.0.1:9222/json/, в котором я обнаружил такие методы как list, new, activate, version, которые используются для управления вкладками.

Также, если мы просто перейдем на http://127.0.0.1:9222/, то сможем перейти на прекрасный веб отладчик, который полностью имитирует стандартный. В нем очень удобно отслеживать как работают апишные методы хрома (окно отладки справа эмулируется внутри окна, а окно браузера — отрисовано на канвасе).

image
Собственно, перейдя на вкладку list, мы можем узнать, адрес вебсокета, с помощью которого мы сможем общаться с вкладкой.

Дальше мы подсоединяемся через вебсокет к желаемой вкладке, и общаемся с нею. Мы можем:

  • Выполнить запрос
  • Подписаться на события в вкладке

Спустя дни мучительного написания функционала для себя у меня получилась вот такая библиотека.

Что в ней есть:

  • Автоматическая подкачка последней версии протокола
  • Обертка протокола в питонистический вид
  • Синхронный и асинхронный клиент (синхронный только для отладки)
  • Надеюсь, удобная абстракция вкладок

image
Вот так выглядит прога, которая подгружает страничку и выдает длину каждого ответа на запрос:

import asyncio
import chrome_remote_interface

if __name__ == '__main__':
    class callbacks:
        async def start(tabs):
            await tabs.add()
        async def tab_start(tabs, tab):
            await tab.Page.enable()
            await tab.Network.enable()
            await tab.Page.navigate(url='http://github.com')
        async def network__loading_finished(tabs, tab, requestId, **kwargs):
            try:
                body = tabs.helpers.unpack_response_body(await tab.Network.get_response_body(requestId=requestId))
                print('body length:', len(body))
            except tabs.FailReponse as e:
                print('fail:', e)
        async def page__frame_stopped_loading(tabs, tab, **kwargs):
            print('finish')
            tabs.terminate()
        async def any(tabs, tab, callback_name, parameters):
            pass
            # print('Unknown event fired', callback_name)

    asyncio.get_event_loop().run_until_complete(chrome_remote_interface.Tabs.run('localhost', 9222, callbacks))

Тут мы используем систему колбеков. Самые интересные: start и any:

  • start(tabs, tab) — вызывается при старте.
  • any(tabs, tab, callback_name, parameters) — вызывается, в случае если событие не нашлось в списке колбеков.
  • network__response_received(tabs, tab, **kwargs) — пример библиотечного события Network.responseReceived.

Мне мой код показался достаточно элегантным, и я буду его использовать дальше, хоть протокол немного сыроват. Если кто-то хочет обсудить, то пишите сюда, в Github или мне на почту.

Однако, было одно но, из-за которого я все таки плачу. С помощью remote API нельзя производить перехват и модификацию запросов и ответов. Насколько я понял — это возможно через mojo, который позволяет использовать хром в качестве библиотеки.

Однако, я подумал, что компиляция нестабильного хрома и отсутствие Python прослойки для меня будет большим горем (сейчас есть C++ и JavaScript в процессе разработки).

Надеюсь, статья была полезной. Спасибо.

image

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 44

  • UFO just landed and posted this here
    • UFO just landed and posted this here
        +3
        Суть в том, что я ничего особо не изобрел.
        За меня все сделал Гугл, добавив headless режим в хром.
        Я взял последнюю апишку хрома: https://chromedevtools.github.io/devtools-protocol/
        Рано или поздно найдется умелец, который это нормально сделает все нативно через Mojo, и я почти уверен что все на это пересядут.
        Ну а про то, что мне не нравится в селениуме с фантомом я же написал.
        • UFO just landed and posted this here
            +1
            Мое решение Моджой быть допилено не может. Это другое решение. Это использование хрома в качестве библиотеки. Как тут быть медленным я не понимаю откровенно.
            Еще ты упомянул, что у каждой компании есть своя переделка вебкита, который тоже во многом разработка Гугла. Думаю, им очень интересно что-то новое производить и поддерживать, большие компании вообще любят что-то свое пилить, деньги на это тратить. Ага.
            Ну да, моя уверенность из пустого места пришла.
            Конечно, я понимаю, что ты хвалишь разработки Скрейпингхаба. Мне они тоже очень нравятся (и сама деятельность их), но я просто пишу, про хорошую фичу хрома, которую ты почему-то очень агришь.
            Ну правда хорошая фича.
    +3
    обидели мой любимый силениум и питончик:
    import selenium.webdriver 
    
    driver = selenium.webdriver.Firefox()
    driver.get("http://www.google.com")
    driver.get_cookies() # получаем куки
    

    WebDriverWait — используется для гибкого задание времени ожидания загрузки страниц
      0
      Я точно не помню, но там разве не для текущего домена (и урла соответственно)?
        0
        Суть в том, что в этом же примере тебе пришлось перейти на гугл, чтобы получить куки.
        А через апи и не надо как бы.
          0
          О, еще нельзя HTTPonly куки тырить
        –1
        А потом Firefox самостоятельно проапгрейдится и код перестанет работать. Нужно будет ждать апдейта модуля селениума.
        А потом ты перейдешь на другую винду и выяснится, что этот скрипт почему-то стал открывать еще и консольное окно.
        Ты в сердцах плюнешь на FireFox и перейдешь на PhantomJS и выяснится, что некоторые JavaScripts таки работают в нем по другому+ он иногда подвисает непонятно почему.
        Ты познаешь БООООЛЬ и больше никогда не захочешь иметь дело с selenium.
        +1

        Скажу страшное, но я как-то реализовал подобное на стареньком vb6. Основные роботы работают через xmlhttprequest, а когда сайт на javascript, в фоновом режиме включается IE и прогружает страницу.

          0
          упс, вам коммент ниже
          0
          Такс.
          Как понял:
          • вы использовали какой-то фетчер, который что-то прогружал
          • вы использовали IE как движок для джаваскрипта (результат его выполнения вы отправляли себе куда-то)

          Так?

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

          Вот
            0

            Да, но там тоже прогружался полноценный IE, а потом прога просто забирала DOM в переменную. Другое дело, что медленновато было.

            0
            А Scrapy не рассматривали?
            Как раз web crawler на Python, более того, весьма известный и востребованный на рынке парсинга сайтов.
              0
              Конечно, смотрел.
              Собственно Scrapy используется для того, чтобы собирать однотипные данные (это не точно, но похоже на истину).
              Ну и там джаваскрипта таки нету. В Scrapnghub (это их продукт) используют Splash, чтобы рендерить Javascript.
              Т.е. Scrapy это немного другое.
              Я думаю, что возможно внедрение хрома в тот же Scrapy по аналогии с Splash.
              Мне же желательно сделать программу, которая будет бегать по сайтику, тыкать некоторые кнопки и еще что-то там.
              Т.е. ближайшими аналогами для меня были: PhantomJS, Splash, Selenium все же.
              0
              Кстати, если нужно делать js на странице, то TamperMonkey может помочь чем-нибудь. Тут речь идет об исполнении js-кода на странице по фильтру.

              Проект открытый, поэтому может написать расширение для перехвата fetch или подобного функционала? WebWorkers с этим вполне могут работать. Уверен, что Chrome на это способен.
                0
                Как понял, вы хотите расширение использующее Service Workers для перехвата трафика (и подмены там).
                Вообще, идея имеет место для жизни, но я все же бы это делал через Mojo, наверное (но это не точно).
                +1
                PyQt, а именно QtWebEngine, наверное, и есть тот самый headless Chrome с готовым интерфейсом для Питона, нет?
                  0
                  Ну я его не использовал. Можешь расписать?
                    0
                    Взяли хромиум и сделали к нему апи для плюсов (чтобы загружать урлы, работать с html, управлять куками и т.п.). PyQt, соответственно, предоставляет возможность делать все это на питоне. Phantomjs, кстати, написан как раз поверх qt (только там управление js-ом и менее гибко чем при ручной работе с webengine).

                    Легче примеры погуглить. Вот, в частности, один старенький для получения скриншота страницы https://webscraping.com/blog/Webpage-screenshots-with-webkit/
                      0
                      Как я понял, парни из QT взяли таки вебкит, но не хром и сделали вполне ничего такой браузер, который за одно можно запустить без головы. Только я не понял пока насколько полноценный. Т.е. поддерживает ли всякие сервис воркеры, стореджи всякие разные и т.д.

                      Ну а в хроме там 2 варика:
                      Mojo: оно работает как-то напрямую с хромом, и ты можешь юзать его как библиотечку. (Могу в чем-то ошибаться, поэтому почекай ссылку)
                      Ну и второй вариант: запускаем обычный хром с флагами, чтобы мы могли с ним общаться через вебсокет, как я и сделал. Апишки тут.
                    0
                    нет. embedded chromium и headless chrome — это перпендикулярные вещи.
                      0
                      headless — это что? Браузер, только без окна и отрисовки страниц и, соответственно, накладных расходов с этим связанных. Webengine, на сколько помню, позволяет это.
                        0
                        Да, но тем не менее хром и хромиум — две разные вещи.
                        Phantomjs прекращает разработку как раз ввиду выхода хрома с headless mode.
                        headless chrome

                        В хромиум тоже впилили, так что для этих целей лишняя прослойка в виде QT вряд ли нужна:
                        headless chromium
                        Хотя не уверен, может там интерфейс поинтереснее, не знаю. Я из мира ноды, у нас все сразу по людски делается.
                          0
                          Phantomjs прекращает разработку как раз ввиду выхода хрома с headless mode.

                          А webdriver впили и на сколько адекватно?
                  0
                  Особенно порадовала подкачка
                  Автоматическая подкачка последней версии протокола

                    0
                    Ну сейчас я автоматически забираю протокол с localhost:9222/json/protocol
                    0

                    Интересная статья. Но есть вопрос к автору: ваше решение сможет обойти Distil? И ко всем: как вы решаете проблему с Distil? Спасибо

                      0
                      Такс. Distil я не использовал, и не знаю тамошние методы защиты. Мне кажется, это должно обходить примерно на том же уровне, что и селениум. Думаю, если напишешь поведение в браузере похоже на человеческое, то мож и не запалят.
                      Однако, кто-то должен это протестировать.
                        0
                        В зависимости от сайта, Distil палит селениум в лутшем случее через 30-50 запросов, в худшем через 1. Надо будет протестировать ваш вариант на досуге.
                          0
                          Ну надо повтыкать, что за трюки используются им, чтобы детектить

                          P.S. Замажь дефолтный User-Agent. Он там палевный :)
                            0
                            P.S. Если что-то не так у меня или непонятно — пиши, потому что я очень часто меняю что-то в проекте сейчас и он не сильно стабилен.
                        0
                        Ребята, подскажите пожалуйста, на чем сейчас проще всего написать бота, который будет парсить странчику и кликать когда спарсил то что ему надо? Естественно с поддерживанием хттп-запросов и чтобы подгружал javascript. Я посмотрел сплэш, но к моему сожалению я совершенно не знаю пайтон. Есть ли что-то подобное на js или php?
                          0
                          Но Splash же lua использует, вроде.
                          Ну используй PhantomJS или это (если хочешь попробовать использовать решение, которое в статье).
                          Если честно, я бы на твоем месте использовал PhantomJS, если что-то несложное или какой-нибудь кравлер на ноде.
                            0
                            Спасибо за ответ, думаю самое подходящее для меня будет разобраться в PhantomJS.
                          0
                          У меня есть таск дёргать из dev tools хрома время и график загрузки страницы… я понимаю, что к этому окну можно получить доступ через эмуляцию f12, либо через иньекцию js в само окно браузера, но вот что-то ни один найденнй метод не работает. Идеи?
                            0
                            Стопе. В том, что я написал, не эмуляция F12, там апишки, с помощью которых, ты можешь проделать все тоже самое, чтобы ты мог делать через F12.
                            Ну и ты там можешь все это проследить.
                            Говорю, запусти хром с ключом --remote-debugging-port=9222, и подключись к хрому по адресу localhost:9222, потом открой вкладку и открой инспектор.

                            Получится такая веселая чепуха:
                            image
                            0
                            Всем добрым людям привет!
                            Подскажите пжл тулзу, которая позволила бы парсить вэб страницу, но есть небольшое условие, но эти тулзы не должны требовать программирование?
                            0
                            ОК. Попробую.

                            Only users with full accounts can post comments. Log in, please.