Найти тему
Аналитика данных

Парсинг сайта на Python. Имитация действий юзера

Допустим вам нужно собрать данные с некоторого сайта, на котором по списку ИНН организаций можно получить официальные данные компаний. Сейчас таких сайтов много и большинство из них предлагают подобные данные бесплатно. Такие как:

  • Юридическое наименование
  • ОГРН
  • КПП
  • ОКПО
  • Дата регистрации
  • Учредители
  • Виды деятельности ОКВЭД
  • Финансовая отчетность
  • Официальные контакты и т.п.

В задаче ниже будем собирать коды ОКВЭД главного вида деятельности компаний. Если у вас тысячи ИНН в списке, то ручная проверка займёт уйму времени. А делать простые requests-запросы не позволяет сервер, выдавая ошибку HTTP 429: "Too Many Requests". Эта ошибка возвращается, при слишком большом количестве запросов с одного IP-адреса в течение короткого периода времени, период зависит от настроек сервера.

Но решение есть! Можно воспользоваться библиотекой pyautogui на python, чтобы имитировать действия пользователя в браузере. Причём таким методом можно не только парсить сайты, но и автоматизировать много другой рутинной работы на компьютере.

Открываем Jupyter Notebook, пишем код. Для начала импортируем пакет через pip для имитации действий пользователя.

pip install pyautogui --quiet
Импорт пакета pyautogui
Импорт пакета pyautogui

У меня в ноутбуке прописан URL сайта для удобства, т.к. задача по вытягиванию данных стоит как ежемесячная и чтобы всё было под рукой, сразу прописал адрес сайта в ячейке-markdown. Ну и для напоминания прописал на какое разрешение экрана ориентирован код, это важно, т.к. используются координаты зависящие от размеров экрана в пикселях.

Ещё важный момент, т.к. библиотека pyautogui программно нажимает на кнопки клавиатуры, то до запуска скрипта важно переключить раскладку на английский язык, иначе горячие клавиши типа Ctrl+C, Ctrl+V не будут работать. В сети есть решения, опять же, для программной проверки и переключения раскладки. Но у меня почему-то эти решения не стали работать и даже написанный код от ИИ, также не взлетал. Пока решения для себя не нашёл, переключаю раскладку руками, неудобства не доставляет. Остальное работает нормально. Движемся дальше...

import pandas as pd
import pyautogui
import pyperclip
import time
Устанавливаем нужные библиотеки
Устанавливаем нужные библиотеки

Читаем наш файл с заранее собранными ИНН компаний, по которым нужно узнать остальную официальную информацию:

df = pd.read_excel('src.xlsx')

Обязательно переводим столбец с ИНН в тип данных string

df['ИНН'] = df['ИНН'].astype(str)
Читаем даные по ИНН. Переводим тип данных ИНН в string
Читаем даные по ИНН. Переводим тип данных ИНН в string

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

df.info()

Есть один нюанс, если ИНН в исходной таблице excel хранятся как числа типа int или float, то нули в начале ИНН будут отброшены как незначащие, такая особенность редактора таблиц. И это приведёт к тому, что поиск в форме сайта по такому ИНН не даст результаты.

Чтобы вернуть отброшенные нули на место сделаем следующую проверку. Для этого вам понадобится наличие в вашей исходной таблице не только ИНН, но и наименования компаний: ООО «Пупкин и Ко». Если таких данных нет, то придётся делать такую проверку вручную.

Дело в том что есть два типа ИНН (я встречал только эти): у ИП в ИНН 12 цифр, а у юридического лица – 10. Получается нам нужно проверить какая организационно-правовая форма (ОПФ) у компании. И сколько чисел в ИНН указано в таблице. Зная это пишем код в ноутбуке. Для начал из названия компании выделим ОПФ. Для этого выделяем из названия компании ОПФ создаём колонку с организационно-правовой формой, назавём её 'org':

df['org'] = df['Компания'].str.split(" ", n=1).str[0]

Но если в наименовании компании прописано не сокращённая форма типа ООО или ЗАО, а полная: «Общество с Ограниченной Ответственностью» или «Закрытое Акционерное Общество», то нужно предусмотреть в скрипте и такую возможность. Для этого пишем:

df.loc[df['Компания'].str.contains('общество с ограниченной ответственностью', case=False), 'org'] = 'ООО'
df.loc[df['Компания'].str.contains('закрытое акционерное общество', case=False), 'org'] = 'ЗАО'
df.loc[df['Компания'].str.contains('акционерное общество', case=False), 'org'] = 'АО'
df.loc[df['Компания'].str.contains('открытое акционерное общество', case=False), 'org'] = 'ОАО'
df.loc[df['Компания'].str.contains('Индивидуальный предприниматель', case=False), 'org'] = 'ИП'
df.loc[df['Компания'].str.contains('непубличное акционерное общество', case=False), 'org'] = 'НПАО'
df.loc[df['Компания'].str.contains('некоммерческая организация', case=False), 'org'] = 'НКО'
Определение ОПФ
Определение ОПФ

