Python мультипоточный парсер через Tor
Здраствуйте. Пытаюсь написать мультипоточный парсер, который использует Tor, со сменой IP Tor прокси на лету. url сайта поставил другой, здесь это абсолютно не важно и никак не влияет на логику работы. В функции start_all() создается Pool потоков, каждый из которых в функции start_thread() генерирует по еще одному additional Pool-у потоков. Этот additional Pool потоков использует один и тот же тор прокси. Проблема в том что при возникновении исключения в функции get_data(), когда перестает работать Tor прокси на порту additional Pool-а, блокировка срабатывает сразу у нескольких потоков из этого Pool-а и они начинают одновременно пытаться менять Tor прокси мешая друг другу. Подскажите как организовать блокировку, чтобы во время срабатывания исключения, только один из потоков additional Poola занимался сменой прокси, остальные в это время ждали и запускались только по команде, когда прокси сменился? Переменная proxies выглядит в виде списка словарей:
proxies = [
{'port': 0000, 'pid': 0000, 'http': 'socks5://127.0.0.1:0000', 'https': 'socks5://127.0.0.1:0000'},
{'port': 1111, 'pid': 1111, 'http': 'socks5://127.0.0.1:1111', 'https': 'socks5://127.0.0.1:1111'},
{'port': 2222, 'pid': 2222, 'http': 'socks5://127.0.0.1:2222', 'https': 'socks5://127.0.0.1:2222'},
]
Код:
import json
import requests
from multiprocessing.pool import ThreadPool as Pool
import threading
from time import sleep
from random import randint
import sys
from fake_useragent import UserAgent
import tor_socks
from config import *
report = {}
proxies = []
lock = threading.Lock()
event = threading.Event()
logger = logging.getLogger(__name__)
register_logger(log_file=log_path)
ua_obj = UserAgent()
def end(mess):
logger.warning(mess)
tor_socks.kill_tor_all()
sys.exit()
def load_report():
global report
if os.path.exists(main_path + 'report.txt'):
with open(main_path + 'report.txt', encoding='utf-8') as f:
report = json.loads(f.read())
else:
report = {
'total_count': 0,
'search_count': 0,
'empty_count': 0,
'proxy_change': 0,
}
def save_report():
global report
with open(main_path + 'report.txt', 'w', encoding='utf-8') as w:
w.write(json.dumps(report))
def check_internet():
try:
try:
requests.get('https://www.google.com')
except requests.ConnectTimeout:
requests.get('http://1.1.1.1')
return True
except requests.ConnectionError:
return False
def change_proxy(main_thread):
tor_socks.kill_pid(proxies[main_thread]['pid'])
new_proxy = tor_socks.do_one_sock(proxies[main_thread]['port'])
if not new_proxy:
max_port = sorted(proxies, key=lambda k: (k['port']))[-1]['port']
port = max_port + 1
while not new_proxy:
new_proxy = tor_socks.do_one_sock(port)
port += 1
proxies[main_thread] = new_proxy
def get_data(main_thread, additional_thread):
global report, killer, event
try:
event.wait()
headers = {'User-Agent': ua_obj.random}
url = "http://httpbin.org/ip"
res = requests.get(url, proxies=proxies[main_thread], headers=headers).json()
return res
except Exception as e:
event.clear()
logger.warning(f'[{main_thread}-{additional_thread}] clear')
logger.warning(f'[{main_thread}-{additional_thread}] {e}')
while True:
change_proxy(main_thread)
logger.warning(f"[{main_thread} {proxies[main_thread]['port']}] proxy ip changed")
report['proxy_change'] += 1
sleep(randint(2, 3))
break
logger.warning(f'[{main_thread}-{additional_thread}] set')
event.set()
return 'error'
def check(main_thread, additional_thread):
global report
data = 'error'
num_error = 0
while data == 'error':
data = get_data(main_thread, additional_thread)
num_error += 1
if num_error >= 5:
end('num errors >= 5')
with lock:
print(f"[{main_thread}-{additional_thread} {proxies[main_thread]['port']}] {data}")
report['total_count'] += 1
if data:
with open('results.txt', 'a') as w:
w.write(data + "\n")
report['search_count'] += 1
else:
report['empty_count'] += 1
if not report['total_count'] % 1000:
save_report()
logger.warning(report)
def start_thread(main_thread):
event.set()
while True:
pool = Pool(requests_per_thread)
for additional_thread in range(requests_per_thread):
pool.apply_async(check, (main_thread, additional_thread))
pool.close()
pool.join()
def start_all():
pool = Pool(threads)
for main_thread in range(threads):
pool.apply_async(start_thread, (main_thread,))
pool.close()
pool.join()
if __name__ == '__main__':
logger.warning(f'#### start with threads {threads * requests_per_thread} {threads}/{requests_per_thread} ####')
if check_internet():
load_report()
proxies = tor_socks.do_socks(num=threads)
if len(proxies) < threads:
end('num proxies != num threads')
start_all()
else:
print("Network Error")
logger.warning('end')
Ответы (1 шт):
Вам нужно сделать две вещи. Во-первых, нужно захватить блокировку, чтоб смену прокси делал только один поток, а остальные не могли сделать этого одновременно.
Во-вторых, нужно чтобы поток знал с каким прокси он работал когда случилась ошибка, и не пытался его поменять, если это сделал уже какой-то другой поток раньше (пока этот ждал блокировку). Для этого можно в change_proxy передавать тот прокси, который использовался, и смотреть, если уже сконфигурирован другой, но менять прокси не нужно (кто-то уже это сделал).
proxies = []
proxies_locks = []
...
def change_proxy(main_thread, current_proxy):
with proxies_locks[main_thread]:
if proxies[main_thread] != current_proxy:
return
tor_socks.kill_pid(proxies[main_thread]['pid'])
new_proxy = tor_socks.do_one_sock(proxies[main_thread]['port'])
if not new_proxy:
max_port = sorted(proxies, key=lambda k: (k['port']))[-1]['port']
port = max_port + 1
while not new_proxy:
new_proxy = tor_socks.do_one_sock(port)
port += 1
proxies[main_thread] = new_proxy
def get_data(main_thread, additional_thread):
global report, killer, event
try:
event.wait()
headers = {'User-Agent': ua_obj.random}
url = "http://httpbin.org/ip"
current_proxy = proxies[main_thread]
res = requests.get(url, proxies=current_proxy, headers=headers).json()
return res
except Exception as e:
event.clear()
logger.warning(f'[{main_thread}-{additional_thread}] clear')
logger.warning(f'[{main_thread}-{additional_thread}] {e}')
while True:
change_proxy(main_thread, current_proxy)
# логирование и посчет количества нужно перенести в change_proxy
logger.warning(f"[{main_thread} {proxies[main_thread]['port']}] proxy ip changed")
report['proxy_change'] += 1
sleep(randint(2, 3))
break
logger.warning(f'[{main_thread}-{additional_thread}] set')
event.set()
return 'error'
...
if __name__ == '__main__':
logger.warning(f'#### start with threads {threads * requests_per_thread} {threads}/{requests_per_thread} ####')
if check_internet():
load_report()
proxies = tor_socks.do_socks(num=threads)
if len(proxies) < threads:
end('num proxies != num threads')
proxies_locks = [Lock() for _ in range(threads)]
start_all()
else:
print("Network Error")
logger.warning('end')