Game Maker - создание игр | HellRoom Games
Март 24, 2025, 03:47:13 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.

Войти
Новости:
 
   Начало   Game Maker Помощь Правила форума Поиск Календарь Войти Регистрация  
Страниц: [1]   Вниз
  Печать  
Автор Тема: Увлекательный Python. Часть 1: пишем клиент к чату hellroom  (Прочитано 46545 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Sky Rider
Активный участник
*****

Репутация: 70
Offline Offline

Пол: Мужской
Награды:
За постоянность! [10 дней на форуме]
API: Love
Деятельность: Lua, Python
Сообщений: 323


« : Июнь 18, 2014, 16:24:53 »

1. Предисловие
   Почему-то многие разработчики игр, особенно юные, думают, что программировать вне геймдева, а тем более писать консольные приложения – скучное занятие. Их можно понять: казалось бы, что может допотопное чёрное окно с серыми строчками противопоставить яркой графике, ПИУ-ПИУ и МИДУРАЧЬЁДЕРЖИ? А я утверждаю, что кое-что может, и не так уж мало! В цикле статей «Увлекательный Python» я собираюсь наглядно продемонстрировать, что весело писать можно не только игры.
    Наверняка все из нас когда-то хотели написать какую-то полезную программу, выполняющую определённые действия в интернете. Да-да, товарищ в заднем ряду, не ты ли мечтал сделать бота, который обогатил бы тебя в твоей некогда любимой браузерной игре? :) Но ботов мы писать не будем, по крайней мере, не сегодня. Вместо этого мы напишем консольный клиент к чату всеми нами любимого форума.

2. Что нам понадобится
2.1. Интерпретатор Python
   Первое, что нам нужно – интерпретатор Python. Мы будем использовать третью версию: смысл писать на второй есть, только если нам нужны какие-то библиотеки, ещё не портированные на третью. К счастью, всё, что нам понадобится, на третьем Питоне давно имеется.
    Скачать интерпретатор можно с официального сайта: https://www.python.org/download/. На момент написания статьи последняя версия – 3.4.1. Если вы видите версию новее, смело качайте её – все новые выпуски обладают обратной совместимостью.
    Во время установки обратите внимание на окно выбора устанавливаемых компонентов:
   По умолчанию, опция «Add python.exe to Path» будет отключена. Обязательно включите её, кликнув на ней левой кнопкой мыши и выбрав пункт «Will be installed on local hard drive». Дальше никаких подводных камней не будет.
    Проверим, что все компоненты установились. Откройте консоль (Win+R -> “cmd”) и введите «python». Должен запуститься интерпретатор:
   Чтобы выйти, вводим «quit()».
    Теперь проверим, установился ли пакетный менеджер. Всё в той же консоли, из которой запускали интерпретатор, вводим «pip». Должна вылезти объёмная подсказка (на английском) о всех доступных командах. Используя pip, мы будем по ходу пьесы устанавливать все требуемые нам сторонние библиотеки Python.
    Если же интерпретатор или пакетный менеджер не запускаются – не расстраиваемся, а идём прямиком в панель управления. В строку поиска вбиваем «переменные среды» -> «Изменение системных переменных среды» -> «Переменные среды». В нижней половине окна, где и расположены системные переменные, ищем “Path”. Нашли? Теперь кликаем на неё два раза, и в поле «Значение переменной» в самое начало добавляем:
Код:
C:\Python34\;C:\Python34\Scripts;
   Если вы установили Python не в стандартную директорию, отредактируйте пути соответствующим образом. Значение теперь должно выглядеть примерно так:
   Нажимаем «ОК» во всех окнах. Попробуйте снова запустить интерпретатор и пакетный менеджер. Теперь всё работает!

2.2. Notepad++
   Во-вторых, нам нужна программа, где мы будем писать код. Устанавливать какие-нибудь навороченные IDE для наших целей будет лишним, лучше воспользуемся простым редактором Notepad++.
    Официальный сайт: http://notepad-plus-plus.org/.
    Во время установки не забудьте выбрать русскую локализацию из огромного списка доступных языков.

2.3. Mozilla Firefox
   Ну и последнее, что нам нужно – Firefox. Почему именно он? Потому что мы планируем пользоваться некоторыми продвинутыми возможностями браузера и плагинами. Несомненно, аналогичные возможности есть и у других браузеров, но при написании статьи я пользовался Firefox’ом. Если вы уверены, что сможете все нужные возможности найти в своём браузере, я вас не заставляю.
    Скачать Firefox можно с официального сайта: https://mozilla-russia.org/
    Также нам понадобится плагин Tamper Data: https://addons.mozilla.org/ru/firefox/addon/tamper-data/

