我如何从线程中的每个请求获得新的IP?

时间:2019-06-24 09:16:11

标签: python-3.x python-requests multiprocessing python-asyncio tor

我尝试使用TOR代理进行抓取,并且所有功能在一个线程中都可以正常工作,但这很慢。 我尝试做一些简单的事情:

def get_new_ip():
    with Controller.from_port(port = 9051) as controller:
        controller.authenticate(password="password")
        controller.signal(Signal.NEWNYM)
        time.sleep(controller.get_newnym_wait())


def check_ip():
    get_new_ip()
    session = requests.session()
    session.proxies = {'http': 'socks5h://localhost:9050', 'https': 'socks5h://localhost:9050'}
    r = session.get('http://httpbin.org/ip')
    r.text


with Pool(processes=3) as pool:
    for _ in range(9):
        pool.apply_async(check_ip)
    pool.close()
    pool.join()

运行它时,我看到输出:

{"origin": "95.179.181.1, 95.179.181.1"}
{"origin": "95.179.181.1, 95.179.181.1"}
{"origin": "95.179.181.1, 95.179.181.1"}
{"origin": "151.80.53.232, 151.80.53.232"}
{"origin": "151.80.53.232, 151.80.53.232"}
{"origin": "151.80.53.232, 151.80.53.232"}
{"origin": "145.239.169.47, 145.239.169.47"}
{"origin": "145.239.169.47, 145.239.169.47"}
{"origin": "145.239.169.47, 145.239.169.47"}

为什么会发生这种情况,如何给每个线程分配自己的IP? 顺便说一下,我尝试了类似TorRequests,TorCtl之类的库,结果是一样的。

我知道TOR似乎在发布新IP之前有延迟,但是为什么同一个IP进入不同的进程?

2 个答案:

答案 0 :(得分:1)

您只有一个代理,该代理正在侦听9050端口。所有3个进程都通过该代理并行发送请求,因此它们共享相同的IP。

正在发生的事情是:

  1. 所有3个过程均要求代理获得新IP
  2. 代理要么请求3次新IP,接收3个响应并应用最后一个IP,要么它将识别出它已经在等待新IP而忽略了其中2个请求,一起回答了3个。这将取决于代理的实现。
  3. 进程通过代理发送其请求,从而获得相同的IP。
  4. 该过程已完成,另外3个过程已启动。冲洗并重复。

这就是为什么每个3个请求块的IP都相同的原因。 您需要3个独立的代理才能同时拥有3个不同的IP。


编辑:

使用锁并假设3个代理在后台运行的可能解决方案:

import contextlib, threading, time

_controller_ports = [
    # (Controller Lock, connection port, management port)
    (threading.Lock(), 9050, 9051),
    (threading.Lock(), 9060, 9061),
    (threading.Lock(), 9070, 9071),
]

def get_new_ip_for(port):
    with Controller.from_port(port=port) as controller:
        controller.authenticate(password="password")
        controller.signal(Signal.NEWNYM)
        time.sleep(controller.get_newnym_wait())

@contextlib.contextmanager
def get_port_with_new_ip():
    while True:
        for lock, con_port, manage_port in _controller_ports:
            if lock.acquire(blocking=False):
                get_new_ip_for(manage_port)
                yield con_port
                lock.release()
                break
        time.sleep(1)

def check_ip():
    with get_port_with_new_ip() as port:
        session = requests.session() 
        session.proxies = {'http': f'socks5h://localhost:{port}', 'https': f'socks5h://localhost:{port}'}
        r = session.get('http://httpbin.org/ip')
        print(r.text)

with Pool(processes=3) as pool:
    for _ in range(9):
        pool.apply_async(check_ip)
    pool.close()
    pool.join()

答案 1 :(得分:1)

如果您希望每个连接使用不同的IP,则还可以通过为每个连接指定不同的代理username:password组合,在SOCKS上使用Stream Isolation

使用此方法,您只需要一个Tor实例,并且每个请求客户端可以使用具有不同退出节点的不同流。

要进行设置,请为每个requests.session对象添加唯一的代理凭据,如下所示:socks5h://username:password@localhost:9050

import random
from multiprocessing import Pool
import requests

def check_ip():
    session = requests.session()
    creds = str(random.randint(10000,0x7fffffff)) + ":" + "foobar"
    session.proxies = {'http': 'socks5h://{}@localhost:9050'.format(creds), 'https': 'socks5h://{}@localhost:9050'.format(creds)}
    r = session.get('http://httpbin.org/ip')
    print(r.text)


with Pool(processes=8) as pool:
    for _ in range(9):
        pool.apply_async(check_ip)
    pool.close()
    pool.join()

Tor浏览器通过将凭据设置为firstpartydomain:randompassword来按域隔离流,其中randompassword是每个唯一的第一方域的随机随机数。

如果您要爬网相同的站点,并且需要随机IP,请为每个会话使用随机的username:password组合。如果要爬网随机域,并想使用相同的电路来请求域,请使用Tor浏览器的domain:randompassword方法获取凭据。