python - 多个Tor代理上的并发HTTP请求

时间:2018-03-28 06:33:41

标签: python multithreading web-scraping concurrency tor

我的计算机上运行了多个Tor客户端,每个客户端都可以通过自己的端口访问(目前端口为9050-9054)。我想通过这些Tor客户端同时请求一个大的URL列表,其速率限制使得在任何给定的Tor端口上每N秒只发出一个请求,并且每个端口都使用一个唯一的出口节点(即没有两个端口同时从同一IP发出请求。

目标是通过使请求看起来来自多个不同的IP(每个IP都以最大速率消耗)来从IP速率限制的网站/ API中获取。匿名并不是一个真正的目标 - 使用Tor只是为了创建一个大量的IP来诱骗速率限制器认为它是一群请求数据的不同用户。因此,例如,如果API限制为每秒一个请求,并且我有10个客户端运行,每个客户端都以此速率请求,那么我可以以最大速率的10倍发出请求...

这是我到目前为止的代码,它通过我的Tor客户端池同时获取所有URL:

import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep
from datetime import datetime 
from threading import Lock
import logging

logging.basicConfig(filename='log.txt', level=logging.DEBUG)

tor_ports = ['9050', '9051', '9052', '9053', '9054']
port_locks = {port: Lock() for port in tor_ports}

delay = 1   # wait N seconds between requests on same port

ua_string = 'Mozilla/5.0 (X11; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0'

# Create a new HTTP Requests Session routed through one of our Tor proxies
def newTorSession(port):
    assert port in tor_ports
    session = requests.session()
    session.proxies = {'http':  'socks5://127.0.0.1:' + port,
                       'https': 'socks5://127.0.0.1:' + port}
    return session

# Go through the list of all Tor proxies and return one that isn't locked
def getFreeTorPort(hangtime):
    start = datetime.now()
    while (datetime.now() - start).total_seconds() < hangtime: 
        for port in tor_ports:
            if port_locks[port].locked():
                next; # port in use ... try the next one
            else:
                port_locks[port].acquire()
                return port

    return None # this is when we exceed hangtime ... should be an exception we catch


# URL to fetch, and how long to sleep() after request
def torGet(url, delay):

    port = getFreeTorPort(60)
    session = newTorSession(port)

    try:
        response = session.get(url, headers = {'User-Agent': ua_string})
    except requests.exception.RequestException as e:
        logging.warning("Request of URL " + url + " failed with exception: " + e)

    sleep(delay) # Pause for `delay` seconds after request

    port_locks[port].release()

    return response


# given a list of URLs, use multiple threads with own tor client to GET items
def torGetConcurrent(urls):
    responses = []

    with ThreadPoolExecutor(max_workers=len(tor_ports)) as executor:
        futures = [executor.submit(torGet, url, delay) for url in urls]
        responses = [f.result() for f in as_completed(futures)]

    return responses

我的问题是如何确保每个Tor客户端始终使用不同的退出IP ?也就是说,我想确保如果我运行10个客户端,那么我将始终使用10个唯一的退出节点。我目前设置它的方式,我有时有多个客户端使用相同的退出,这使我超过每个IP的速率限制。我知道我可以在每个客户端的ExitNodes文件的torrc字段中明确指定退出节点列表,但我想知道是否有一种方法可以通过python检查这一点脚本,因为我不想手动使用退出节点列表更新配置文件,并且我并不关心它们使用哪个退出节点,只要它们都是唯一的。

谢谢!

1 个答案:

答案 0 :(得分:1)

我不一定说Tor有“大量的IP”。目前大约有850个出口,其中一些可能超载且无法使用。

在任何情况下,请尝试为所有出口构建指纹列表(多个网站发布这些列表)或您要使用的国家/地区的指纹列表,并将每个tor客户端的ExitNodes配置设置为a特定指纹使得它们中没有一个同时使用相同的指纹。这比向单个客户端发送NEWNYM信号更成功,希望它们不会同时重叠,并且必须运行慢速检查以查看任何给定客户端正在使用哪个退出。

编辑:

要执行我所描述的操作,请获取退出列表(例如https://check.torproject.org/exit-addresseshttps://torstatus.blutmagie.de/)并将其拉入您选择的列表中,以便随机选择唯一的指纹列表,然后使用stem连接到每个实例的控制端口。连接后,将每个实例的配置值ExitNodes设置为其中一个指纹。 ExitNodes可以是国家/地区,节点列表或单个节点。当设置为单个节点时,您基本上是将该中继用作出口的客户端。这可确保没有两个客户端同时使用相同的退出中继。准备好循环后,将ExitNodes设置为新指纹,然后拨打SIGNAL NEWNYN以构建新电路。

执行此操作可能比检查每个实例并强制使用新IP(如果其中任何一个相同)更快。然后,其中一个实例之间没有机会在会话之间建立新电路并且在不知情的情况下使用重复的IP。

目前,如果不检查外部网站上的IP,就无法使用Python或任何其他语言来获取退出IP或指纹。您通常可以使用控制端口查看活动电路列表,从中提取退出指纹,并从目录状态请求中查找IP。由于Tor可以同时拥有多个电路,因此您无法分辨脚本可能使用哪个电路。