3. Немного теории
   Перед тем, как что-то писать, давайте сначала определимся, что такое клиент. Почти каждое сетевое приложение состоит из клиентской и серверной части. Просто говоря, сервер – это компьютер, обладающий определёнными ресурсами, которые нужны клиентам. Клиенты обращаются к серверу с запросами на эти ресурсы. Сервер может либо удовлетворить запрос, либо не удовлетворить.
    Какие же ресурсы есть у чатового сервера Hellroom, доступ к которым мы хотим получить, как клиенты?
    Ресурс всего один: это база данных, в которой хранятся сообщения. Чатовый клиент должен уметь всего две вещи:
      1) Запросить сообщения из базы данных для чтения;
      2) Отправить сообщение на сервер, чтобы тот внёс его в базу данных.
    Оба действия совершаются от лица конкретного пользователя. То есть перед тем, как сервер сможет удовлетворить запрос, он должен убедиться, что этот запрос сделан законным пользователем, обладающим соответствующими правами.
    Предоставление прав на определённые действия называется авторизацией.
    Но как нам узнать, в какой именно форме мы должны обмениваться запросами с сервером?

4. Обратная разработка
   Давайте задумаемся на минуточку. Мы ведь можем пользоваться чатом через браузер, верно? Значит, наш браузер вполне себе осведомлён, каким именно образом он может взаимодействовать с чатовым сервером. А всё благодаря тому, что в главную страницу форума уже вшит клиент! И клиент этот написан на javascript. И раз он уже есть в странице, надо всего лишь открыть её исходный код и посмотреть, как он работает.
    Итак, открываем главную страницу форума в Firefox’е. Обязательно проследите за тем, чтобы вы были авторизированы. Нажимаем правой клавишей на любом месте страницы и в контекстном меню выбираем опцию «Исходный код страницы».
    Перед нами – огромное количество html-кода, смешанного с javascript. Теперь надо найти интересующие нас функции запроса и отправки сообщений. Пойдём от простого: при каком событии клиент запрашивает сообшения с сервера? Во-первых, при загрузке страницы, но этот код не так легко найти. А во-вторых, при нажатии кнопки «обновить» - вот это уже намного проще. Так что смело жмём Ctrl+F и в строке поиска вбиваем «обновить».
    Браузер находит нам следующую строку:
Код:
<a href="#refresh" onclick="Shoutbox_GetMsgs(); return false;">Обновить</a>
   Свойство onclick содержит в себе javascript-код, который будет выполнен при нажатии ссылки. Очевидно, будет вызвана функция Shoutbox_GetMsgs. Осталось только найти саму эту функцию, и дело в шляпе.
    Однако поиск по строке «Shoutbox_GetMsgs» ничего не даёт. А это потому, что функция находится во внешнем js-файле, который подключается к странице. Можно, конечно, найти нужный файл вручную, но намного лучше воспользоваться продвинутым инструментом – отладчиком. Для этого нажимаем Alt и в появившемся сверху меню выбираем «Инструменты» -> «Веб-разработка» -> «Отладчик». Смело жмём Ctrl+F, после чего вводим в строке поиска «@Shoutbox_GetMsgs» («собака» означает, что мы ищем определение функции).
    Опа! Наша функция отыскалась в подключенном файле «shoutbox.js». Её код следующий:

    Первый if содержит какую-то проверку, которая нам неинтересна.
    Второй if, очевидно, выполняет определённые действия, если это первый запрос чата (на самом деле, утверждать этого лишь по этой функции нельзя – но я уже полазил по исходному коду, так что можете мне верить).
    Следующий блок в три строчки кода для нас заботливо прокомментировали: он отображает, что сообщения загружаются.
    Та часть кода, которая нас интересует – всего лишь одна строчка:
Код:
Shoutbox_getXML(smf_scripturl + "?action=shoutbox;sa=get;xml;row=" + Shoutbox.maxmsgs + (Shoutbox.first ? ';restart' : ''), Shoutbox_PutMsgs);

    Чтобы кое-что прояснить, переходим во вкладку «Консоль» и вбиваем туда переменную «smf_scripturl», и интерпретатор, не будучи Штирлицем, мгновенно отобразит нам её значение: "http://forum.hellroom.ru/index.php". По названию переменной это можно было предположить, не правда ли? :) То же самое проделываем с «Shoutbox.maxmsgs» и «Shoutbox.first», на что получаем ответы: 80 и false. Возможно, вы получили не 80, а больше – к этому мы вернёмся позже.
    То есть функции Shoutbox_getXML в общем случае передаётся первый аргумент вида: "http://forum.hellroom.ru/index.php?action=shoutbox;sa=get;xml;row=80". С большой долей уверенности можно утверждать, что это и есть тот запрос, в ответ на который сервер присылает сообщения чата. Как можно догадаться из названия функции, присылает их сервер в формате XML, но об этом мы поговорим потом.
    На всякий случай заглядываем в функцию Shoutbox_getXML. Ага, так и есть: первый аргумент – это url, на который совершается запрос, а второй аргумент – callback-функция, то есть та функция, которой будут переданы результаты запроса.
    Теперь можно написать тестовый скрипт, делающий такой же запрос, и посмотреть, что мы получим в ответ от сервера. Но нас останавливает одно обстоятельство: мы ведь вовсе не уверены, что при первом запросе переменные Shoutbox.maxmsgs и Shoutbox.first имеют именно те значения, которые мы получили уже после загрузки страницы! Логика подсказывает, что последняя переменная изначально имеет значение true, а вот чему равна первая?
    Ответ на этот и многие другие вопросы мы получим, заглянув в конструктор класса… Тьфу, какой конструктор, это же javascript! Нужно просто найти строки, где значения этим переменным присваиваются в первый раз. Сделать это оказалось не так уж сложно:
Код:
Shoutbox.first = true;
Shoutbox.countmsgs = Shoutbox.maxmsgs = 0;
   
    То есть наш первый запрос к чату должен иметь следующий вид:
    http://forum.hellroom.ru/index.php?action=shoutbox;sa=get;xml;row=0;restart

5. Время писать код
   Чтобы отправлять http-запросы при помощи Python, нам понадобится библиотека requests. Она не входит в число стандартных библиотек, так что открываем консоль (Win+R -> “cmd”) и вводим:
Код:
pip install requests
   Через несколько секунд библиотека должна установиться. Проверим это, открыв интерпретатор Python (это можно сделать, введя «python» в уже открытой нами консоли). Введём:
Код:
>>> import requests
   Нажимаем Enter, и, если никаких ошибок не произошло, значит, библиотека действительно установилась.
    Закрываем консоль, поскольку писать в ней код мы не будем, а лучше запишем его сразу в файл. Открываем Notepad++ и создаём новый файл со следующим содержимым:


    Сохраняем его где-нибудь под именем “main.py”.
    Теперь открываем консоль в той же папке, где мы создали файл (для этого нужно нажать рядом с файлом правой кнопкой при зажатом shift и выбрать в контекстном меню пункт «Открыть окно команд»). Вводим имя нашего файла: “main.py”. Если вы всё сделали правильно, в консоли должна появиться надпись «Привет, мир!».
    Как вы могли догадаться, функция main – это та функция, которая будет выполняться при запуске файла. Её можно было назвать по-другому или вообще обойтись без неё, это уже личные предпочтения.
    Для осуществления http-запросов в библиотеке requests есть простая функция get. В качестве аргумента она принимаем адрес, а возвращает объект, называемый Response, то есть «ответ». Из этого объекта мы можем получить много полезной информации, в том числе – сам текст, который нам вернул сервер. Текст хранится в атрибуте text.
    Что ж, попробуем осуществить наш запрос и вывести на экран ответ сервера:


    Запускаем скрипт всё тем же образом, через консоль. И что мы видим? Ничего! Сервер нам ничего не ответил, и скрипт вывел пустую строку. Помните, я говорил, что сервер может либо удовлетворить запрос, либо нет? Сервер наш запрос не удовлетворил, потому что мы выполнили его от лица анонимного пользователя интернета. А чат могут просматривать лишь авторизированные пользователи.
    Всё очень просто: нам нужно авторизоваться!