Далее посчитаем количество символов в ИНН:

df['len_inn'] = df['ИНН'].astype(str).str.len()
Количество символов в ИНН
Количество символов в ИНН

После подсчёта количества символов в ИНН можно приступить к восполнению утраченных нулей в начале строк. Для этого нам понадобятся маски. Отдельно для ИП и для всех остальных, т.к. они отличаются количеством символов в ИНН.

# Маска для ИП
mask = (df['org'] == 'ИП') & (df['len_inn'] < 12)
# Добавляем нули в начало строки в 'ИНН', чтобы длина стала 12
df.loc[mask, 'ИНН'] = df.loc[mask, 'ИНН'].str.zfill(12)
# Условие с маской для всех кроме ИП
mask = (df['org'] != 'ИП') & (df['len_inn'] < 10)
# Добавляем нули в начало строки в 'ИНН', чтобы длина стала 10
df.loc[mask, 'ИНН'] = df.loc[mask, 'ИНН'].str.zfill(10)
Маски для определения ИП и всех кто не ИП
Маски для определения ИП и всех кто не ИП

После таких манипуляций все наши ИНН будут находится в корректном формате. Разобравшись с ИНН, предусмотрим тот факт, что на сайте, где мы будем искать инфу, может и не быть указанного нами ИНН, или может быть просто какая-то ошибка. Для этого добавим две новые колонки в наши исходные данные со значениями 'nodata'. Если данные вытащить получится, то 'nodata' заменится ими. В колонке 'code_mainokved' будет хранится числовой код главного вида деятельности компании, а в 'txt_mainokved' будет его текстовое описание.

# Добавляем новые колонки для хранения результатов
df['code_mainokved'] = 'nodata'
df['txt_mainokved'] = 'nodata'
Добавляем новые колонки
Добавляем новые колонки

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

Координаты блоков
Координаты блоков

В общем виде алгоритм такой:

  1. Запускаем цикл по перебору ИНН из нашего исходного датафрейма и для каждого ИНН делаем следующие действия;
  2. Переводим мышку на поле поиска сайта по ИНН;
  3. Нажимаем ЛКМ;
  4. Вставляем текущий ИНН;
  5. На полученной странице копируем нужную нам информацию также с помощью кликов, выделения и Ctrl+C;
  6. Вставляем полученные данные в исходную таблицу.
  7. Сохраняем все данные в excel-файл
# Перебор всех ИНН в датафрейме
for index, row in df.iterrows():
copied_mainokved = 'nodata'
copied_txt_mainokved = 'nodata'
inn = str(row['ИНН']) # Приводим к строке
# Нажать на форму поиска
pyautogui.moveTo(search_box_coords, duration=2)
pyautogui.doubleClick(interval=0.1)
pyautogui.press('delete')
time.sleep(1) # Небольшая пауза для стабильности
# Ввести ИНН и нажать Enter
pyautogui.write(inn)
pyautogui.press('enter')
time.sleep(2) # Подождать загрузку новой страницы
# Переместить курсор к коду ОКВЭД
pyautogui.moveTo(okved_area_coords, duration=1)
pyautogui.doubleClick(interval=0.1)
pyautogui.hotkey('ctrl', 'c') # Скопировать код ОКВЭД
time.sleep(0.1)
copied_mainokved = pyperclip.paste()
# Сохранение ОКВЭД в датафрейме
df.at[index, 'code_mainokved'] = copied_mainokved
pyautogui.click()
# Задержка перед выделением текста
time.sleep(1)
# Движемся к описанию ОКВЭД
pyautogui.moveTo(text_area_coords, duration=1)
pyautogui.moveTo(txthird_area_coords, duration=2)
pyautogui.tripleClick(interval=0.1)
pyautogui.hotkey('ctrl', 'c') # Скопировать выделенный текст
time.sleep(0.1)
copied_txt_mainokved = pyperclip.paste()
# Сохранение ОКВЭД в датафрейме
df.at[index, 'txt_mainokved'] = copied_txt_mainokved
time.sleep(1)
# Вывод информации об обработанном ИНН
print(f"ИНН: {inn}, ОКВЭД: {df.at[index, 'code_mainokved']}")
print(f"Текст: {df.at[index, 'txt_mainokved']}")
print() # Пустая строка для отбивки

Так комбинируя последовательности действий: клики, выделения, копирование и вставки можно собирать любую информацию с сайтов. После завершения конца нужно сохранить датафрейм в excel-файл:

df.to_excel('output_okved.xlsx', index=False)
Сохранение данных
Сохранение данных