В этой статье будет рассмотрен способ создать .exe файл, позволяющий пройти авторизацию ВК и выполнить определенные запросы к VK API. Установка Python не требуется.

Системные требования



Предыстория и, собственно, зачем мне это понадобилось


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

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

Но и это не все: каждый участник, наделенный редакторскими правами, также должен быть в состоянии обновить все странички. Способ «заставить этих гуманитариев всех редакторов установить себе python» очевидно не подходит. Нужно просто — клик, клик, запустилось, заработало, закрылось. Проще говоря, нужен .exe файл.

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

Возможно, изложенный здесь способ — очередной изобретенный велосипед, но все-таки я продолжу.

Шаг нулевой. Подготовка


В папке, где будут храниться наши будущие файлы, создаем подпапку (например, bin), куда сохраняем chromedriver.exe, cacert.pem и (если очень хочется) иконку для нашего будущего .exe файла. В эту же папку помещаем пока что пустой текстовый файл path_to_chrome.txt.

Шаг первый. Авторизация


Итак, все готово. Еще раз о том, как происходит процесс авторизации и получения ключа доступа access_token для работы с API.

  1. Пользователь переходит по ссылке, приложение запрашивает доступ
  2. Пользователь разрешает доступ
  3. Происходит перенаправление на пустую страницу: в адресной строке появляется access_token

О том, как формируется ссылка, рассказано в официальной документации.

Импортируем все необходимое, объявляем константы:

import os
import sys
import time

from selenium import webdriver
from selenium.webdriver.common.keys import Keys

import vk

from script import PagesUpdater


SCOPE = 'pages'  # vk.com/dev/permissions
OAUTH = 'https://oauth.vk.com/authorize?client_id=4836475&scope={}&redirect_uri=https://oauth.vk.com/blank.html&display=popup&v=5.37&response_type=token'.format(SCOPE)

IDLE = 3
TOKEN = 'access_token='  # после этого следует сам access_token
ATOKEN_LEN = 85          # и его длина — 85 символов
CHROME_DRIVER = '\chromedriver.exe'

PATH = os.getcwd()
PATH_TO_CHROME_TXT = PATH + '\path_to_chrome.txt'


class Executor(object):
    def launch(self):
        chrome_path = self.get_chrome_path()
        self.driver = webdriver.Chrome(chrome_path)  # запуск selenium

        access_token = self.get_access_token()

        vkapi = vk.API(access_token=access_token)    # запуск API

        script = PagesUpdater(vkapi, vk.api.VkAPIMethodError, self.driver)
        script.launch()

        self.driver.quit()

О PagesUpdater и передаваемых ему аргументах будет рассказано чуть позже.

Selenium требует указать полный путь к файлу chromedriver.exe, который должен находиться в одной папке с браузером. Путь к браузеру наверняка отличается на каждом компьюетере, поэтому вынесем его в отдельный текстовый файл path_to_chrome.txt следующего содержания:

C:\chrome-win32\chrome-win32

    def get_chrome_path(self):
        with open(PATH_TO_CHROME_TXT, 'r') as target:
            return target.read()

Разумеется, пользователь должен заранее указать полный путь к браузеру в path_to_chrome.txt.

Перехватываем access_token:

    def get_access_token(self):
        self.driver.get(OAUTH)  # открыть ссылку для получения ключа

        access_token = ''
        while not access_token:
            page_url = self.driver.current_url

            if TOKEN in page_url:
                token_start = page_url.index(TOKEN) + len(TOKEN)
                access_token = page_url[token_start:token_start+ATOKEN_LEN]
                break

            else:
                time.sleep(IDLE)

        return access_token

Ну, и напоследок, забегая вперед, скажу, что после компиляции возникает ошибка из-за нехватки файла cacert.pem. Здесь дано решение: добавить cacert.pem в PATH, а потом не забыть собрать при сборке .exe:

def resource_path(relative):
    return os.path.join(getattr(sys, '_MEIPASS', os.path.abspath(".")),
                        relative)


cert_path = resource_path('cacert.pem')
os.environ['REQUESTS_CA_BUNDLE'] = cert_path

Последний штрих.
exe = Executor()
exe.launch()

Всё. Идем дальше.

Шаг второй. Обращаемся к VK API


Опять же, импортируем все необходимое, объявляем константы:

import requests
import time


LIMIT = 1
CAPTCHA_IMG = 'captcha_img'
CAPTCHA_SID = 'captcha_sid'