6. Копаем глубже: авторизация
   Каким образом мы обычно авторизуемся на сайте? Вводим логин, пароль и нажимаем кнопку «Войти». После этого браузер отправляет на сервер http-запрос с логином и паролем, а сервер либо подтверждает авторизацию, либо возвращает ошибку.
    Понять, куда отправляются наши данные и в каком виде, можно всё тем же способом, который мы уже применяли – анализом исходного кода страницы. Но я хочу вам показать другой способ, который обычно намного проще.
    Мы просто-напросто перехватим запрос, когда браузер отправит его на сервер. Для этого нам понадобится плагин Firefox под названием Tamper Data.
    Откроем главную страницу форума. Убедитесь, что вы сейчас не авторизированы. Желательно также закрыть все прочие вкладки, поскольку плагин будет перехватывать их запросы тоже.
    Введите свой логин и пароль, но пока не отправляйте. Нажмите Alt и в панели меню выберите «Инструменты» -> «Перехватка данных». В появившемся окне нажмите кнопку «Запустить перехват», находящуюся в левом верхнем углу.
    Вот теперь можно нажимать кнопку «Войти».
    Как только вы это сделаете, выскочит окошко с предложением вмешаться в процесс передачи данных:
   Смело жмём кнопку «Вмешаться». Перед нами откроется новое окно:
   В левом верхнем углу указан адрес, на который был отправлен запрос. В левой половине окна – техническая информация, не представляющая для нас интереса. А вот в правой половине – именно то, что мы ищем. Это – POST-параметры запроса.
    В протоколе http предусмотрено два способа передачи информации – POST и GET. С последним способом мы с вами уже знакомы. Да-да, в нашем запросе странички "http://forum.hellroom.ru/index.php?action=shoutbox;sa=get;xml;row=0;restart" всё, что находится после вопросительного знака – GET-параметры. А вот данные, передаваемые с помощью POST, невооружённым глазом не увидишь, потому что они зашиты в тело запроса.
    Как мы видим, наш запрос имеет 4 POST-параметра: это имя пользователя, пароль, некий параметр cookielength (он нас не интересует) и хеш-сумма пароля. Причём значение пароля – пустая строка, то есть пароль передаётся только в захешированном виде.
    Маленькое отступление. Если вы заметили, мой логин – “Sky Rider” – записан в параметре с “+”, хотя никакого плюса там не должно быть. Это – особая кодировка данных в URL. Беспокоиться об этом нам не нужно – requests кодирует и декодирует информацию автоматически.
    Теперь мы знаем для авторизации всё… кроме того, каким образом пароль хешируется. Так что залезть в исходный код страницы всё-таки придётся. Если вы уже авторизовались в браузере – это зря, выходим из учётной записи и вновь открываем исходный код главной страницы форума. Нас интересуют действия, совершаемые браузером после нажатия кнопки «Войти».


    Собственно говоря, в html-коде формы мы видим все те данные, которые узнали перехватом запроса. Здесь же становитcя очевидным назначение cookielength.
    Кнопка «Войти» имеет тип “submit”, то есть при её нажатии данные отправляются с формы на сервер. Нас интересует параметр формы “onsubmit”, который содержит в себе javascript-код, выполняемый за мгновение до отправки данных.
    Предугадываете следующий шаг? Правильно, открываем отладчик и осуществляем поиск объявления функции “hashLoginPassword”. Отыскалось оно в файле “script.js”.


    Как становится понятно из названия второго параметра, та абракадабра – идентификатор текущей сессии. Первый параметр – сама форма, но это было и так ясно, если вы немного знаете javascript. Кроме этого, во всей функции нас интересует всего одна строчка - та, где осуществляется непосредственно хеширование.
    Мы видим, что для хеширования применяется алгоритм sha1. Этот алгоритм есть в стандартной Питоновской библиотеке hashlib. А вот функция php_to8bit – это уже что-то интересное. Находим её в файле “sha1.js”. Первое, что мы видим – большой блок if … else if… else if… – в нём проверяется кодировка страницы. Кодировка – вещь постоянная и во времени не меняется, поэтому просто проверяем значение переменной “smf_charset” в консоли браузера, на что получаем ответ: “UTF-8”. Мысленно выкидывая все прочие ветки условий, получаем следующий код функции:


    Функция определённым образом преобразует символы Юникода в их восьми битное представление – это можно было предположить и из названия, а комментарий над функцией и вовсе развеивает все сомнения.
    Также в хешировании используется функция “php_strtolower” – можно догадаться, что она опускает строку в нижний регистр. Убедиться в этом легко, даже не глядя её код – проведём в консоли небольшой эксперимент, введя:
Код:
"EnGLiSH РуССкиЙ".php_strtolower()
   В ответ получаем: "english РуССкиЙ". Ай-ай-ай, кириллица-то не опустилась! С другой стороны, при хешировании опускается только логин, а он русским быть не может. Так что никаких неприятностей это недоразумение не вызывает.
    Теперь наступает ответственный момент. Каким образом мы реализуем функцию хеширования в Python? Тут есть два пути: Путь Лени и Путь Чести.

7. Хеширование
7.1. Путь Лени
   Давайте задумаемся. Для хеширования у нас есть всё, что нужно – правда, на javascript, а нам нужно на Python. Точнее, нам нужно, чтобы мы могли получить результат в Python, а каким образом он будет достигнут, нам совершенно неинтересно.
    Решение лежит на поверхности – нужно исполнить код javascript из-под Python и получить результат!
    Каким образом мы это сделаем? Дядя Google спешит на помощь!
    Изначально, когда я писал программу на Питоне второй версии, я нашёл PyV8 – это привязки Питона к V8, javascript-движку, используемому в Google Chrome. Однако на Python третьей версии PyV8 то ли не ещё не портировали, то ли портировали плохо – в общем, попытка его установить закончилась ничем. Зато Google вывел меня на другую библиотеку, и код с её использованием оказался даже короче. Библиотека эта называется PyExecJS.
    Устанавливаем её уже знакомым способом, через консоль (Win+R -> “cmd”):
Код:
pip install pyexecjs
   Сделаем тестовый забег. Открываем интерпретатор Python (Win+R -> “python”) и вводим:
Код:
>>> import execjs
>>> execjs.eval(“1+1”)
   Вот те раз, наш простой код вызвал ошибку! Немного почитав примеры PyExecJS, выясняем, что сама библиотека не является интерпретатором javascript, а лишь использует имеющиеся в системе runtime’ы. Чтобы выяснить, какой же runtime используется у нас, выполняем:
Код:
>>> execjs.get().name
‘JScript’
   Попробуем поставить какой-нибудь другой рантайм. На сайте PyExecJS написано, что библиотека также умеет пользоваться node.js, если он установлен. Качаем его с официального сайта (http://nodejs.org/download/) и устанавливаем. Но мало просто установить, нужно, чтобы PyExecJS его увидел. Для этого заходим в панель управления, где вводим в поиск «переменные среды» -> «Изменение системных переменных среды» -> «Переменные среды». В списке системных переменных находим Path и добавляем в его начало “C:\Program Files\nodejs\;” (либо другой путь, куда вы установили node.js), при этом не забываем поставить точку с запятой! Всё сохраняем и закрываем.
    Перезапускаем интерпретатор Python. Посмотрим, поменялось ли что-нибудь.
Код:
>>> import execjs
>>> execjs.get().name
'Node.js (V8)'
>>> execjs.eval('1+1')
2
   Ура, работает! Теперь мы можем доверить библиотеке код посложнее. Вот только надо сначала понять, какой именно код нам пригодится. Ведь вы могли заметить, что к главной странице форума подключено множество js-файлов, и далеко не все нам нужны.
    Начнём сверху: нам нужно выполнить функцию hashLoginPassword, но её реализация нас не очень устраивает, поскольку она получает на вход объект формы, и результат хеширования записывает в него же. Давайте немного перепишем эту функцию:

Код:
function hashLoginPassword(login, passwrd, cur_session_id) {
    return hex_sha1(hex_sha1(login.php_to8bit().php_strtolower() + passwrd.php_to8bit()) + cur_session_id);
}

    Для того, чтобы функция выполнила свою задачу, ей нужны три другие функции: это hex_sha1, php_to8bit и php_strtolower. Все эти функции находятся в “sha1.js” и активно используют другие функции этого же файла. Так что просто берём и копируем всё содержимое “sha1.js” в одноимённый файл, который кладём рядом с нашим “main.py”. В конец файла дописываем исправленную функцию  “hashLoginPassword”.
    Теперь можно писать код в “main.py”:


    В первой строке функции hashLoginPassword мы открываем файл “sha1.js”, во второй – скармливаем его содержимое execjs’у, а третьей вызываем уже одноимённую функцию, написанную на javascript, передавая ей соответствующие аргументы.
    В main мы тестируем нашу функцию, вызывая её с какими-то произвольными параметрами. То, что было в main до этого, я вынес в другую функцию – print_messages().
    Ну что, посмотрим, что из этого выйдет? Вновь открываем консоль в папке с файлом и исполняем его. У меня получился следующий результат:
Код:
a38ded3b8f09c7eae9da9f00895b49c106f88fab
   Если у вас тоже, то поздравляю, вы всё сделали правильно!

7.2. Путь Чести
   Второй способ получить то же самое – полностью переписать на Python все нужные нам функции. К счастью, как я уже говорил, реализация алгоритма хеширования sha1 присутствует в стандартной библиотеке, функция «опускания» в нижний регистр – тоже. Остаётся лишь переписать php_to8bit.
    Не буду долго томить, вот как всё это выглядит в Питоне:


    Теперь сравним результаты, получаемые при помощи двух реализаций одной и той же функции:


    Исполняя этот код, получаем следующий результат:

Код:
a38ded3b8f09c7eae9da9f00895b49c106f88fab
a38ded3b8f09c7eae9da9f00895b49c106f88fab
True

    Ура, обе функции абсолютно эквивалентны!
    Наверное, вы хотите спросить, почему предыдущий способ назывался Путь Лени, хотя мороки с ним вышло намного больше? Поверьте, на то, чтобы переписать этот несложный код, у меня ушло в 2 раза больше времени, чем на установку PyExecJS и node.js.  А когда я писал на Python 2.7, PyV8 заработал сразу же, «из коробки». Так что в первом случае было больше слов, чем дела, во втором – наоборот.
    В дальнейшем вы можете использовать ту реализацию, которая вам больше нравится. Лично я буду использовать родной Питоновский код, чтобы не зависеть от внешнего javascript’а, да и работает он быстрее. Вы же можете выбрать реализацию с execjs за лаконичность кода и гипотетически бОльшую совместимость с оригинальным алгоритмом.

8. Получаем, наконец, сообщения
   Теперь, когда функция хеширования у нас на руках, ничто нам не мешает авторизоваться на форуме. Ещё раз напомню всё, что нам для этого нужно:
      1) Адрес: http://forum.hellroom.ru/index.php?action=login2
      2) POST-параметры:
        • user – логин пользователя:
        • passwrd – пустая строка;
        • cookielength – насколько долго помнить сессию, оставляем -1;
        • hash_passwrd – хеш-сумма пароля.
    Но для хеширования нам нужен ключ сессии, который вшит прямиком в тело главной страницы форума как второй аргумент javascript-функции hashLoginPassword. Перед тем, как авторизоваться, нам нужно получить этот ключ.
    Напишем несложный код, это делающий:


    Вторая строчка функции – простое регулярное выражение, возвращающее текст, содержащийся между «hashLoginPassword(this, ' » и « ')». На языке регулярных выражений конструкция «(.+?)» означает буквально «что угодно». Обратные слеши перед другими скобками нужны, чтобы эти скобки не рассматривались как часть регулярного выражения.
    Запускаем код и видим выведенный в консоль идентификатор сессии. Здорово!
    А теперь начинается самое интересное. Попробуйте запустить код ещё раз. А потом ещё и ещё.
    Каждый раз мы получаем разный идентификатор!
    Давайте повторим получение идентификатора несколько раз внутри одного скрипта:


    Всё равно у нас в консоли пять разных идентификаторов!
    Если мы попытаемся авторизоваться одним запросом с помощью идентификатора, полученного от другого запроса, ничего не выйдет – ведь у нового запроса и идентификатор уже новый! Как же сделать так, чтобы сервер нас «запомнил»?
    Всё просто – нужно создать сессию.


    Отлично! Теперь сервер видит, что все запросы сделаны в рамках одной сессии. Теперь мы можем одним запросом получить ключ сессии, а вторым авторизоваться, отправив запрос с соответствующими POST-параметрами. Для этого мы воспользуемся функцией post библиотеки requests. Эта функция принимает аргумент с именем data – словарь со всеми параметрами и их значениями. Одноимённый метод есть и у объекта Session.
    В ответ сервер пришлёт нам html-страницу – либо с ошибкой, если мы что-то сделали не так, либо главную страницу форума, если авторизация прошла успешно. Эту страницу мы запишем в файл, чтобы увидеть её в браузере.
    Вот как выглядит процесс авторизации, воплощённый в коде:


    С замиранием сердца запускаем скрипт. Открываем появившийся рядом файл login.html…
    Ура! Если вы всё сделали правильно, то вас приветствует главная страничка форума с вашим любимым аватаром :)
    Ну а теперь дело осталось за малым. Помните функцию print_messages – ту самую, которой мы наивно пытались получить сообщения чата в самом начале? В последующих примерах я убрал её, чтобы не мозолили глаза. Время вернуть её в строй!


    Ура, сервер наконец дал нам ответ! Только… Эм… Это вообще что?

