1. Теперь за форумную активность начисляются биткоины и другие криптоденьги. Подробнее.
    Скрыть объявление
  2. Появилась архивная версия форума arhiv.xaker.name, где собраны темы с 2007 по 2012 год.
    Скрыть объявление

Простое распознавание символов методом частичных гистограмм.

Тема в разделе "Статьи", создана пользователем Сашка Суралмашка, 17 июн 2016.

  1. Сашка Суралмашка
    Симпатии:
    0
    Простое распознавание символов методом частичных гистограмм. Простое распознавание символов методом частичных гистограмм. Простое распознавание символов методом частичных гистограмм. Простое распознавание символов методом частичных гистограмм. Простое распознавание символов методом частичных гистограмм. Простое распознавание символов методом частичных гистограмм. Простое распознавание символов методом частичных гистограмм. Простое распознавание символов методом частичных гистограмм. О чем эта статья
    В этой статье будет описано распознавание простого текста, а именно номера телефона продавца с Авито простым способом. Чтобы показать, что мы тута не в бирюльки играем, этот метод был назван наукообразным (и даже отражающим суть) термином.

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

    Предмет статьи
    Защита avito от парсинга и метод ее обхода были рассмотрены в статье за авторством anshlag. Уже понятно, как получить картинку с телефоном. Рассмотрим некоторые особенности, позволяющие довольно просто эту картинку распознать.

    Картинка с телефоном представляет из себя PNG-файл с альфа-каналом, 102 пикселя в ширину, 16 пикселей в высоту. Например, компоненты картинки будут таковы: основной канал , альфа-канал . Забегая вперед, скажу, что в данному случае нам хватит данных из одного канала для успешного распознавания.

    Далее, набрав N картинок с телефонами, стекируем их (т.е. располагаем друг над другом в одном большом коллаже), используя, например, такой кодес:

    from PIL import Image
    from glob import glob
    filenames = glob('images/phone*.png')
    number_of_images = len(filenames)
    width, height = Image.open(filenames[0]).size
    total_height = height*number_of_images
    stacked = Image.new('RGBA', (width, total_height))
    for index,filename in enumerate(filenames):
    image = Image.open(filename)
    stacked.paste(image,(0,index*height))
    stacked.save('stacked.png')
    Получаем примерно такой результат:



    Проводим вертикальные границы каждой из цифр:



    Выясняется, что каждая цифра имеет размер 6 на 10 пикселей. Также выясняется, что для каждой картинки с номером положение каждой цифры статично. Таким образом, имя картинку с номером, мы можем легко разбить ее на отдельные цыферки.

    Собрав отдельные цифры (используя главный канал), получаем: Как видим, цифры выглядят довольно своеобразно, но в деле распознавания образов предпочтительные для человека и компьютера качества картинки редко совпадают. Другими словами, зачастую страшные кривые цифры могут быть легко распознаны компьютером, а красивый шрифт с вензелями - нет.

    Ну а теперь сам секретный метод. Для каждой цифры подсчитываем количество черных пикселей. Чтобы не производить подсчет вручную, вычисляем гистограмму, получив при этом список из 256 значений, каждое из которых равно количеству пикселей с соответствующим значением (от 0 до 255). Таким образом, первый элемент списка (доступный по индексу 0) равен количеству черных пикселей.
    Выполнив следующий код

    from PIL import Image
    for digit in xrange(0,10):
    filename = '{}.png'.format(digit)
    image = Image.open(filename)
    width, height = image.size
    histogram = image.histogram()
    print '{} ----> {},{}'.format(digit,histogram[0],histogram[-1])
    Получим вывод



    Для каждой цифры имеем количество черных (значение пикселя = 0) и белых (значение пикселя = 255) пикселей. Стоит отметить, что во-первых, сумма этих двух значений для каждой цифры равна 60 (=6x10, то есть количеству всех пикселей цифры), то есть в главном канале изображения цифры полутонов нет, и во-вторых, каждая цифра может быть однозначно идентифицирована по количеству черных (или белых) пикселей. Именно второе наблюдение позволяет нам распознавать номера телефонов без особых хитростей.

    Соединяя воедино и воспользовавшись находками из статьи anshlag'а, получаем следующий код.

    import requests
    from lxml import html
    from re import findall
    from PIL import Image
    import base64
    from StringIO import StringIO
    from collections import namedtuple
    import sys

    Advertisement = namedtuple('Advertisement','id price phone')

    URLS = ["https://www.avito.ru/moskva/telefony/novye_iphone_5s_16gb_lte_garantiya_original_731816493",
    "https://www.avito.ru/moskva/telefony/zaryadnoe_ustroystvo_dlya_ayfon_5.6_791740810",
    "https://www.avito.ru/moskva/telefony/optom_i_v_roznitsu-vse_v_nalichii-magazin-24_678666846",
    "https://www.avito.ru/moskva/telefony/iphone_6_1286416_-optom_i_v_roznitsu-24chasa_578416635",
    "https://www.avito.ru/moskva/telefony/iphone_44s_chehol-zaryadka_522990551",
    ]

    def get_image_pkey(ad_id, ad_phone):
    if ad_id and ad_phone:
    ad_subhash = findall(r'[0-9a-f]+', ad_phone)
    if int(ad_id) % 2 == 0:
    ad_subhash.reverse()
    ad_subhash = ''.join(ad_subhash)
    return ad_subhash[::3]

    def recognize(base64_image):
    image = Image.open(StringIO(base64.b64decode(base64_image))).convert('LA')
    left_margins = (2,13,20,27,39,46,53,65,73,84,92)
    DIGITS_BY_HIST = {44: '0', 16: '1',38: '2', 43: '3', 28: '4', 41: '5', 45: '6',25: '7', 53: '8', 47: '9'}
    digits=[]
    for index, left_margin in enumerate(left_margins, start=1):
    box = (left_margin,4, left_margin+6,14)
    digit = image.crop(box)
    characteristic = digit.histogram()[0]
    digits.append(DIGITS_BY_HIST.get(characteristic,'x'))
    return ''.join(digits)


    def parse(url):
    ad_id = url.split('_')[-1]
    headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'}
    s = requests.Session()
    response = s.get(url, headers=headers)
    doc = html.fromstring(response.content)
    script = doc.xpath('//div[@class="clearfix" and @itemscope and @itemtype="http://schema.org/Product"]/script')
    if not script:
    return None
    script = script[0].text
    price, phone_hash = None, None

    for line in script.split('\n'):
    line = line.strip()
    if not line:
    continue
    var_name, var_value = [item.strip('\'";') for item in line.split(' = ')]
    if var_name=='avito.item.price':
    price = int(var_value)
    if var_name=='avito.item.phone':
    phone_hash = var_value
    pkey = get_image_pkey(ad_id, phone_hash)
    image_url = 'https://www.avito.ru/items/phone/{}?pkey={}'.format(ad_id,pkey)
    headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36',
    'x-requested-with':'XMLHttpRequest', 'referer':url}
    image = s.get(image_url, headers=headers).json().get('image64')
    phone = None
    if image:
    base64_image = image.split(',')[1]
    phone = recognize(base64_image)

    result = Advertisement(ad_id, price, phone)
    return result

    if __name__=='__main__':

    if len(sys.argv)>1:
    url = sys.argv[1]
    print "Parsing URL {}".format(url)
    ad = parse(url)

    if ad:
    print "Result: ID {}, phone {}, price {}.".format(ad.id,ad.phone, ad.price)
    else:
    print "Could not parse URL. "

    else:
    TEMPLATE = "{:10} | {:11} | {:>6}"
    print TEMPLATE.format('ID','Phone','Price')
    for url in URLS:
    ad = parse(url)
    if ad:
    print TEMPLATE.format(ad.id, ad.phone, ad.price)
    else:
    print TEMPLATE.format(None,None,None)
    Чтобы использовать его, сначала установите зависимости (используется python 2.7):

    pip install requests
    pip install lxml
    pip install pillow
    Использование: передайте URL объявления аргументом к скрипту:

    python avito-parser.py https://www.avito.ru/moskva/telefony/iphone_5s_16gb_silver_794946127
    и получите вывод:

    Result: ID 794946127, phone 89645892690, price 12999.
    При запуске без аргументов скрипт обрабатывает захардкоженные ссылки, со временем они могут устареть.

    python avito-parser.py
    ID | Phone | Price
    731816493 | 89067128880 | 11990
    791740810 | 89128821808 | 500
    678666846 | 89651386888 | 9900
    578416635 | 89651386888 | 19900
    522990551 | 89055622660 | 1200
    В некоторых случаях, например при ограничении количества запросов на сервере (rate-limit) или появлении капчи, скрипт может вываливаться с исключением. Следует иметь в виду, что скрипт представляет из себя proof-of-concept, при желании можете самостоятельно его доработать под свои нужды.
     
    17 июн 2016
  2. Сашка Суралмашка
    Симпатии:
    0
    Всё пошло не так( Прошу прощения. как удалить?
    Беспорядок получился(
     
    17 июн 2016
  3. admin
    admin Команда форума Админ
    Симпатии:
    26
    Ссылку на источник дай, я приведу в порядок.
     
    17 июн 2016
  4. Сашка Суралмашка
    Симпатии:
    0
    17 июн 2016

Поделиться этой страницей

Загрузка...