class PagesUpdater(object):
    def __init__(self, vkapi, vkerror, driver):
        self.vkapi = vkapi
        self.vkerror = vkerror
        self.driver = driver

self.vkapi будет использоваться для обращения к любым методам VK API. Два других аргумента будут использоваться для обработки Captcha (см. ниже).

Обработка ошибок


Я столкнулся с тремя ошибками, которые гарантированно появлялись при большом количестве запросов:

  1. VK Captcha Error (не введен текст с изображения Captcha)
  2. VK Flood Control (слишком много запросов в секунду)
  3. requests.exceptions.Timeout (появляется, когда ей угодно)

Создадим метод, в который будем «оборачивать» все запросы к VK API:

    def errors(self, vkmethod, **vkkwargs):
        while True:
            try:
                time.sleep(LIMIT)  # LIMIT == 1 just in case
                return vkmethod(**vkkwargs)

            except requests.exceptions.Timeout:
                continue

            break

Со второй и третьей ошибками разобрались:

  • Перед каждым запросом будет происходить секундная задержка (подробнее о запросах в в документации).
  • Если возникнет requests.exceptions.Timeout, то запрос попросту повторится

С Captcha всё чуточку сложнее. Читаем документацию:
… следует запросить пользователя ввести текст с изображения captcha_img и повторить запрос, добавив в него параметры:

  • captcha_sid — полученный идентификатор
  • captcha_key — текст, который ввел пользователь

Добавим except блок, в котором будет происходить обработка Captcha:

            except self.vkerror as e:
                if CAPTCHA_IMG in e.error:
                    self.driver.get(e.error[CAPTCHA_IMG])  # открыть картинку

                    key = raw_input(R)                     # ввести код
                    vkkwargs[captcha_sid] = e.error[CAPTCHA_SID]
                    vkkwargs[captcha_key] = key            # повторить запрос
                    continue

                else:
                    raise  # если же ошибка не в Captcha

С ошибками разобрались. Идем дальше.

Запрос к API


Добавим простой метод, который возвращает список вики-страниц в группе:

    def get_titles(self, gid=NGID):
        return self.errors(self.vkapi.pages.getTitles,
                           group_id=gid)

И метод launch, в котором будет происходить все остальное:

    def launch(self):
        print self.get_titles()
        # do something else

Собственно, вот и всё.

Компиляция в .exe


Осталось только запустить файл со следующим кодом:

import os
import sys
from distutils.core import setup

import py2exe

sys.argv.append('py2exe')

AUTHOR = 'Варвара Холодная'
COMPANY = 'ООО «Контора»'
NAME = 'Имя будущего .exe файла'
DESCRIPTION = 'Описание файла'
SCRIPT = 'auth_user.py'
VERSION = '1.0.0.0'
BIN = 'bin/'
# файлы, которые нужно будет собрать из папки bin
DATA_FILES = [
    'path_to_chrome.txt', 'cacert.pem', 'chromedriver.exe'
]


setup(
    options={
        'py2exe': {
            'bundle_files': 1,
            'compressed': True,
            'unbuffered': True,
            'optimize': 0,
        }
    },
    data_files=[('', [BIN+s for s in DATA_FILES])],
    console=[{
        'script': SCRIPT,
        'name': NAME,
        'dest_base': NAME,
        'description': DESCRIPTION,
        'copyright': AUTHOR,
        'company_name': COMPANY,
        'version': VERSION
    }],
    zipfile=None,
)

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

P.S.


Тестировать файлы с ручной авторизацией — неудобно. Куда легче самому пройти по ссылке OAuth, выписать access_token и в дальнейшем выполнять другой файл, например, auth_direct.py (разумеется, он не подойдет для передачи другому пользователю):

import vk

from selenium import webdriver
from selenium.webdriver.common.keys import Keys

from script import PagesUpdater


ACCESS_TOKEN = 'Заранее полученный и сохраненный access_token'
VKAPI = vk.API(access_token=ACCESS_TOKEN)
VKError = vk.api.VkAPIMethodError


class DirectUpdater(PagesUpdater):
    def __init__(self, driver_path):
        self.vkapi = VKAPI
        self.vkerror = VKError
        self.driver = webdriver.Chrome(driver_path)

du = DirectUpdater(
    'Путь к chromedriver.exe')

du.launch()

Комментарии (0)