Пишем многопоточные приложения на python for fun and profit
от Virus-ONЪ
Давеча возникла необходимость использовать парсер веб страниц с поддержкой исполнения Java Script, Grab framework с задачей не справился (вообще то там есть такая фишка как транспорты с поддержкой того же селениума, однако какой-либо документации, увы, нет). Поэтому как инструмент для распознавания был выбран selenium framework + браузер PhantomJS. В отличии от grab у Selenium нет функционала для асинхронных (или хотя бы многопоточных) запросов, поэтому мы и будем реализовывать данный функционал вручную.
Писать мы будем чекер биткоинов принимающий как входной параметр текстовый файл с ключами и адресами в формате ключ адрес (через пробел). Для проверки будем использовать встроенный функционал DuckDuck.gо Все гуды(кошельки с балансом) будем писать в файл good.txt
Итак, на этом этапе особых вопросов возникнуть не должно, собственно использование Queue и Замка станет понятно чуть-чуть позднее.
Итак, Queue можно назвать своеобразным массивом-очередью с какими-либо данными, при помощи метода get_nowait() мы можем получить новое задание, а благодаря методу put() можем поместить наше задание в очередь.
Теперь поговорим про RLock. Точнее про то для чего он нужен. Итак, допустим такую гипотетическую ситуацию, два потока одновременно завершают исполнение и одновременно обращаются к функции write.
Что же произойдёт? Писать абсолютно одновременно, конечно, у них не получится, но данные это всё равно не спасёт и в итоге вместо кошельков мы получим кашу из символов, не очень приятно, да? Для избежания такой ситуации мы и используем RLock, он замыкает функцию на время исполнения одним потоком, так что другие потоки не могут к ней обратиться. То что нам и нужно.
Вот собственно и всё. Всем добра.
Писать мы будем чекер биткоинов принимающий как входной параметр текстовый файл с ключами и адресами в формате ключ адрес (через пробел). Для проверки будем использовать встроенный функционал DuckDuck.gо Все гуды(кошельки с балансом) будем писать в файл good.txt
- import threading
- import time
- from selenium import webdriver
- from Queue import Queue
- from sys import argv
- from time import sleep
- THREADS_COUNT = 20 # Задаём количество потоков
- queue = Queue() # Инициализация объекта типа Queue, служит для создания очереди тасков и доступа к ним.
- LOCK = threading.RLock() # Замок, служит для блокирования доступа к блоку кода только для одного потока одновременно, от объекта Lock() отличается тем, что один и тот же поток может пользоваться
- этим же кодом без блокирования.
Итак, на этом этапе особых вопросов возникнуть не должно, собственно использование Queue и Замка станет понятно чуть-чуть позднее.
- def parse():
- global queue # Для доступа к глобальной переменной, в принципе не обязательно, но более наглядно
- out = [] # Список гудов
- driver = webdriver.PhantomJS() # Инициализируем webdriver для пользования браузером PhantomJS
- # Важное замечание, PhantomJS должен быть либо в директории с .py файлом, либо в Path
- while True: # Основной цикл
- try: # Если очередь заданий пуста цикл перервётся
- adress = queue.get_nowait() # Получаем задание из очереди
- except:
- break
- try:
- driver.get('https://duckduckgo.com/?q=' + adress.split(' ')[1])
- except: # Коль что-то не заладилось вернём в очередь и попробуем снова позже
- sleep(5) # Поспим, авось всё наладится
- queue.put(adress)
- continue
- try: # Если бабки есть, пишем адресс в гуды, иначе выводим что кошель не существует
- print(driver.find_element_by_xpath('//td[@class="record__cell record__cell--value"]').text)
- if (driver.find_element_by_xpath('//td[@class="record__cell record__cell--value"]').text) != '0 BTC':
- out.append(adress)
- except:
- print('Dont exist')
- write(out) # Пишем гуды в файл
- driver.close() # Закрываем браузер, он своё отработал, нефиг в памяти висеть
- def write(string):
- global LOCK
- LOCK.acquire() # Накидываем замок
- for s in string:
- result_file = open('good.txt','a')
- result_file.write(s+'\n')
- result_file.close()
- LOCK.release() # снимем его
Итак, Queue можно назвать своеобразным массивом-очередью с какими-либо данными, при помощи метода get_nowait() мы можем получить новое задание, а благодаря методу put() можем поместить наше задание в очередь.
Теперь поговорим про RLock. Точнее про то для чего он нужен. Итак, допустим такую гипотетическую ситуацию, два потока одновременно завершают исполнение и одновременно обращаются к функции write.
Что же произойдёт? Писать абсолютно одновременно, конечно, у них не получится, но данные это всё равно не спасёт и в итоге вместо кошельков мы получим кашу из символов, не очень приятно, да? Для избежания такой ситуации мы и используем RLock, он замыкает функцию на время исполнения одним потоком, так что другие потоки не могут к ней обратиться. То что нам и нужно.
- print "STARTED"
- global THREADS_COUNT
- input_file = open(argv[1])
- for line in input_file:
- queue.put(line.replace('\n','')) # Заполняем очередь
- for _ in xrange(THREADS_COUNT): # Запускаем треды
- thread_=threading.Thread(target=parse)
- thread_.start()
- while threading.active_count() >1: # Пока активны дочерние треды, не стоит закрывать основной
- time.sleep(1)
- print "FINISHED"
Вот собственно и всё. Всем добра.