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

use*_*623 4 python python-requests

我想抓取一个网站,但是cloudflare正在阻碍.我能够获得服务器IP,因此cloudflare不会打扰我.

如何在请求库中使用它?

例如,我想直接进入 www.example.com/foo.php,但在请求中它将解析cloudflare网络上的IP而不是我想要它使用的IP.如何让它使用我想要它使用的那个?

我会发送一个请求,所以真正的IP与主机设置为www.example.com,但这只会给我一个主页.如何访问网站上的其他链接?

Tym*_*aul 13

您必须设置一个host值为的自定义标头example.com,例如:

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

应该做的伎俩.如果要验证,则键入以下命令(需要netcat):nc -l -p 80然后运行上面的命令.它将在netcat窗口中生成输出:

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
Run Code Online (Sandbox Code Playgroud)

  • 看起来有人创建了一个实用程序来允许请求为 SSL 连接指定主机标头:https://toolbelt.readthedocs.io/en/latest/adapters.html#hostheaderssladapter (3认同)
  • 仅适用于http。如果使用https进行操作,则会收到主机名与证书不匹配的错误。 (2认同)
  • @tymoteusz-paul 您可以在请求中禁用 SSL 证书验证,这应该允许您访问服务器,但会遭受中间人攻击:/sf/ask/1081218701/我禁用安全证书签入 python 请求 (2认同)

Mar*_*ers 5

您必须告诉requests伪造Host标头,并使用IP地址替换URL中的主机名:

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

可以使用urlparse库完成URL"修补" :

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

response = requests.get(ip_url, headers={'Host': hostname})
Run Code Online (Sandbox Code Playgroud)

针对Stack Overflow的演示:

>>> import urlparse
>>> import socket
>>> url = 'http://stackoverflow.com/help/privileges'
>>> parsed = urlparse.urlparse(url)
>>> hostname = parsed.hostname
>>> hostname
'stackoverflow.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]>
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我动态查找IP地址.


Pat*_*ick 5

HTTPS/SNI 支持答案:HostHeaderSSLAdapterrequests_toolbelt 模块中使用

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

上面提供了一个简单的方法,可以将传输适配器添加到为您处理此问题的请求中。

例子

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"})
Run Code Online (Sandbox Code Playgroud)


小智 5

我认为将 https 请求发送到特定 IP 的最佳方法是添加一个自定义解析器,将域名绑定到您要访问的 IP。这样,SNI 和主机头都正确设置,并且证书验证始终可以作为 Web 浏览器成功。

否则,即使您尝试不同的标头组合和验证参数,您也会看到各种问题,例如InsecureRequestWarning,SSLCertVerificationError和 SNI 始终缺失Client Hello

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

另外,我试过

requests_toolbelt

pip install requests[security]

强制iphttps适配器

这里提到的所有使用 TLS 请求的解决方案都不提供 SNI 支持

当直接点击 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)
Run Code Online (Sandbox Code Playgroud)