пятница, 12 июля 2013 г.

Рекурсия с помощью рекурсивного цикла в jinja2.

Код выводит "дерево-лесенку" в виде списка, каждая "ступенька" которой находится в списке parents.

{#
{% for parent in (0,) recursive %} это "def func():", а
{{ loop((0,)) }} - это func().
#}
{% for parent in (0,) recursive %}
  • {{ parents.pop() }}
    {% if parents %}
      {{ loop((0,)) }}

    {%- endif %}

  • {% endfor %}

    Cоздание собственных операторов в sqlalchemy

    В postgresql есть такие типы данных как INET, CIDR, но полноценно в sqlalchemy они не поддерживаются. То есть, например, запрос для поиска подсетей
    SELECT * FROM nets WHERE ip >> '10.10.10.10' так просто сформулировать на sqlalchemy не получится. Для этого нужно переопределить оператор >> у типа данных CIDR и создавать запросы с его помощью:

    class Cidr(postgresql.CIDR):
    '''
    Делаем свой тип колонки для того, чтобы была возможность использовать
    операторы >> и <<
    '''

    class comparator_factory(postgresql.CIDR.Comparator):
    def __lshift__(self, other):
    return self.op("<<")(other)

    def __rshift__(self, other):
    return self.op(">>")(other)

    class Net(Base):
    __tablename__ = 'nets'
    id = Column(Integer, primary_key=True)
    ip = Column(Cidr)

    db.session.query(Net).filter(Net.ip>>'10.10.10.10')

    четверг, 28 февраля 2013 г.

    http-снифферы.

    Только Windows:

    1. HttpAnalyzer - классный сниффер, есть все необходимые плюшки для http-сниффера: "pretty" отображение POST DATA; возможность открыть ответ на запрос в браузере; плюс, это не debugging proxy, а значит можно дебажить работу скриптов через прокси/соксы, т.к. перехватываются все запросы. Из минусов: платный; подвисает при остановке сниффинга; открытие ответа частенько тупит и открывается не браузер, а блокнот, разобраться почему так происходит у меня не вышло.

    2. Fiddler - это как раз таки debugging proxy, то есть является проксей через которую надо направлять запросы, которые хотим увидеть. То есть отдебажжить запросы скрипта через прокси не получится, зато нет остальных минусов HttpAnalyzer-а, а остальные плюшки присутствуют. После HttpAnalyzer-а перешел на этот сниффер, удобно, полностью устраивает.


    Linux (в общем то, кросплатформенное):

    1. Charles - неплохой proxy, если бы не одно "НО", нельзя открыть ответ запроса в браузере, приходилось брать ответ, сохранять его как html и открывать в firefox. Платный, если бы не извращение с сохранением html можно было бы даже купить.

    2. Paros proxy, burp proxy и т.д... - когда искал сниффер под Linux находил кучу этих проксей/снифферов, уже не помню что там и как, разве что burp proxy можно попробовать посмотреть.

    3. Webscarab - похоже вот оно, сниффер под Linux, не идеальный и не такой удобный как fiddler, но с которым вроде бы можно работать. Вроде бы - потому что практики с ним не было, пока только поверхностный обзор. Первый минус что бросился в глаза - нельзя очистить окно от запросов, а в остальном терпимо. А, ну, внешний вид УГ - Java чо)


    Прочее:

    1. HttpFox - плагин для фаерфокса, очень похожий на HttpAnalyzer, юзабелен, но снифит только браузер.

    2. CommView - прикольный сниффер общего назначения, начинал с него. Платный, windows only.

    3. Wireshark - классика вроде бы, кроссплатформенен, плотно работать с ним не приходилось.


     

    вторник, 13 ноября 2012 г.

    Памятка по обновлению libcurl Debian.

    Памятка примерная, без команд, названия пакетов неточные.

    1) Скачал курл с оф. сайта, скомпилил, установил.

    2) Удалил libcurl3-gnutls и libcurl3.

    3) Установил пакеты python-dev и libcurl-openssl-dev.

    4) pip install pycurl. Получил pycurl с последней версией libcurl, а также с openssl вместо gnutls. Не знаю, что лучше, просто openssl было привычнее, плюс какая-то ошибка была у gnutls.

    пятница, 1 июня 2012 г.

    grab.spider tricks.

    1) Дефолтный valid_status для всех заданий. Нужно переопределить метод valid_response_code, например таким образом:


    def valid_response_code(self, code, task):
    return True

    В данном примере все ответы будут считаться валидными.

    2) Список стартовых урл, initial_urls, может оказаться просто ненужным. Чтобы не делать лишнее действие, можно запускать spider не через task_initial, а через task_generator:

    class MainSpider(Spider):
    #initial_urls = ['http://www.google.com/']

    def task_generator(self):
    url = "http://www.google.com/"
    yield Task(name='page', url=url)

    Это один из способов, но далеко не единственный.

    вторник, 29 мая 2012 г.

    Собираем pycurl под windows (python 2.7).

    Наверняка сейчас под windows pycurl у всех стоит вот с этого сайта , к сожалению, в этой версии есть такой неприятный баг, который неизвестно при каких условиях проявляется. Таким образом, необходимо собрать версию поновее, ибо больше сайтов со сборками pycurl я не видел.

    Всё делал вот по этой статье http://curl.haxx.se/mail/curlpython-2009-11/0010.html за исключением небольшого ньюанса с ассемблером.

    Для работы необходимо установить:

    1) Visual Studio C++ Express (я ставил 2008).

    2) Perl (я качал вот этот http://www.activestate.com/activeperl/downloads)

    3) Ассемблер Nasm, а также добавить путь до bin в переменную path (брать отсюда http://www.nasm.us/)

    Чтобы собрать pycurl нам надо собрать по статической библиотеке(.lib) из исходников для каждого из 3х составляющих, в скобках указаны версии которые я использовал:

    zlib - http://www.zlib.net/ (1.2.7)
    openssl - http://www.openssl.org/source/ (1.0.1с)
    curl - http://curl.haxx.se/download.html (7.26.0)

    И, конечно же, нам понадобятся исходники самого pycurl:
    pycurl - http://pycurl.sourceforge.net/download/ (7.19.0)

    Итак, создаем на диске С: в корне папку build и помещаем в неё все 4 папки:

    • curl-7.26.0

    • openssl-1.0.1с

    • pycurl-7.19.0

    • zlib-1.2.7

    Открываем Visual C++ Command Line, переходим в папку с zlib и прописываем: nmake -f win32\MakeFile.msc

    Без проблем собирается и в корне появляется файл zlib.lib

    На очереди openssl, переходим в его папку и прописываем команды:

    1) perl Configure VC-WIN32

    2) ms\do_nasm.bat

    3) nmake -f ms\nt.mak

    После компиляции в out32 должны появится два файла *.lib

    Переходим в папку c curl и пишем: nmake vc-ssl-zlib WINDOWS_SSPI=1 VC=vc9 OPENSSL_PATH=С:\build\openssl-1.0.1c ZLIB_PATH=C:\build\zlib-1.2.7

    Наконец, собираем pycurl. Для этого качаем скрипт, распаковываем содержимое архива в папку с pycurl, переходим с помощью командной строки в неё и пишем: python setup_win32_ssl_own.py --curl-dir=..\curl-7.26.0 --openssl-dir=..\openssl-1.0.1с --zlib-dir=..\zlib-1.2.7 bdist_msi

    В папке dist должен появится файл pycurl-ssl-7.19.0.win32-py2.7.msi

    четверг, 23 февраля 2012 г.

    Парсер Grab:Spider.

    Вот ссылка о нём. Парсер на пару строк, парсит 10к популярных фильмов с кинопоиска:

    from grab.spider import Spider, Task, Data
    from grab.tools.logs import default_logging
    from grab import Grab
    import re


    class KinoSpider(Spider):
    def task_initial(self, grab, task):
    for xpath in grab.tree.xpath('//*/div[@class="stat"]/div/a'):
    name = xpath.text_content().encode('utf8')
    name = re.sub("\(.*?\)",'',name)
    self.out.write(name+"\n")
    self.out.flush()

    def main():
    initial_urls = ["http://www.kinopoisk.ru/level/56/day/2012-02-22/page/{0}/".format(x) for x in xrange(1,51)]
    threads = 50
    default_logging(grab_log="log/log.txt")
    fl = open("out.txt","w")


    bot = KinoSpider(thread_number=threads,network_try_limit=2)
    bot.initial_urls = initial_urls
    bot.out = fl

    try: bot.run()
    except KeyboardInterrupt: pass
    fl.close()

    print bot.render_stats()

    if __name__ == '__main__':
    main()

    Процесс парсинга занимает 3-5 секунд!

    суббота, 11 февраля 2012 г.

    Свой дистрибутив Python для Windows.

    Создал свой дистрибутив python с блекджеком и шлюхами (grab, lxml, pycurl, mysql, sqlalchemy, setuptools). Сделал это с помощью программы installshield, просто закинул необходимые модули в site-packages. Давно это надо было сделать, а то запарило уже всем подряд объяснять, скачай тут, запусти то, пропиши setup.py install...

    понедельник, 30 января 2012 г.

    sqlalchemy

    Sqlalchemy - фреймворк для работы с базами данных с технолгией ORM. Почитать про него можно тут. Моей задачей было понять, как использовать этот фреймворк в потоках.


    import sqlalchemy
    from sqlalchemy.orm import sessionmaker, scoped_session

    connection = "mysql://root:@localhost/mydatabase"
    # pool_size - указывает сколько запросов хранить в очереди, если будет больше, вылетит exception
    engine = sqlalchemy.create_engine(connection, pool_size=30)
    # Создаем объект Session и дальше во всех потоках используем только его.
    Session = scoped_session(sessionmaker(bind=engine))

    #Accounts - моя таблица, к которой мне нужно делать запросы, код по настройке отображения я опустил.
    accounts = Session.query(Accounts)
    account = accounts[0]

    Один интересный момент, account, созданный в одном потоке и переданный в другой, изменить не получится.
    Если мы поменяем атрибуты account в новом потоке и выполним Session.commit() - таблица не обновится.

    суббота, 14 января 2012 г.

    Threading и KeyboardInterrupt.

    Как организовать пул потоков? Допустим у меня есть 100 данных (пусть они находятся в каком-либо списке) и все их нужно переработать в 10 потоков. Стартуем 10 потоков, в каждый передаем по 1 данному, следим за потоками, как только один отработал, стартуем новый и так пока не закончатся данные... В общем, это не то, чтобы бред, но есть способ поудобней.

    За всё время работы скрипта, стартует только 10 потоков, просто каждый из них, отработав со своим 1 данным, не дохнет, а берет из списка следующее 1 данное и делает с ним эту же работу:


    # -*- coding: utf-8 -*-
    import threading, Queue, time
    import traceback



    class Worker(threading.Thread):
    def __init__(self,queue):
    threading.Thread.__init__(self)
    self.__queue = queue

    def run(self):
    while True:

    try: item = self.__queue.get_nowait() # ждём данные
    except Queue.Empty: break # данные закончились, прекращаем работу

    try: self.work(item) # работа
    except Exception: traceback.print_exc()

    time.sleep(0.5)
    self.__queue.task_done() # задача завершена
    return
    def work(self,item):
    print item
    #pass


    def main():
    # Выводим в 5 потоков цифры от 1 до 100.
    queue = Queue.Queue()
    num_threads = 5 # 5 потоков

    for x in xrange(100):
    queue.put(x) # заносим данные в очередь
    for i in xrange(num_threads):
    t = Worker(queue) # создаем поток
    t.start() # стартуем
    time.sleep(0.1) # чтобы в консоли друг на друга не накладывались

    queue.join() #блокируем выполнение программы, пока не будут израсходованы данные.

    print "Done!"
    if __name__ == '__main__':
    main()


    И, в общем, хотелось бы еще, чтобы можно всё это в любой момент можно было бы остановить. Но поскольку мы выполнение программы блокируем с помощью queue.join(), то скрипт наши Ctr+C банально "не услышит". Я начал искать, что делать в таком случае, нашел вот такое решение, но у меня оно работало абы как. А потом, поразмыслив, я сам придумал решение, которое оказалось ужасно простым.


    # -*- coding: utf-8 -*-
    import threading, Queue, time
    import traceback



    class Worker(threading.Thread):
    def __init__(self,queue):
    threading.Thread.__init__(self)
    self.__queue = queue

    self.kill_received = False # флаг прекращения работы
    def run(self):
    while not self.kill_received:

    try: item = self.__queue.get_nowait() # ждём данные
    except Queue.Empty: break

    try: self.work(item)
    except Exception: traceback.print_exc()

    time.sleep(0.5)
    self.__queue.task_done() # задача завершена
    self.__queue.put(item) # зациклим
    return
    def work(self,item):
    print item


    def main():
    queue = Queue.Queue()
    num_threads = 5 # 5 потоков

    threads = []
    for x in xrange(100):
    queue.put(x) # заносим данные в очередь
    for i in xrange(num_threads):
    t = Worker(queue) # создаем нить
    threads.append(t)
    t.start() # стартуем
    time.sleep(0.1)


    #Пока в "живых" не останется только главный поток, ждем.
    while threading.activeCount()>1:
    try:
    time.sleep(1)
    except KeyboardInterrupt:
    print "Ctrl-c received! Sending kill to threads..."
    for t in threads:
    t.kill_received = True # даем сигнал о завершении всем потокам
    print "Done!"
    if __name__ == '__main__':
    main()

    четверг, 29 сентября 2011 г.

    Multiprocessing

    Узнал про модуль spynner, это такой управляемый браузер. Если на страничке много трудноподдающегося JavaScript-а, то вместо urllib2 или curl можно попробовать натравить на него spynner. Основы работы с ним можно почитать здесь. В общем был он нужен мне, но проблема в том, что он достаточно медленный, а работать в несколько потоков с ним не представляется возможным, так он берет своё начало от GUI-класса библиотеки Qt, которые в свою очередь можно использовать только в main-потоке. Поэтому вместо многопоточности пришлось думать о многопроцессовости. Ну это тоже самое если сделать однопоточный скрипт и
    запустить одновременно N его копий. Про многопоточность я уже написал один пост, который полностью состоит из быдлокода=) С тех пор, я чутка поумнел и для организации потоков наследуюсь от класса Thread. С многопроцессовостью всё аналогично, только классы другие, а методы и структура вся та же.

    # coding: utf8
    import spynner
    from spynner import browser
    import multiprocessing
    from multiprocessing import Process


    class Serfer(multiprocessing.Process):
    def __init__(self,queue):
    multiprocessing.Process.__init__(self)
    self.__queue = queue # наша очередь заданий
    self.kill_received = False # На всякий случай переменная, вдруг надо будет всё остановить
    def run(self):
    while not self.kill_received:
    try: item = self.__queue.get_nowait() # ждём данные
    except Queue.Empty: break

    error = True
    try: error = self.serfing(item)
    except: traceback.print_exc()

    time.sleep(0.1)
    self.__queue.task_done() # задача завершена
    if error: self.__queue.put(item) # Если была ошибка, то еще раз с этими данными
    return
    def serfing(self,url):

    br = browser.Browser() # запускаем браузер
    br.create_webview()
    br.show() # отображаем
    br.load(url) # загружаем страницу
    br.wait_a_little(75)

    br.destroy_webview()
    br.close() # закрываем браузер

    def main():
    queue = multiprocessing.JoinableQueue() # создаем очередь заданий
    processes = 5
    # Урлы по которым надо будет перейти в браузере
    urls = ["http://ya.ru","http://google.ru","http://yahoo.com",
    "http://bing.com","http://rambler.ru"]

    for url in urls:
    queue.put(url) # заносим данные в очередь
    for i in xrange(processes):
    t = Serfer(queue) # создаем процесс
    t.start() # стартуем
    time.sleep(0.1)
    queue.join() # приостанавливаем дальнейшее выполнение кода, пока очередь не опустошится
    print "Done"
    if __name__ == '__main__':
    main()

    четверг, 22 сентября 2011 г.

    Декодировка писем.

    Узнал на форуме модуль для декодировки MIME. Когда в письме, которое мы тянем скриптом с какого-либо ящика, находится вот такая абракадабра:
    =80=D0=BE=D0=B2=D0=B0=D0=BB=D0=B8 =D1=83=D1=87=D1=91=D1=82=D0=BD=D1=83=D1=
    =8E =D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8C =D0=B2 =D0=A2=D0=B2=D0=B8=D1=82=D1=

     нужно использовать модуль quopri

    import quopri
    s = '''=D0=95=D1=81=D0=BB=D0=B8 =D0=B2=D1=8B =D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B8=
    =D0=BB=D0=B8 =D1=8D=D1=82=D0=BE =D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=
    =D0=B8=D0=B5 =D0=BF=D0=BE =D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B5, =D0=B8 =D0=
    =B2=D1=8B =D0=BD=D0=B5 =D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B8=D1=
    =80=D0=BE=D0=B2=D0=B0=D0=BB=D0=B8 =D1=83=D1=87=D1=91=D1=82=D0=BD=D1=83=D1=
    =8E =D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8C =D0=B2 =D0=A2=D0=B2=D0=B8=D1=82=D1=
    =82=D0=B5=D1=80=D0=B5, =D0=BF=D1=80=D0=BE=D0=B9=D0=B4=D0=B8=D1=82=D0=B5 =D0=
    =BF=D0=BE =D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B5:
    http://twitter.com/account/not_my_account/sdfaasdfldfjlas/65558-5ED38-131=
    666'''
    print quopri.decodestring(s).decode('utf8')

    Получаем:
    Если вы получили это сообщение по ошибке, и вы не регистрировали учётную запись в Твиттере, пройдите по ссылке:
    http://twitter.com/account/not_my_account/sdfaasdfldfjlas/65558-5ED38-131666

    Еще письмо может быть закодировано в base64. Среди стандартных модулей Python есть один для работы с этой кодировкой:

    import base64
    body = base64.b64decode(body)

    четверг, 25 августа 2011 г.

    Использование TOR'а в pycurl

    Писать много неохота. Поэтому коротенечко: нужно установить Vidalia Bundle. Затем модуль для Python - TorCtl. Затем проксей курлу указываем 127.0.0.1:9050 с типом Socks5. Всё, таким образом мы лазим серферим через какую-то проксю тора. Для смены этой прокси нам надо приконнектиться к тору и сбросить соединение с помощью модуля TorCtl.

    conn = TorCtl.connect()
    conn.sendAndRecv('signal newnym\r\n')

    суббота, 21 мая 2011 г.

    Динамический импорт модуля.

    Нужно было реализовать что-то типа плагинов: есть скрипт, представляющий из себя основу, и набор модулей для этой основы, для разных сервисов. То есть появился новый сервис - написал модуль, закинул в папку к скрипту - всё работает. Делается это довольно просто с помощью функции __import__:


    module = "common_module" // название файла модуля
    dyn_module = __import__(module)
    Class = dyn_module.Dyn_Class // Dyn_Class - класс из модуля

    суббота, 19 марта 2011 г.

    Segmentation fault при использовании pycurl в Linux.

    Написал клиенту простейший скрипт с использованием pycurl (я его всегда, кстати, использую), который состоит буквально из одного get-запроса, а при использовании этого скрипта под Linux, начала периодически валиться ошибка Segmentation fault и обрывать работу скрипта. Как выяснилось надо было вставить следующий кусочек кода, чтобы этого не происходило:

    import sys
    import pycurl

    try:
    import signal
    from signal import SIGPIPE, SIG_IGN
    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
    except ImportError:
    pass

    Пока не пробовал, но чтобы не забыть решил записать.

    воскресенье, 13 марта 2011 г.

    Использование __setattr_.

    Очень часто использую файл настроек для скрипта посредством модуля ConfigParser. И как-то немножко поднадоело писать в несколько строк каждый раз:

    key = config.get("Basic","Key")
    ns1 = config.get("Domains","NS1")
    ns2 = config.get("Domains","NS2")


    Короче, захотел я, чтобы доступ к настройкам выглядел примерно так: ns1 = config.Domains['ns1'], когда надо обратился к ней, вне зависимости от того, сколько настроек в файле. Самая хитрость - это сделать свойством класса секцию файла настроек. Именно для этого нужна функция __setattr__. Вот так у меня всё в итоге получилось:

    class ConfigDict(object):
    def __init__(self,path):
    config = ConfigParser.RawConfigParser()
    config.read(path)
    for section in config.sections():
    dct = {}
    for option in config.options(section):
    dct[option] = config.get(section,option)
    self.__setattr__(section,dct)


    Используется это вот так:


    def main():
    conf = ConfigDict("settings.cfg")
    threads = conf.Basic['threads']
    ns1 = conf.Domains['ns1']
    if __name__ == '__main__':
    main()

    Ну и сам файл настроек settings.cfg:

    [Basic]
    THREADS = 1 ; потоки
    [Domains]
    NS1 = ns1.0adz.com

    воскресенье, 14 ноября 2010 г.

    Работаем с ftp через curl.

    Подробной статьи для чайников я не нагуглил, даже на php, а ftp-uploader написать было необходимо. Поэтому приведу куски кода, которые мне помогли. Во-первых, нам надо залогиниться, юзаем опцию:

    # userpwd = "login:parol"
    curl.setopt(pycurl.USERPWD,userpwd)

    Во-вторых нам надо заливать файлы, делается это так:

    size = os.path.getsize(path)
    fp = open(path,"rb")
    # url = ftp://ftp.host.com/directory/file.txt
    curl.setopt(pycurl.URL,url)
    curl.setopt(pycurl.UPLOAD,1)
    curl.setopt(pycurl.INFILE,fp)
    curl.setopt(pycurl.INFILESIZE,size)
    curl.perform()

    В-третьих, нам надо создавать папки, а для этого нужно юзать команды ftp. Делается это так:

    quote = "MKD {0}".format(folder)
    curl.setopt(pycurl.URL,host)
    curl.setopt(pycurl.POSTQUOTE,[quote])
    curl.setopt(pycurl.UPLOAD,0)
    curl.perform()

    Пока всё, этого мне хватило, чтобы написать uploader. Пришлось немного попариться при создании вложенных папок, но вроде всё работает=) Прокси юзать также как и для http.

    пятница, 6 августа 2010 г.

    Переход на python 2.7?

    На форуме дали ссылку на популярные модули для Питона, причем как для 2.6, так и 2.7. Все модули в виде exe-файлов, не надо набирать в консоли setup.py install и тому подобное. Сама ссылка очень порадовала, все необходимые мне модули там есть: pycurl, mechanize(ClientForm), Pil, и т.д.; и заставила задуматься о переходе на версию 2.7.

    понедельник, 14 июня 2010 г.

    Пути к файлам


    # добавление папки в которой искать модули
    sys.path.append(path)
    # определение директории файла
    os.path.dirname(path)
    # смена рабочей директории
    os.chdir(directory)
    # переменная, в которой хранится путь до текущего скрипта
    __file__
    # делаем, чтобы все относительные пути
    # вычислялись относительно пути до скрипта
    os.chdir(os.path.dirname(__file__))

    суббота, 29 мая 2010 г.

    Распознавание captcha.

    Решил я научиться распознавать самую простую капчу, какая только существует. Вот она:.  Это png-изображение с размерами 90x25.  То есть каждый символ занимает 18 пикселей, всего 5 символов. Весь алфавит этой капчи состоит из 16 символов: 0123456789abcdef. Таким образом мне пришлось собрать все картинки для этого алфавита, нарезать из них буковки, а затем склеить в одну картинку: . Ну и весь алгоритм сводится к тому, чтобы сравнивать все символы по очереди с алфавитом. Для этого я воспользовался библиотекой PIL:
    from PIL import Image, ImageColor
    def cmp_image(img1,img2):
        return list(img1.getdata()) == list(img2.getdata())
    def decode(img):
        # Весь алфавит
        bigimg = Image.open(r"all.png")
        # Соответсвие букв изображениям
        alpha = "0123456789abcdef"
        res = ""
        for x in xrange(5):
            tmp = Image.new('RGB', (18, 25))
            tmp.paste( img.crop((x*18,0,x*18+18,25)))
    
            letter = "a"
            for y in xrange(16):
                ideal = Image.new('RGB', (18, 25))
                ideal.paste(bigimg.crop((y*18,0,y*18+18,25)))
                rms = cmp_image(ideal,tmp)
                if rms:
                    letter = alpha[y]
                    break
            res += letter
        return res
    img = Image.open(r"e:\image.png")
    print decode(img)