Русский
Русский
English
Статистика
Реклама

Из песочницы Как задача из классического сбора данных, перешла в решение простенькой задачи MNIST. Или как я спарсил сайт ЦИК

В один из будничных дней, под вечер, от моего начальника прилетела интересная задачка. Прилетает ссылка с текстом: хочу отсюда получить все, но есть нюанс. Через 2 часа расскажешь, какие есть мысли по решению задачи. Время 16:00.

Как раз об этом нюансе и будет эта статья.

Я как обычно запускаю selenium, и после первого перехода по ссылке, где лежит искомая таблица с результатами выборов Республики Татарстан, вылетает оно

image

Как вы поняли, нюанс заключается в том, что после каждого перехода по ссылке появляется капча.

Проанализировав структуру сайта, было выяснено, что количество ссылок достигает порядка 30 тысяч.

Мне ничего не оставалось делать, как поискать на просторах интернета способы распознавания капчи. Нашел один сервис

+ Капчу распознают 100%, так же, как человек
Среднее время распознавания 9 сек, что очень долго, так как у нас порядка 30 тысяч различных ссылок, по которым нам надо перейти и распознать капчу.

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

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

На часах уже было 17:00, и я начал искать предобученные модели по распознаванию чисел. После проверки их на данной капче точность меня не удовлетворила ну что ж, пора собирать картинки и обучать свою нейросетку.

Для начала нужно собрать обучающую выборку.

Открываю вебдрайвер Хрома и скриню 1000 капчей себе в папку.

from selenium import webdriveri = 1000driver = webdriver.Chrome('/Users/aleksejkudrasov/Downloads/chromedriver')while i>0:    driver.get('http://www.vybory.izbirkom.ru/region/izbirkom?action=show&vrn=4274007421995&region=27&prver=0&pronetvd=0')    time.sleep(0.5)    with open(str(i)+'.png', 'wb') as file:        file.write(driver.find_element_by_xpath('//*[@id="captchaImg"]').screenshot_as_png)    i = i - 1


Так как у нас всего два цвета преобразовал наши капчи в чб:

from operator import itemgetter, attrgetterfrom PIL import Imageimport globlist_img = glob.glob('path/*.png')for img in list_img:    im = Image.open(img)    im = im.convert("P")    im2 = Image.new("P",im.size,255)    im = im.convert("P")    temp = {}# Бежим по картинке и переводим её в чб    for x in range(im.size[1]):        for y in range(im.size[0]):            pix = im.getpixel((y,x))            temp[pix] = pix            if pix != 0:                 im2.putpixel((y,x),0)    im2.save(img)

20761


Теперь нам надо нарезать наши капчи на цифры и преобразовать в единый размер 10*10.
Сначала мы разрезаем капчу на цифры, затем, так как капча смещается по оси OY, нам нужно обрезать все лишнее и повернуть картинку на 90.

def crop(im2):    inletter = False    foundletter = False    start = 0    end = 0    count = 0    letters = []    for y in range(im2.size[0]):         for x in range(im2.size[1]):             pix = im2.getpixel((y,x))            if pix != 255:                inletter = True#ищем первый черный пиксель цифры по оси OX        if foundletter == False and inletter == True:             foundletter = True            start = y#ищем последний черный пиксель цифры по оси OX         if foundletter == True and inletter == False:             foundletter = False            end = y            letters.append((start,end))        inletter = False    for letter in letters:#разрезаем картинку на цифры        im3 = im2.crop(( letter[0] , 0, letter[1],im2.size[1] )) #поворачиваем на 90        im3 = im3.transpose(Image.ROTATE_90)         letters1 = []#Повторяем операцию выше        for y in range(im3.size[0]): # slice across            for x in range(im3.size[1]): # slice down                pix = im3.getpixel((y,x))                if pix != 255:                    inletter = True            if foundletter == False and inletter == True:                foundletter = True                start = y            if foundletter == True and inletter == False:                foundletter = False                end = y                letters1.append((start,end))            inletter=False        for letter in letters1:#обрезаем белые куски            im4 = im3.crop(( letter[0] , 0, letter[1],im3.size[1] )) #разворачиваем картинку в исходное положение         im4 = im4.transpose(Image.ROTATE_270)         resized_img = im4.resize((10, 10), Image.ANTIALIAS)        resized_img.save(img)

Время уже, 18:00 пора заканчивать с этой задачкой, подумал я, попутно раскидывая цифры по папкам с их номерами.

Объявляем простенькую модель, которая на вход принимает развернутую матрицу нашей картинки.

Для этого создаем входной слой из 100 нейронов, так как размер картинки 10*10. В качестве выходного слоя 10 нейронов каждый из которых соответствует цифре от 0 до 9.

from tensorflow.keras import Sequentialfrom tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, Activation, BatchNormalization, AveragePooling2Dfrom tensorflow.keras.optimizers import SGD, RMSprop, Adamdef mnist_make_model(10, 10):    # Neural network model    model = Sequential()    model.add(Dense(100, activation='relu', input_shape=(10*10)))    model.add(Dense(10, activation='softmax'))    model.compile(loss='categorical_crossentropy', optimizer=RMSprop(), metrics=['accuracy'])    return model

