<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0">

<channel>

<title>Блог об аналитике, визуализации данных, data science и BI, заметки с тегом: selenium</title>
<link>http://test.leftjoin.ru/tags/selenium/</link>
<description></description>
<generator>E2 (v3365; Aegea)</generator>

<item>
<title>Робот для автоматизированного просмотра Instagram на Python и Selenium</title>
<guid isPermaLink="false">92</guid>
<link>http://test.leftjoin.ru/all/instagram-python-selenium-bot/</link>
<comments>http://test.leftjoin.ru/all/instagram-python-selenium-bot/</comments>
<description>
&lt;p class="note"&gt;Недавно мы начали вести Instagram — &lt;a href="https://www.instagram.com/leftjoin.ru/"&gt;подписывайтесь&lt;/a&gt;, чтобы не пропустить контент, которого нет в блоге и Telegram!&lt;/p&gt;
&lt;p&gt;Многие из нас ежедневно заходят в Instagram, чтобы посмотреть истории друзей и полистать ленту постов и рекомендаций. Предлагаем действенный способ сохранить своё время — напишем на Python и Selenium робота, который возьмёт на себя рутинную задачу проверки свежих новостей друзей и подсчитает число новых историй и входящих сообщений.&lt;/p&gt;
&lt;h2&gt;Авторизация в аккаунт&lt;/h2&gt;
&lt;p&gt;При переходе в браузерную версию сайта, нас встречает такое окно:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/1-22.png" width="827" height="619" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Но просто вставить логин, пароль и нажать на кнопку «Войти» недостаточно: впереди будет ещё два окна. Во-первых, предложение сохранить данные — здесь мы тактично жмём «Не сейчас». Instagram тщательно следит за каждым нашим действием и малейшие аномалии в поведении приводят к блокировке, поэтому любые предложения по сохранению данных будем на всякий случай пропускать.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/2-21.png" width="414" height="344" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Следующим препятствием будет предложение включить уведомление, которое мы тоже пропустим:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/3-19.png" width="449" height="381" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Первым делом импортируем библиотеки:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from bs4 import BeautifulSoup as bs
import time
import random&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И описываем функцию authorize — она будет принимать driver в качестве аргумента, отправлять в нужные поля логин и пароль, нажимать на кнопку «Войти», затем ждать десять секунд на загрузку страницы, нажимать на кнопку «Не сейчас», снова ждать загрузки страницы и пропускать уведомления:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def authorize(driver):
    username = 'login'
    password = 'password'
    driver.get('https://www.instagram.com')
    time.sleep(5)
    driver.find_element_by_name(&amp;quot;username&amp;quot;).send_keys(username)
    driver.find_element_by_name(&amp;quot;password&amp;quot;).send_keys(password)
    driver.execute_script(&amp;quot;document.getElementsByClassName('sqdOP  L3NKy   y3zKF     ')[0].click()&amp;quot;)
    time.sleep(10)
    driver.execute_script(&amp;quot;document.getElementsByClassName('sqdOP  L3NKy   y3zKF     ')[0].click()&amp;quot;)
    time.sleep(10)
    driver.execute_script(&amp;quot;document.getElementsByClassName('aOOlW   HoLwm ')[0].click()&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Новые сообщения&lt;/h2&gt;
&lt;p&gt;В Instagram могут прийти сообщения двух видов. В случае, если вы не подписаны на отправителя — придёт запрос на диалог. Если подписаны — придёт входящее сообщения. Оба случая обрабатываются по-разному. Число входящих сообщений можно получить с главной страницы — это число над иконкой бумажного самолётика:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-01-25--15.19.31.png" width="231" height="70" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;А число запросов можно забрать текстом заголовка h5 из раздела «Сообщения». Сперва перейдём в этот раздел и попробуем найти строку с запросами на сообщение. Затем вернёмся на главную страницу и возьмём то самое число новых сообщений.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def messages_count(driver):
    driver.get('https://www.instagram.com/direct/inbox/')
    time.sleep(2)
    inbox = bs(driver.page_source)
    try:
        queries_text = inbox.find_all('h5')[0].text
    except Exception:
        queries_text = None
    driver.get('https://www.instagram.com')
    time.sleep(2)
    content = bs(driver.page_source)
    try:
        messages_count = int(content.find_all('div', attrs={'class':'KdEwV'})[0].text)
    except Exception:
        messages_count = 0
    return queries_text, messages_count&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Подсчёт числа новых сторис&lt;/h2&gt;
&lt;p&gt;Все истории хранятся в одном блоке:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-01-25--15.02.05.png" width="637" height="201" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Это список с одинаковым классом, но в каждом элементе списка лежит ещё один div-блок. У новых историй это класс eebAO  h_uhZ, у просмотренных — eebAO.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-01-25--15.03.48.png" width="200" height="132" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Ещё есть такая кнопка, которая показывает следующую пачку историй:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/--2021-01-25--15.05.14.png" width="269" height="135" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;При этом Instagram динамически прогружает код страницы, и в нём не найти те элементы, которые вы не видите своими глазами. Поэтому мы возьмём первые 8 видимых новых историй, добавим в список, нажмём на кнопку «Показать следующие истории» и будем продолжать так, пока кнопка ещё отображается. А затем подсчитаем число уникальных элементов, чтобы избежать возможных дубликатов.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def get_stories_count(driver):
    stories_divs = []
    scroll = True
    while scroll:
        try:
            content = bs(driver.page_source)
            stories_divs.extend(content.find_all('div', attrs={'class':'eebAO h_uhZ'}))
            driver.execute_script(&amp;quot;document.getElementsByClassName('  _6CZji oevZr  ')[0].click()&amp;quot;)
            time.sleep(1)
        except Exception as E:
            scroll = False
    return len(set(stories_divs))&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Просмотр сторис&lt;/h2&gt;
&lt;p&gt;Следующее, чем может заняться реальный пользователь после авторизации — просмотр свежих историй. Для того, чтобы зайти в блок историй, нужно просто нажать на кнопку класса OE3OK:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/4-12.png" width="285" height="164" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Есть еще две кнопки, о которых мы должны знать. Это кнопка для переключения на следующую историю — она в классе FhutL и кнопка закрытия блока историй — класс wpO6b. Пускай одна история будет отнимать у нас от 10 до 15 секунд, и с вероятностью 1/5 мы переключим на следующую. При этом зададим переменные counter и limit — пусть сейчас мы хотим посмотреть случайное число историй от 5 до 45, и если мы уже посмотрели столько, то выходим из функции и историй.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def watch_stories(driver):
    watching = True
    counter = 0
    limit = random.randint(5, 45)
    driver.execute_script(&amp;quot;document.getElementsByClassName('OE3OK ')[0].click()&amp;quot;)
    try:
        while watching:
            time.sleep(random.randint(10, 15))
            if random.randint(1, 5) == 5:
                driver.execute_script(&amp;quot;document.getElementsByClassName('FhutL')[0].click()&amp;quot;)
            counter += 1
            if counter &amp;gt; limit:
                driver.execute_script(&amp;quot;document.getElementsByClassName('wpO6b ')[1].click()&amp;quot;)
                watching = False
    except Exception as E:
        print(E)
        watching = False&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Скроллинг ленты&lt;/h2&gt;
&lt;p&gt;После просмотра актуальных историй можно поскроллить ленту — это действие ничем не отличается от классического скроллинга страниц в Selenium. Запоминаем последнюю доступную длину страницы, скроллим до неё, ожидаем прогрузки, получаем новую. Прекратим просматривать ленту в двух случаях — если в random.randint() сгенерировалась единица или если лента кончилась.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def scroll_feed(driver):
    scrolling = True
    last_height = driver.execute_script(&amp;quot;return document.body.scrollHeight&amp;quot;)
    while scrolling:
        driver.execute_script(&amp;quot;window.scrollTo(0, document.body.scrollHeight);&amp;quot;)
        time.sleep(random.randint(4,10))
        new_height = driver.execute_script(&amp;quot;return document.body.scrollHeight&amp;quot;)
        if new_height == last_height or random.randint(1, 10) == 1:
            scrolling = False
        last_height = new_height&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Просмотр рекомендуемых аккаунтов&lt;/h2&gt;
&lt;p&gt;Instagram в заглавной странице сам рекомендует нам для подписки некоторые аккаунты. Выглядит она так:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/5-12.png" width="645" height="573" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;И на ней тоже придётся скроллить, чтобы дойти до конца. Заходим на страницу и ожидаем 5 секунд прогрузки, затем снова получаем длину страницы и скроллим вниз. Выходим тоже с вероятностью 1/10 или если страница кончилась, но ещё с вероятностью 1/2 подписываемся на некоторые из первых 100 аккаунтов рекомендаций:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def scroll_recomendations(driver):
   driver.get('https://www.instagram.com/explore/people/suggested/')
    time.sleep(5)
    scrolling = True
    last_height = driver.execute_script(&amp;quot;return document.body.scrollHeight&amp;quot;)
    while scrolling:
        driver.execute_script(&amp;quot;window.scrollTo(0, document.body.scrollHeight);&amp;quot;)
        time.sleep(random.randint(4,10))
        new_height = driver.execute_script(&amp;quot;return document.body.scrollHeight&amp;quot;)
        if new_height == last_height or random.randint(1, 10) == 1:
            scrolling = False
        last_height = new_height
        if random.randint(0, 1):
            try:
                driver.execute_script(f&amp;quot;document.getElementsByClassName('sqdOP  L3NKy   y3zKF     ')[{random.randint(1,100)}].click()&amp;quot;)
            except Exception as E:
                print(E)&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Просмотр рекомендуемых постов&lt;/h2&gt;
&lt;p&gt;Помимо ленты, которая сформирована из наших подписок, Instagram собирает ленту рекомендаций. Туда входят все посты, которые потенциально могут вам понравиться — мы просто пройдём вниз по этой ленте. Выйдем с вероятностью 1/5 или когда кончится, чтобы долго не засиживаться.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def scroll_explore(driver):
    driver.get('https://www.instagram.com/explore')
    time.sleep(3)
    scrolling = True
    last_height = driver.execute_script(&amp;quot;return document.body.scrollHeight&amp;quot;)
    while scrolling:
        driver.execute_script(&amp;quot;window.scrollTo(0, document.body.scrollHeight);&amp;quot;)
        time.sleep(random.randint(4,10))
        new_height = driver.execute_script(&amp;quot;return document.body.scrollHeight&amp;quot;)
        if new_height == last_height or random.randint(1, 5) == 1:
            scrolling = False
        last_height = new_height&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Итог&lt;/h2&gt;
&lt;p&gt;Теперь можно собрать все функции вместе — создаём новый driver, проводим авторизацию, считаем число новых сторис и сообщений, просматриваем сторис, переходим в рекомендуемые подписки и листаем ленту. В конце печатаем полученные данные — число новых сообщений, запросов и историй друзей.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;driver = webdriver.Chrome(ChromeDriverManager().install())
authorize(driver)
queries_text, messages_count = messages_count(driver)
stories_count = get_stories_count(driver)
watch_stories(driver)
scroll_recomendations(driver)
scroll_feed(driver)
scroll_explore(driver)

if queries_text is not None:
    print(queries_text)
else:
    print('Нет новых запросов на диалог')
print('Новых сообщений:', messages_count)

print('Новых историй:', stories_count)&lt;/code&gt;&lt;/pre&gt;</description>
<pubDate>Mon, 25 Jan 2021 16:49:42 +0300</pubDate>
</item>

<item>
<title>Пишем парсер свежих прокси на Python для Selenium</title>
<guid isPermaLink="false">50</guid>
<link>http://test.leftjoin.ru/all/selenium-proxy/</link>
<comments>http://test.leftjoin.ru/all/selenium-proxy/</comments>
<description>
&lt;p&gt;Случается такое, что во время парсинга страниц через Selenium можно словить бан по IP-адресу. Чтобы этого избежать, лучше использовать прокси. Сегодня напишем скрипт, который сам спарсит новые прокси, проверит их и в случае успеха передаст в Selenium.&lt;/p&gt;
&lt;h2&gt;Парсинг новых прокси&lt;/h2&gt;
&lt;p&gt;Начнём с импортирования библиотек — нам понадобятся модули для отправления запросов, для парсинга и хранения данных.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;import requests_html
from bs4 import BeautifulSoup
import pickle
import requests&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Все прокси будем хранить в множестве &lt;span class="inline-code"&gt;px_list&lt;/span&gt;, а также отправлять в pickle-файл &lt;span class="inline-code"&gt;proxis.pickle&lt;/span&gt;. В случае, если он не будет пустым, попробуем взять из него данные.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;px_list = set()
try:
    with open('proxis.pickle', 'rb') as f:
            px_list = pickle.load(f)
except:
    pass&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Функция &lt;span class="inline-code"&gt;scrap_proxy()&lt;/span&gt; будет заходить на сайт free-proxy-list.net и собирать оттуда 20 последних прокси. На сайте новые адреса появляются ежеминутно. Вот, как выглядит интересующая нас область сайта:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/1-8.png" width="1190" height="513" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Из всего этого будем собирать ID Address и Port. Посмотрим, как элементы расположены в коде страницы:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="http://test.leftjoin.ru/pictures/2-8.png" width="671" height="260" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Все нужные данные являются ячейками таблицы. В цикле будем брать первые 20 строк, обращаясь к IP-адресу и порту по &lt;span class="inline-code"&gt;xpath&lt;/span&gt;. В конце функция будет отправлять свежие прокси в pickle-файл и возвращать список прокси.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def scrap_proxy():  
    global px_list
    px_list = set()

    session = requests_html.HTMLSession()
    r = session.get('https://free-proxy-list.net/')
    r.html.render()
    for i in range(1, 21):
        add=r.html.xpath('/html/body/section[1]/div/div[2]/div/div[2]/div/table/tbody/tr[{}]/td[1]/text()'.format(i))[0]
        port=r.html.xpath('/html/body/section[1]/div/div[2]/div/div[2]/div/table/tbody/tr[{}]/td[2]/text()'.format(i))[0]
        px_list.add(':'.join([add, port]))

    print(&amp;quot;---New proxy scraped, left: &amp;quot; + str(len(px_list)))
    with open('proxis.pickle', 'wb') as f:
        pickle.dump(px_list, f)
    return px_list&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Проверка полученных прокси&lt;/h2&gt;
&lt;p&gt;Не всегда свежие прокси оказываются рабочими: мы напишем функцию, которая сама отправит get-запрос к сайту Google с прокси и в случае появления любой ошибки будет возвращать &lt;span class="inline-code"&gt;False&lt;/span&gt;. В случае, если прокси оказался рабочим, функция вернёт &lt;span class="inline-code"&gt;True&lt;/span&gt;.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def check_proxy(px):
    try:
        requests.get(&amp;quot;https://www.google.com/&amp;quot;, proxies = {&amp;quot;https&amp;quot;: &amp;quot;https://&amp;quot; + px}, timeout = 3)
    except Exception as x:
        print('--'+px + ' is dead: '+ x.__class__.__name__)
        return False
    return True&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Основная функция&lt;/h2&gt;
&lt;p&gt;Главная функция скрипта будет принимать в аргумент переменную scrap, по умолчанию принимающую &lt;span class="inline-code"&gt;False&lt;/span&gt;. Мы будем собирать новые прокси только в том случае, если &lt;span class="inline-code"&gt;scrap == True&lt;/span&gt; или длина списка прокси менее 6. Затем в цикле &lt;span class="inline-code"&gt;while True&lt;/span&gt; собираем новые прокси, берём последний, проверяем его и в случае, если &lt;span class="inline-code"&gt;check_proxy&lt;/span&gt; вернёт &lt;span class="inline-code"&gt;True&lt;/span&gt;, отправляем прочие прокси в pickle-файл и возвращаем рабочий адрес и порт.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;def get_proxy(scrap = False):
    global px_list
    if scrap or len(px_list) &amp;lt; 6:
            px_list = scrap_proxy()
    while True:
        if len(px_list) &amp;lt; 6:
            px_list = scrap_proxy()
        px = px_list.pop()
        if check_proxy(px):
            break
    print('-'+px+' is alive. ({} left)'.format(str(len(px_list))))
    with open('proxis.pickle', 'wb') as f:
            pickle.dump(px_list, f)
    return px&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Используем скрипт с Selenium&lt;/h2&gt;
&lt;p class="note"&gt;А ещё мы писали, как через Selenium &lt;a href="http://test.leftjoin.ru/all/selenium-button/"&gt;имитировать нажатие кнопки&lt;/a&gt; и &lt;a href="http://test.leftjoin.ru/all/parsim-dannye-kataloga-sayta-ispolzuya-beautiful-soup-i-selenium/"&gt;скроллинг каталога интернет-магазина&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Чтобы к скрипту Selenium подключить прокси, импортируем функцию &lt;span class="inline-code"&gt;get_proxy&lt;/span&gt;. Заходим в бесконечный цикл, в переменную &lt;span class="inline-code"&gt;PROXY&lt;/span&gt; запишем свежие полученные прокси и, используя опции браузера, добавим наши прокси и инициируем новый webdriver с обновленными опциями. Затем пробуем зайти на сайт, добавить свои cookie и в случае успеха выходим из цикла оператором &lt;span class="inline-code"&gt;break&lt;/span&gt;. Если новый прокси всё равно оказался нерабочим или вылезла капча, в цикле получим новые прокси и повторим, пока не получится.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code&gt;from px_scrap import get_proxy

while True:
    PROXY = get_proxy(scrap=True)
    options.add_argument('--proxy-server=%s' % PROXY)
    driver = webdriver.Chrome(chrome_options=options, executable_path=os.path.abspath(&amp;quot;chromedriver&amp;quot;))
    try:
        driver.get('https://google.com')
        driver.add_cookie(cookies)
    except:
        print('Captcha!')&lt;/code&gt;&lt;/pre&gt;</description>
<pubDate>Fri, 03 Jul 2020 17:16:54 +0300</pubDate>
</item>


</channel>
</rss>