如何通过requests库从http请求中获取IP地址?

35 投票
3 回答
100382 浏览
提问于 2025-04-17 22:53

我正在用Python的requests库发送HTTP请求,但我需要知道响应这个请求的服务器的IP地址。我想避免发两次请求(这样可能会得到一个和响应不一样的IP地址)。

这样做可能吗?有没有Python的HTTP库可以让我做到这一点?

另外,我还需要发送HTTPS请求,并且使用一个需要认证的代理。

更新 1:

举个例子:

import requests

proxies = {
  "http": "http://user:password@10.10.1.10:3128",
  "https": "http://user:password@10.10.1.10:1080",
}

response = requests.get("http://example.org", proxies=proxies)
response.ip # This doesn't exist, this is just an what I would like to do

然后,我想知道请求连接的是哪个IP地址,想通过响应中的某个方法或属性来获取。在其他库中,我能通过找到sock对象并使用getpeername()函数来做到这一点。

3 个回答

1

更新 2023年8月8日:

对于Python 3.11.3和requests 2.30.0,使用以下的猴子补丁:

def patch_http_and_https_connection():
    import http.client

    def getresponse(self, *args, **kwargs):
        response = self._old_getresponse(*args, **kwargs)
        if self.sock:
            response._local = self.sock.getsockname()
            response._remote = self.sock.getpeername()
        else:
            response._local = None
            response._remote = None
        return response

    http.client.HTTPConnection._old_getresponse = http.client.HTTPConnection.getresponse
    http.client.HTTPConnection.getresponse = getresponse

然后对于这个例子:

rsp = requests.get('https://www.google.com', stream=True)

使用以下内容:

Remote IP Address/Port = rsp.raw._original_response._remote
Local IP Address/Port = rsp.raw._original_response._local
3

试试:

import requests

proxies = {
  "http": "http://user:password@10.10.1.10:3128",
  "https": "http://user:password@10.10.1.10:1080",
}

response = requests.get('http://jsonip.com', proxies=proxies)
ip = response.json()['ip']
print('Your public IP is:', ip)
66

结果发现,这个问题比较复杂。

这里有一个在使用 requests 版本 1.2.3 时的修改方法:

我们需要对 HTTPConnectionPool_make_request 方法进行包装,以便在 HTTPResponse 实例中存储来自 socket.getpeername() 的响应。

在我使用的 python 2.7.3 中,这个实例可以通过 response.raw._original_response 来访问。

from requests.packages.urllib3.connectionpool import HTTPConnectionPool

def _make_request(self,conn,method,url,**kwargs):
    response = self._old_make_request(conn,method,url,**kwargs)
    sock = getattr(conn,'sock',False)
    if sock:
        setattr(response,'peer',sock.getpeername())
    else:
        setattr(response,'peer',None)
    return response

HTTPConnectionPool._old_make_request = HTTPConnectionPool._make_request
HTTPConnectionPool._make_request = _make_request

import requests

r = requests.get('http://www.google.com')
print r.raw._original_response.peer

这样做的结果是:

('2a00:1450:4009:809::1017', 80, 0, 0)

哦,如果涉及到代理或者响应是分块的,那么 HTTPConnectionPool._make_request 就不会被调用。

所以这里有一个新的版本,改为修补 httplib.getresponse

import httplib

def getresponse(self,*args,**kwargs):
    response = self._old_getresponse(*args,**kwargs)
    if self.sock:
        response.peer = self.sock.getpeername()
    else:
        response.peer = None
    return response


httplib.HTTPConnection._old_getresponse = httplib.HTTPConnection.getresponse
httplib.HTTPConnection.getresponse = getresponse

import requests

def check_peer(resp):
    orig_resp = resp.raw._original_response
    if hasattr(orig_resp,'peer'):
        return getattr(orig_resp,'peer')

运行结果是:

>>> r1 = requests.get('http://www.google.com')
>>> check_peer(r1)
('2a00:1450:4009:808::101f', 80, 0, 0)
>>> r2 = requests.get('https://www.google.com')
>>> check_peer(r2)
('2a00:1450:4009:808::101f', 443, 0, 0)
>>> r3 = requests.get('http://wheezyweb.readthedocs.org/en/latest/tutorial.html#what-you-ll-build')
>>> check_peer(r3)
('162.209.99.68', 80)

我还检查了在设置了代理的情况下运行,代理地址也能返回。


更新 2016/01/19

est 提供了 一个不需要修改的方法

rsp = requests.get('http://google.com', stream=True)
# grab the IP while you can, before you consume the body!!!!!!!!
print rsp.raw._fp.fp._sock.getpeername()
# consume the body, which calls the read(), after that fileno is no longer available.
print rsp.content  

更新 2016/05/19

根据评论,这里复制一下以便大家看到,Richard Kenneth Niescior 提供了一个在 requests 2.10.0 和 Python 3 中确认有效的解决方案。

rsp=requests.get(..., stream=True)
rsp.raw._connection.sock.getpeername()

更新 2019/02/22

使用 Python3 和 requests 版本 2.19.1。

resp=requests.get(..., stream=True)
resp.raw._connection.sock.socket.getsockname()

更新 2020/01/31

使用 Python3.8 和 requests 2.22.0。

resp = requests.get('https://www.google.com', stream=True)
resp.raw._connection.sock.getsockname()

撰写回答