Разбиваем наши данные на обучающую и тестовую выборку:

list_folder = ['0','1','2','3','4','5','6','7','8','9']X_Digit = []y_digit = []for folder in list_folder:    for name in glob.glob('path'+folder+'/*.png'):        im2 = Image.open(name)        X_Digit.append(np.array(im2))        y_digit.append(folder)

Разбиваем на обучающую и тестовую выборку:

from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X_Digit, y_digit, test_size=0.15, random_state=42)train_data = X_train.reshape(X_train.shape[0], 10*10) #Преобразуем матрицу векторов размерностью 100test_data = X_test.reshape(X_test.shape[0], 10*10) #Преобразуем матрицу векторов размерностью 100#преобразуем номер класса в вектор размерностью 10num_classes = 10train_labels_cat = keras.utils.to_categorical(y_train, num_classes)test_labels_cat = keras.utils.to_categorical(y_test, num_classes)

Обучаем модель.

Эмпирическим путем подбираем параметры количество эпох и размер бэтча:

model = mnist_make_model(10,10)model.fit(train_data, train_labels_cat, epochs=20, batch_size=32, verbose=1, validation_data=(test_data, test_labels_cat))


Сохраняем веса:

model.save_weights("model.h5")

Точность на 11 эпохе получилась отличная: accuracy = 1.0000. Довольный, в 19:00 иду домой отдыхать, завтра еще нужно будет написать парсер для сбора информации с сайта ЦИКа.

Утро следующего дня.

Дело осталось за малым, осталось обойти все страницы на сайте ЦИКа и забрать данные:

Загружаем веса обученной модели:

model = mnist_make_model(10,10)model.load_weights('model.h5')

Пишем функцию для сохранения капчи:

def get_captcha(driver):    with open('snt.png', 'wb') as file:        file.write(driver.find_element_by_xpath('//*[@id="captchaImg"]').screenshot_as_png)    im2 = Image.open('path/snt.png')    return im2

Пишем функцию для предсказания капчи:

def crop(im):    list_cap = []    im = im.convert("P")    im2 = Image.new("P",im.size,255)    im = im.convert("P")    temp = {}    for x in range(im.size[1]):        for y in range(im.size[0]):            pix = im.getpixel((y,x))            temp[pix] = pix            if pix != 0:                im2.putpixel((y,x),0)        inletter = False    foundletter=False    start = 0    end = 0    count = 0    letters = []    for y in range(im2.size[0]):         for x in range(im2.size[1]):             pix = im2.getpixel((y,x))            if pix != 255:                inletter = True        if foundletter == False and inletter == True:            foundletter = True            start = y        if foundletter == True and inletter == False:            foundletter = False            end = y            letters.append((start,end))        inletter=False    for letter in letters:        im3 = im2.crop(( letter[0] , 0, letter[1],im2.size[1] ))        im3 = im3.transpose(Image.ROTATE_90)        letters1 = []        for y in range(im3.size[0]):            for x in range(im3.size[1]):                pix = im3.getpixel((y,x))                if pix != 255:                    inletter = True            if foundletter == False and inletter == True:                foundletter = True                start = y            if foundletter == True and inletter == False:                foundletter = False                end = y                letters1.append((start,end))            inletter=False        for letter in letters1:            im4 = im3.crop(( letter[0] , 0, letter[1],im3.size[1] ))        im4 = im4.transpose(Image.ROTATE_270)        resized_img = im4.resize((10, 10), Image.ANTIALIAS)        img_arr = np.array(resized_img)/255        img_arr = img_arr.reshape((1, 10*10))        list_cap.append(model.predict_classes([img_arr])[0])    return ''.join([str(elem) for elem in list_cap])

Добавляем функцию, которая скачивает таблицу:

def get_table(driver):    html = driver.page_source #Получаем код страницы     soup = BeautifulSoup(html, 'html.parser') #Оборачиваем в "красивый суп"    table_result = [] #Объявляем лист в котором будет лежать финальная таблица    tbody = soup.find_all('tbody') #Ищем таблицу на странице    list_tr = tbody[1].find_all('tr') #Собираем все строки таблицы    ful_name = list_tr[0].text #Записываем название выборов    for table in list_tr[3].find_all('table'): #Бежим по всем таблицам        if len(table.find_all('tr'))>5: #Проверяем размер таблицы            for tr in table.find_all('tr'): #Собираем все строки таблицы                snt_tr = []#Объявляем временную строку                for td in tr.find_all('td'):                    snt_tr.append(td.text.strip())#Собираем все стоблцы в строку                table_result.append(snt_tr)#Формируем таблицу    return (ful_name, pd.DataFrame(table_result, columns = ['index', 'name','count']))

Собираем все линки за 13 сентября:

