Python 请求 - 使用服务器 IP 导航站点

Python Requests - Use navigate site by servers IP

我想抓取网站,但 cloudflare 挡住了我的路。我能够获取服务器 IP,因此 cloudflare 不会打扰我。

如何在请求库中使用它?

比如我想直接去 www.example.com/foo.php,但在请求中,它将解析 cloudflare 网络上的 IP,而不是我希望它使用的 IP。我怎样才能让它使用我想要它使用的那个?

我会发送一个请求,以便将主机设置为 www.example.com 的真实 IP,但这只会给我主页。我怎样才能访问网站上的其他链接?

您将必须设置一个自定义 header host,其值为 example.com,例如:

requests.get('http://127.0.0.1/foo.php', headers={'host': 'example.com'})

应该可以解决问题。如果要验证,请输入以下命令(需要 netcat):nc -l -p 80 然后输入 运行 上面的命令。它将在 netcat window:

中产生输出
GET /foo.php HTTP/1.1
Host: example.com
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.6.2 CPython/3.4.3 Windows/8

您必须告诉 requests 伪造 Host header,并将 URL 中的主机名替换为 IP 地址:

requests.get('http://123.45.67.89/foo.php', headers={'Host': 'www.example.com'})

URL 'patching' 可以用 urlparse 库完成:

parsed = urlparse.urlparse(url)
hostname = parsed.hostname
parsed = parsed._replace(netloc=ipaddress)
ip_url = parsed.geturl()

response = requests.get(ip_url, headers={'Host': hostname})

针对 Stack Overflow 的演示:

>>> import urlparse
>>> import socket
>>> url = 'http://whosebug.com/help/privileges'
>>> parsed = urlparse.urlparse(url)
>>> hostname = parsed.hostname
>>> hostname
'whosebug.com'
>>> ipaddress = socket.gethostbyname(hostname)
>>> ipaddress
'198.252.206.16'
>>> parsed = parsed._replace(netloc=ipaddress)
>>> ip_url = parsed.geturl()
>>> ip_url
'http://198.252.206.16/help/privileges'
>>> response = requests.get(ip_url, headers={'Host': hostname})
>>> response
<Response [200]>

在这种情况下,我动态查找了 ip 地址。

HTTPS/SNI 支持的答案:使用 requests_toolbelt module 中的 HostHeaderSSLAdapter:

上述解决方案适用于 non-encrypted HTTP 连接的虚拟主机。对于 HTTPS,您还需要在 TLS header 中传递 SNI(服务器名称标识),因为某些服务器会根据通过 SNI 传递的内容提供不同的 SSL 证书。此外,默认情况下 python ssl 库不会查看 Host: header 以在连接时匹配服务器连接。

上面提供了 straight-forward 用于将传输适配器添加到为您处理此问题的请求。

例子

import requests

from requests_toolbelt.adapters import host_header_ssl

# Create a new requests session
s = requests.Session()

# Mount the adapter for https URLs
s.mount('https://', host_header_ssl.HostHeaderSSLAdapter())

# Send your request
s.get("https://198.51.100.50", headers={"Host": "example.org"})

我认为将 https 请求发送到特定 IP 的最佳方法是添加自定义解析器以将域名绑定到您要访问的 IP。这样SNI和host都设置正确header,浏览器证书验证总能成功

否则,您会看到 InsecureRequestWarningSSLCertVerificationError 等各种问题,并且 Client Hello 中始终缺少 SNI,即使您尝试 header 的不同组合并验证参数。

requests.get('https://1.2.3.4/foo.php', headers= {"host": "example.com", verify=True)

另外,我试过了

requests_toolbelt

pip install requests[security]

forcediphttpsadapter

all solutions mentioned here using requests with TLS doesn't give SNI support

None 其中直接访问 https://IP 时设置 SNI。

# mock /etc/hosts
# lock it in multithreading or use multiprocessing if an endpoint is bound to multiple IPs frequently
etc_hosts = {}


# decorate python built-in resolver
def custom_resolver(builtin_resolver):
    def wrapper(*args, **kwargs):
        try:
            return etc_hosts[args[:2]]
        except KeyError:
            # fall back to builtin_resolver for endpoints not in etc_hosts
            return builtin_resolver(*args, **kwargs)

    return wrapper


# monkey patching
socket.getaddrinfo = custom_resolver(socket.getaddrinfo)


def _bind_ip(domain_name, port, ip):
    '''
    resolve (domain_name,port) to a given ip
    '''
    key = (domain_name, port)
    # (family, type, proto, canonname, sockaddr)
    value = (socket.AddressFamily.AF_INET, socket.SocketKind.SOCK_STREAM, 6, '', (ip, port))
    etc_hosts[key] = [value]


_bind_ip('example.com', 443, '1.2.3.4')
# this sends requests to 1.2.3.4
response = requests.get('https://www.example.com/foo.php', verify=True)