9. Суровый XML
   Прежде, чем мы сможем что-то разобрать в полученных данных, надо записать их по-человечески в файл. Чтобы это сделать, меняем последнюю строчку функции print_messages на:

Код:
with open("chat.xml", "w", encoding="utf8") as f:
    f.write(response.text)

    Перезапускаем скрипт и открываем в Notepad++ сгенерированный файл chat.xml. Выглядит он примерно так:


    Так-то гораздо лучше! Очень похоже на html, не правда ли? Это родственный html’ю формат, называется он – xml. Если вы знакомы с языком гипертекстовой разметки, то и в xml быстро поймёте, что к чему.
    Интересующая нас информация – чатовые сообщения – находится внутри тега “msgs”, то есть между “<msgs>” и “</msgs>”. Конструкция вида <![CDATA[ … ]]> указывает на то, что внутри (вместо «…» ) находится простая символьная информация. Если вы всмотритесь, то внутри – тоже xml. Это как бы xml внутри xml.
    Давайте попытаемся понять его структуру. Сделать это будет немного сложнее, потому что этот внутренний xml никаким образом не форматирован, в отличие от внешнего. Так что придётся самим делить его на строки и расставлять отступы.
    При детальном рассмотрении оказывается, что структура сообщений не так уж сложна:


    Дальше идёт новый тег “tr” с тем же набором внутренних тегов.
    Тут вроде всё понятно. Внутри тега “a” находится имя автора, внутри “span” – дата отправки, а вот дальше… Это ещё что за крокозябры с решётками и амперсандами?
    Оказывается, формат xml (и не только он) позволяет таким образом кодировать символы Unicode. Как можно догадаться, строка «&#1101;» означает символ юникода под номером 1101.
    Можно быстренько написать функцию, которая проводит замены этих конструкций на соответствующие символы. Но зачем изобретать велосипед, когда он давно изобретён?
    Запускаем интерпретатор Python в папке с файлом “chat.xml” (для этого откройте там командную строку и введите “python”). Введите в него несколько строк кода (отступы делаются кнопкой Tab, они важны!):
   Готово! Открываем свежесозданный файл “chat-decoded.xml” и любуемся на нормальные русские символы. Теперь со структурой всё понятно: между открывающим “span” и закрывающим “div” находится само сообщение.
    Как бы нам теперь всё это дело извлечь, желательно попроще?

10. DOM: объектная модель документа
   Стандартным способом работы с xml, придуманным вместе с самим форматом, является DOM – Document Object Model, по-русски – объектная модель документа. Её минимальная реализация входит в стандартную библиотеку Python.
    В этой главе мы ненадолго забудем о сессиях, авторизации и вообще сетевой стороне нашего приложения. Работать мы будем только с содержимым файла “chat.xml”. Для этой цели давайте даже заведём новый файл с исходным кодом, и назовём его “chatxml.py”.
    Давайте, например, выведем число сообщений – оно содержится внутри тега “count”:


    Разберём построчно, что делает этот код.
    

    Вроде несложно, неправда ли? А теперь займёмся настоящим делом – возьмёмся за внутренний xml тега “msgs”:


    Последняя строка функции main выдаёт нам ошибку: “junk after document element”, дословно: «мусор после элемента документа». Дело в том, что, согласно спецификации xml, все теги документа должны быть заключены в один внешний тег. Если вы ещё раз посмотрите в наш “chat.xml”, то он начинается и заканчивается тегом “smf” (заголовок не в счёт). А вот внутренний xml не имеет одного глобального тега.
    Исправляем это недоразумение, заменив предпоследнюю строку функции main на:

Код:
msgs = "<msgs>" + text_node.nodeValue + "</msgs>"

    Но всё равно парсеру что-то не нравится! Теперь ошибка другая: “mismatchet tag”.
    Давайте ещё раз посмотрим на структуру внутреннего xml. А точнее, на ту часть, где находится текст сообщения. Почему это текст заключён в открывающий “span”, но закрывающий “div”? Ответ на этот вопрос могут дать лишь разработчики чата. Но мы ничего не потеряем, если заменим все закрывающие “div” на закрывающие “span”:

Код:
msgs = "<msgs>" + text_node.nodeValue.replace("</div>","</span>") + "</msgs>"

    Ура, теперь наш код не выдаёт никаких ошибок, но и ничего не выводит. Двигаемся дальше:


    Нужно немного пояснить последние две строчки перед print. Дело в том, что если в сообщении нет текста (например, один лишь смайл), то nodeValue вернёт нам вовсе не пустую строку, а None. Чтобы в дальнейшем не было недоразумений (ведь текст должен быть текстом!) мы заменяем None на пустую строку.
    Обратите внимание, что к полученному тексту сообщения мы не применяем unescape. Помните, я говорил, что эти амперсанды и решётки – поддерживаемая xml’ем кодировка? Наш парсер тоже её поддерживает и проводит автоматическую замену таких конструкций на соответствующие символы.
    Здорово, не правда ли? Мы получили всю интересующую нас информацию, почти не производя манипуляций со строкой напрямую :)

11. Собираем воедино
   Теперь у нас есть всё, чтобы написать простой скрипт, выводящий последние сообщения чата. Я позволил себе немного реорганизовать код – чисто косметические, ни на что не влияющие правки.



12. Заключение
   Целью статьи было показать, как можно создать клиентское приложение к ресурсу Всемирной Паутины. То, что мы написали – скорее рабочий прототип, чем полнофункциональная программа: здесь нет возможности ввода логина и пароля извне, нет обработки ошибки авторизации, и самое главное – нет возможности писать сообщения. Однако, как мне кажется, с задачей «показать» я справился. Работы ещё немало, но вся она относится к тривиальным задачам языка и выходит за рамки данной статьи. А написание функции отправки сообщения я оставляю тебе, дорогой читатель, в качестве домашнего задания.
    Большим недостатком написанного кода является его процедурность. Нет, я не отношусь к фанатикам объектно-ориентированного программирования, но в контексте нашей задачи оно более, чем уместно. Расширять функционал нашего процедурного кода будет нелегко, поэтому я взял на себя смелость переписать его в объектно-ориентированном стиле. Также я несколько расширил возможности программы, а именно:
    • При старте программа запрашивает логин и пароль, они не вшиты внутрь;
    • При ошибке авторизации выводится сообщение на экран;
    • Сообщения отображаются сверху вниз, то есть последние сообщения внизу – в консоли так намного удобнее;
    • В сообщениях теперь отображается весь текст, включая тот, который внутри ссылок;
    • Усложнён парсинг: в дате отправки отдельно выделяются число, месяц, час, минута;
    • После запуска программа уходит в вечный цикл, запрашивая обновление у сервера каждые n секунд;
    • Число n берётся из серверного файла shoutbox.js, где оно равно 9 (9000 миллисекунд).
    Исходный код я выложил на bitbucket: https://bitbucket.org/DNAcomp/hellchat. Скачать его можно в загрузках (https://bitbucket.org/DNAcomp/hellchat/downloads, ссылка «Download repository»).
    На этом я откланиваюсь. На прощание хочу вам напомнить слова дяди Бена: «чем больше сила, тем больше ответственность». Помните это во время своих экспериментов.
    До встречи в следующих статьях!
« Последнее редактирование: Июнь 18, 2014, 20:32:09 от Sky Rider » Записан
hitrok
Michael Peaceman
GM Pro user
*

Репутация: 203
Offline Offline

Пол: Мужской
Награды:
1000 сообщений!За постоянность! [50 дней на форуме]За лояльность! [+150 репутации]
API: GameMaker Studio Pro
Деятельность: Если есть исходник, я помогу!
Сообщений: 1205


MEOW!


WWW
« Ответ #1 : Июнь 18, 2014, 18:44:47 »

Статья хорошая, было бы неплохо, конечно, припрятать под спойлеры каждую часть. Написано тоже хорошо, без лишней воды. Но сам выбор статьи мне не понравился Python - фи! Увлекательный Python? Что-то не очень меня заинтересовал он. Используя delphi, можно добиться куда большего. Я бы сказал, для клиента чата - самое оно! Хотелось бы уроки поменьше, и по результативней. Мне были бы интересны уроки более крупного масштаба, по unix, где мы бы учились взламывать или защищать сеть. Можно было бы даже турниры проводить, одна команды пытается взломать другую.
И что-то я еще хотел сказать, но забыл...  sideways
Записан