df_table = []driver.get('http://www.vybory.izbirkom.ru')driver.find_element_by_xpath('/html/body/table[2]/tbody/tr[2]/td/center/table/tbody/tr[2]/td/div/table/tbody/tr[3]/td[3]').click()html = driver.page_sourcesoup = BeautifulSoup(html, 'html.parser')list_a = soup.find_all('table')[1].find_all('a')for a in list_a:    name = a.text    link = a['href']    df_table.append([name,link])df_table = pd.DataFrame(df_table, columns = ['name','link'])

К 13:00 я дописываю код с обходом всех страниц:

result_df = []for index, line in df_table.iterrows():#Бежим по строкам таблицы с ссылками    driver.get(line['link'])#Загружаем ссылку    time.sleep(0.6)    try:#Разгадываем капчу если она вылетает        captcha = crop(get_captcha(driver))        driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)        driver.find_element_by_xpath('//*[@id="send"]').click()        time.sleep(0.6)        true_cap(driver)    except NoSuchElementException:#Отлавливаем ошибку если капче не появилась        pass    html = driver.page_source    soup = BeautifulSoup(html, 'html.parser')    if soup.find('select') is None:#Проверяем есть ли выпадающий список на странице        time.sleep(0.6)        html = driver.page_source        soup = BeautifulSoup(html, 'html.parser')                  for i in range(len(soup.find_all('tr'))):#Ищем ссылку на результат выборов            if '\nРЕЗУЛЬТАТ ВБОРОВ\n' == soup.find_all('tr')[i].text:#Ищем фразу, следующая за этой фразой наша ссылка на таблицу с выборами                rez_link = soup.find_all('tr')[i+1].find('a')['href']        driver.get(rez_link)        time.sleep(0.6)        try:            captcha = crop(get_captcha(driver))            driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)            driver.find_element_by_xpath('//*[@id="send"]').click()            time.sleep(0.6)            true_cap(driver)        except NoSuchElementException:            pass        ful_name , table = get_table(driver)#Получаем таблицу        head_name = line['name']        child_name = ''        result_df.append([line['name'],line['link'],rez_link,head_name,child_name,ful_name,table])    else:#Если выпадающий список присутствует, обходим все ссылки        options = soup.find('select').find_all('option')        for option in options:            if option.text == '---':#Пропускаем первую строку из выпадающего списка                continue            else:                link = option['value']                head_name = option.text                driver.get(link)                try:                    time.sleep(0.6)                    captcha = crop(get_captcha(driver))                    driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)                    driver.find_element_by_xpath('//*[@id="send"]').click()                    time.sleep(0.6)                    true_cap(driver)                except NoSuchElementException:                    pass                html2 = driver.page_source                second_soup = BeautifulSoup(html2, 'html.parser')                for i in range(len(second_soup.find_all('tr'))):                    if '\nРЕЗУЛЬТАТ ВБОРОВ\n' == second_soup.find_all('tr')[i].text:                        rez_link = second_soup.find_all('tr')[i+1].find('a')['href']                driver.get(rez_link)                try:                    time.sleep(0.6)                    captcha = crop(get_captcha(driver))                    driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)                    driver.find_element_by_xpath('//*[@id="send"]').click()                    time.sleep(0.6)                    true_cap(driver)                except NoSuchElementException:                    pass                ful_name , table = get_table(driver)                child_name = ''                result_df.append([line['name'],line['link'],rez_link,head_name,child_name,ful_name,table])                if second_soup.find('select') is None:                    continue                else:                    options_2 = second_soup.find('select').find_all('option')                    for option_2 in options_2:                        if option_2.text == '---':                            continue                        else:                            link_2 = option_2['value']                            child_name = option_2.text                            driver.get(link_2)                            try:                                time.sleep(0.6)                                captcha = crop(get_captcha(driver))                                driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)                                driver.find_element_by_xpath('//*[@id="send"]').click()                                time.sleep(0.6)                                true_cap(driver)                            except NoSuchElementException:                                pass                            html3 = driver.page_source                            thrid_soup = BeautifulSoup(html3, 'html.parser')                            for i in range(len(thrid_soup.find_all('tr'))):                                if '\nРЕЗУЛЬТАТ ВБОРОВ\n' == thrid_soup.find_all('tr')[i].text:                                    rez_link = thrid_soup.find_all('tr')[i+1].find('a')['href']                            driver.get(rez_link)                            try:                                time.sleep(0.6)                                captcha = crop(get_captcha(driver))                                driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)                                driver.find_element_by_xpath('//*[@id="send"]').click()                                time.sleep(0.6)                                true_cap(driver)                            except NoSuchElementException:                                pass                            ful_name , table = get_table(driver)                            result_df.append([line['name'],line['link'],rez_link,head_name,child_name,ful_name,table])

А после приходит твит, который изменил мою жизнь

2020-09-29-12-11-31
Источник: habr.com
К списку статей
Опубликовано: 02.10.2020 14:18:50
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Python

Машинное обучение

Tensorflow

Selenium-webdriver

Captcha

Парсинг сайта

Mnist

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru