Скрапинг Avito без headless-браузера

  • Tutorial

Недавно на хабре вышла статья Скрапинг современных веб-сайтов без headless-браузеров, и в комментариях было высказано мнение, что без headless-браузера не выйдет получить номер телефона из объявления на "авито" или "юле". Хочу это опровергнуть, ниже скрипт на python размером менее 100 строк кода, который успешно парсит "авито"

Я не являюсь специалистом по "парсингу" сайтов и это не моя работа, но не редки случаи, когда для решения моих рабочих, и не только задач, приходится это делать. Например необходимо получить баланс лицевого счета в каком-то сервисе(мобильные операторы), который не имеет для этого API или, что совсем печально, список доменов у регистратора (ещё один), который так-же не имеет API.

Как и в статье, пара комментариев из которой побудили меня написать этот пост, я тоже использую Python и библиотеку requests. Если не удается найти "внутренний" API , то приходится подключать библиотеку BeautifulSoup. Но тут всё оказалось намного проще.

Если открыть "полную" версию сайта https://avito.ru, и попытаться скопировать номер телефона, то станет понятно, что номер телефона на сайте не написан, а нарисован. Но в мобильной версии сайта, номер отдается текстом. Это можно проверить, если в инструментах разработчика в браузере посмотреть ответы при нажатии на кнопку "Позвонить".

Я не буду детально разбирать свой скрипт, в коде достаточно комментариев, чтоб понять что и на каком этапе происходит. Если кратко, то используется мобильная версия сайта, объявляются переменные для поиска по сайту, а так-же две переменные "key" и "cookie", о них далее подробнее, потом идет процесс получения куки путем открытия главной страницы, далее запускается цикл, которые собирает id всех объявлений проходя по всем страницам. После того, как получены все объявления вторым циклом проходим по ним и получаем интересующую нас информацию.

Скриншот работы скрипта:

Всё так легко выглядит, т.к. были найдены нужные API. По сути данный скрипт похож на такой-же будь в нем применены официальные API. Я старался не добавлять функции и не проверять ответы на корректность или обрабатывать исключения, это ведь демонстрация метода, а не боевой инструмент. На мой взгляд так понятнее. Хотя несколько проверок и обработок там всё-же есть. Так-же я старался уместить скрипт в 100 строк кода.

По поводу переменных "key" и "cookie", key как я понял статичен, он легко гуглится, то-есть не генерируется на ходу. cookie же я использовал, как простой "антиблок", как оказалось вердикт, что мой IP заблокирован на самом деле не является правдой, достаточно подсунуть свежие куки и "парсинг" продолжается.

Если будет интересно, я подробнее расскажу, как я искал API или могу подобный пример написать и для "юла".

Сам скрипт

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

More

Comments 45

    –1

    Вангую, что вас заминусуют.

      +3
      Как же всё-таки много людей которые парсят Авито.
      Интересно почему Авито до сих пор не сделает нормальный API?
        +2
        Чтобы продавать данные?
          +1
          А можно узнать? Зачем вам парсить авито?
            +1
            Например, чтобы находить товары выставленные по цене ниже рынка как только они появляются. Бывает, что человек не знает сколько реально стоит товар и он проадёт его по заниженной стоимости.
              +1
              То есть в личных интересах, а пользователи, которые при должных удобных инструментах превратятся в банальных перекупов. Нет, апи для этого не нужно открывать. Апи нужно для увеличение общественного блага, а не частного.
                0

                Так там же есть подписка на поиск?

                  0
                  Он срабатывает раз через раз, а точнее раз в сутки. Это совершенно бестолковый инструмент. Интересные лоты уходят за часы и даже минуты.
                  Я не понимаю, почему Авито не продает платную подписку на push-уведомления по интересующим поисковым запросам. И денег бы заработали, и нагрузку на серверы уменьшили от пассеров. У меня, например, по нескольким запросам раз в 3 минуты обновление стоит. А таких умников на Россию ни 10 и не 10000.
                    0

                    А ну да, если нужно часто, то нет вариантов. Если таких "умников" много, то не уверен что они будут монетизироваться.
                    Парсеры у них все равно будут, потому что не все мониторят. Много просто данные собирают — телефоны и т.д.

                      0

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

                +1
                Для своего поиска. Мне, к примеру, не нравится поиск авито (либо я не знаю, как добавлять минус слова).
                0
                А ещё очень хочется получить агрегатор: авто.ру+дром+авито
                  0

                  Для перекупов авто есть свой котел.)

              0
              Все это дело успешно работает ровно до того момента пока целевой сайт не спрячет свои api за какую-нибудь продвинутую bot protection по типу cloudflare. И вот тогда то все внезапно вспоминают про headless браузеры.
                0
                Bot protection по типу cloudflare, это просто «дырка», тот же пайтон-реквест пропускает на ура (при жёсткой фильтрации, но не под «под атакой»). Кроме этого, каптча обходится (не знаю насколько легко) даже новая. Поэтому, если приводить примеры, то точно не клаудфлер, пишу как их клиенты более семи лет (план про, не бесплатный).
                  0
                  Не знаю, не знаю. Есть отдельные сайты, стоящие за cloudflare, которые выдают js challenge почти со 100% вероятностью если используется не браузер. И это точно не under attack mode.
                  0
                  Гораздо сложнее обходится incapsula. Чтобы её обойти надо патчить headless браузеры.
                    0
                    А можно пример такого сайта?
                      0
                        0
                        Access denied
                        Error 16
                        www.bizjournals.com
                          0
                          вот, бот, ты и попался

                          они по ip обрезают доступ, по прокси работает
                            0
                            сайт режет соединения из россии? Из-под тора получилось зайти, со своего компа нет
                              0
                              Да. Из Европы и Штатов сайт доступен. Но если вы натравите на него Selenium вы обламаетесь )
                            +1
                            Просто сайт который не пропускает людей не пропустит и ботов, 100% защита ;)
                            Та же ошибка.
                              0
                              в Египте околоправительственные ресурсы так и закрыты тупо только для своих ip.
                        0
                        Зачем сразу патчить? Может быть не забывать юзерагент по дефолту палевный убрать?
                          0
                          Там дело не в User-Agent, а в названиях переменных JS, которые отличаются в Selenium, например. См. ответы в этом треде: stackoverflow.com/questions/33225947/can-a-website-detect-when-you-are-using-selenium-with-chromedriver
                            0
                            Какие-то глобальные переменные селениума, которые всегда доступны для чтения сайтом?
                              0

                              Там инжектится JS, который всё это
                              дело проверяет.

                                0
                                Не селениумом единым, на puppeteer думаю такого нет
                                  0
                                  C puppeteer попроще, да. Почти всегда достаточно user agent корректный прописать. Но для особо хитрых сайтов приходится все же использовать связку selenium + firefox с запретом на разглашение некоторых вещей, которые он в headless режиме любит рассказывать сайтам.
                                    0
                                    не знаю чего он там любит, помоему все это регулируется тоже.
                                      0
                                      Да я и не спорю. Через profile options пару-тройку настроек надо поменять и все. Благо к firefox внимание со стороны анти-бот защит менее серьезное.
                      +1
                      Открыл свой проект, которому почти год — ключик данный захардкожен, и до сих пор совпадает. Помню что определил его случайно, просматривая xhr-запросы. Это даже как-то странно, что он не изменился со временем.
                        0
                        Странно, что он вообще используется, смысла в нем получается никакого нет.
                        +2

                        Ещё лет пять назад парсил авито, телефоны с картинок распознавал с помощью tesseract. Чистый питон.

                          0
                          Не первый год паршу авито, софтец самописный, недавно, буквально месяц назад оно ввело данные меры при получении номера телефона, раньше достаточно было сделать запрос формата https://m.avito.ru/api/1/items/2053289182/phone?key=af0deccbgcgidddjgnvljitntccdduijhdinfgjgfjir
                          Без кукиз, просто запрос, и авито выдавало телефон объявления Id которого указан после /items/

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

                          Сейчас надо авторизоваться, чтобы получить куку и в пост запросе подсунуть ее данные, вот пример на видео: https://www.youtube.com/watch?v=kxcu_b2UaVU
                            0
                            И правильно. Меня достал спам типа «Александр, увидел ваше объявление %деткий стульчик%, по почте отправите? Вот тут форма» Форма мошенничества, с которой крайне сложно бороться, так как разводят по контактам вне платформы. Лично писал и просил ввести информацию о недопущении общения в мессенджерах вне площадки. то и дело фишинг кидают. Услышали.
                            0

                            Очень интересно, расскажи как получить свежие cookie?

                              0
                              В ручную можно вот так:
                              Открываешь вкладку инкогнито в браузере, нажимаешь F12, переключаешь вид на мобильный(верхний левый угол в инструментах), открываешь авито-объявление, в инструментах выбираешь вкладку Network, из множества запросов находишь любой до авито, там будет куча запросов до яндекс/гугл аналитик, js библиотекам и всему прочему, нужен только до авито. Далее в выбранном запросе выбираешь вкладку Headers, разворачиваешь блок Request Headers и копируешь мега-длинную строчку cookie.
                                0
                                я переписал код автора на с#, естественно авито банит мой ip. Автор говорит что обновление куки поможет решить проблему, как это сделать средствами с# с использованием Leaf.xNet, нашел такой кусок кода, не могу разобраться в нем
                                request.Cookies = new CookieStorage();
                                string xf_session = null;
                                using (request)
                                {
                                request.Get("https://m.avito.ru");
                                var cookies = request.Cookies.GetCookies("https://m.avito.ru");
                                foreach (Cookie cookie in cookies)
                                {
                                // Перебор всех кук в лог
                                project.SendInfoToLog(String.Format("Name: {0} ::: Value: {1}", cookie.Name, cookie.Value), true);

                                // Получаем в переменную куку xf_session
                                if (cookie.Name == "xf_session") xf_session = cookie.Value;
                                }
                                }
                                  0
                                  Мой код был написан как пример того, что номера телефонов получить можно, причем текстом.
                                  Это ни в коем случае не боевой инструмент. Я сам несколько раз ловил бан во время отладки кода. Подмена куки действительно помогала, этого было достаточно для отладки.
                                    0
                                    мне помогает выбор соотношений куки+заголовки+ip(прокси) для запросов. для этого у меня есть соответствующий класс, который генерит данные наборы и пускает в работу пока набор не забанят. в общем, там довольно нехитрая схема, но работает.
                                    коротко алгоритм такой: через определенный прокси с определнными заголовками делается первый запрос на сервер, запоминаются куки, которые используются для дальнейших запросов с этого прокси с такими заголовкоми. чем больше вариантов UserAgent'ов тем лучше. и так далее.

                                    что-то типа того:
                                    public static function getRandomOptions($referer, $useProxy = false) {
                                    		$userAgent = static::USER_AGENT[rand(0, count(static::USER_AGENT) - 1)];
                                    		$cookieFileName = 'common_cookie.txt';
                                    		$proxyOptions = [];
                                    
                                    		if ($useProxy) {
                                    			$proxyList = ProxyHelper::getProxyList();
                                    			if (count($proxyList) > 0) {
                                    				$proxy = $proxyList[rand(0, count($proxyList) - 1)];
                                    
                                    				//Привяжем файл куки к прокси-серверу и user agent'у, чтобы с разных IP шли запросы с привзанными к ним куками
                                    				$cookieFileName = md5($proxy . $userAgent) . '.txt';
                                    				$proxyOptions = [
                                    					CURLOPT_PROXY     => $proxy,
                                    					CURLOPT_PROXYTYPE => CURLPROXY_HTTP
                                    				];
                                    			}
                                    			else {
                                    				//кончились прокси, надо что-то делать
                                    			}
                                    		}
                                    
                                    		$cookieFile = Yii::getAlias('@cookies') . DIRECTORY_SEPARATOR . $cookieFileName;
                                    
                                    		$additionalHeaders = [
                                    			'Referer: ' . $referer,
                                    			'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                                    			'Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3',
                                    			'Dnt: 1',
                                    			'Connection: keep-alive',
                                    			'Cache-Control: max-age=0',
                                    			'Upgrade-Insecure-Requests: 1'
                                    		];
                                    
                                    		return [
                                    				CURLOPT_RETURNTRANSFER => true,
                                    				CURLOPT_USERAGENT      => $userAgent,
                                    				CURLOPT_REFERER        => $referer,
                                    				CURLOPT_COOKIE         => true,
                                    				CURLOPT_COOKIEJAR      => $cookieFile,
                                    				CURLOPT_COOKIEFILE     => $cookieFile,
                                    				CURLOPT_AUTOREFERER    => true,
                                    				CURLOPT_FOLLOWLOCATION => true,
                                    				CURLOPT_CONNECTTIMEOUT => 2,
                                    				CURLOPT_ENCODING       => 'gzip, deflate',
                                    				CURLOPT_MAXREDIRS      => 2,
                                    				CURLOPT_HTTPHEADER     => $additionalHeaders
                                    			] + $proxyOptions;
                                    	}
                                0

                                Ок, я понял)

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