Dmi7ry
Гл. Администратор
*

Репутация: 1379
Offline Offline

Пол: Мужской
Награды:
5000 сообщений!За постоянность! [200 дней на форуме]За лояльность! [+1000 репутации]За помощь в развитии форума!Знаток Game Maker!За помощь новичкам!
API: GameMaker Studio Master
Деятельность: Code, design
Сообщений: 6626



WWW
« Ответ #2 : Июнь 18, 2014, 19:17:07 »

Используя delphi, можно добиться куда большего.
Во-первых, время Delphi прошло лет 10 назад. Во-вторых, глупо их сравнивать, так как у них разные ниши.
Записан

- А какой, собственно, командой процессора колобок ест черта?
- Командой EAT...
Справка и FAQ в правом верхнем углу...
Sky Rider
Активный участник
*****

Репутация: 70
Offline Offline

Пол: Мужской
Награды:
За постоянность! [10 дней на форуме]
API: Love
Деятельность: Lua, Python
Сообщений: 323


« Ответ #3 : Июнь 18, 2014, 19:31:26 »

hitrok,
Если интересно, у меня есть опыт написания MiTM-бота для одной онлайн-игры whistling Клиент чата с графическим интерфейсом можно и на Python написать (привет PyQt5 и старичку PySide). "Взламывать и защищать сеть" - для меня звучит слишком абстрактно, слабо могу себе представить, что за этим кроется. И опыта у меня здесь нет)

Python - фи!
Delphi - фи!
А статья и правда вышла объёмная, я не ожидал. То ли действительно каждый раздел в спойлер упрятать, то ли просто все куски кода, не знаю даже)

Добавлено: Июнь 18, 2014, 19:42:59
Запрятал под спойлеры все объёмные куски кода, вроде стало лучше
« Последнее редактирование: Июнь 18, 2014, 19:42:59 от Sky Rider » Записан
Psycho
Участник
****

Репутация: 35
Offline Offline

Награды:
За постоянность!
API: Unity 3D
Сообщений: 149


« Ответ #4 : Июнь 18, 2014, 20:32:11 »

unix, взламывать или защищать сеть
у меня есть опыт написания MiTM-бота для одной онлайн-игры
Интересно, продолжайте... :3
« Последнее редактирование: Июнь 18, 2014, 20:48:06 от aloneunit » Записан
JeFiX
GM Pro user
*

Репутация: 90
Offline Offline

Награды:
500 сообщений!За постоянность! [10 дней на форуме]1 место в Весеннем конкурсе. Игра
API: Unity 3D
Сообщений: 536



WWW
« Ответ #5 : Июнь 18, 2014, 20:47:09 »

Используя delphi, можно добиться куда большего.
Во-первых, время Delphi прошло лет 10 назад
Похоже, создатели фл студио так не думают...
Записан
YellowAfterlife
Videogames, I'm afraid
Главный Модератор
*

Репутация: 440
Offline Offline

Пол: Мужской
Награды:
Добавление и перевод новостей о Game Maker!500 сообщений!За постоянность! [200 дней на форуме]За лояльность! [+300 репутации]Настоящий игродел!Знаток Game Maker!...
API: GameMaker Studio 2
Сообщений: 731



WWW
« Ответ #6 : Июнь 18, 2014, 20:59:39 »

Похоже, создатели фл студио так не думают...
Если у тебя программный продукт с десятками тысяч строк кода, то портирование на какую-то другую среду займет колоссальное количество времени и средств. Обычно в таких случаях плюются, но сидят смирно. С GameMaker аналогичная ситуация (использование Delphi лишает редактор кросс-платформерности, при том что все раннеры уже давно портированы), но, как было сказано, перепись редактора может занять месяца, поэтому к этому всё ещё подходит.
Записан

Durved
Новичок
*

Репутация: 0
Offline Offline

API: Game Maker 8.0 Pro
Сообщений: 2


« Ответ #7 : Июль 22, 2014, 23:01:19 »

Используя delphi, можно добиться куда большего.
Всё завистит от уровня знаний и прямоты рук, во-первых. Во-вторых это разные языки используемые для разных целей. Кстати на Python написан EVE Online.
Записан
jaxx
Новичок
*

Репутация: 0
Offline Offline

API: Unity 3D
Сообщений: 1


« Ответ #8 : Март 19, 2015, 10:42:52 »

На Python написано много проектов. Например Worl of Tanks. Я считаю, что он самый жирный скриптовый язык.
Статья хорошая. Я вот сейчас пытаюсь переехать с php на python. И тут отличный пример как, что делать.
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  

HellRoom Games © 2006-2012 All Rights Reserved
Powered by SMF 1.1.21 | SMF © 2013, Simple Machines
Страница сгенерирована за 0.273 секунд. Запросов: 33.