【问题标题】:Python Requests - Use navigate site by servers IPPython 请求 - 通过服务器 IP 使用导航站点
【发布时间】:2015-05-01 21:04:21
【问题描述】:

我想抓取一个网站,但 cloudflare 妨碍了我。我能够获得服务器 IP,所以 cloudflare 不会打扰我。

如何在请求库中使用它?

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

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

【问题讨论】:

    标签: python python-requests


    【解决方案1】:

    您必须设置一个自定义标头host,其值为example.com,类似于:

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

    应该可以解决问题。如果要验证,请输入以下命令(需要 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
    

    【讨论】:

    • 仅适用于 http。如果您使用 https 执行此操作,您将收到主机名与证书不匹配的错误。
    • @tymoteusz-paul 您可以在 Requests 中禁用 SSL 证书验证,这应该允许您访问服务器,但会向中间人攻​​击:stackoverflow.com/questions/15445981/…
    • 看起来有人创建了一个实用程序来允许请求为 SSL 连接指定主机头:toolbelt.readthedocs.io/en/latest/…
    【解决方案2】:

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

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

    可以使用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})
    

    针对 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]>
    

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

    【讨论】:

    • 效果很好。不幸的是,似乎存在一个错误,当您使用 POST 和数据生成器(用于分块编码)时,您最终会拥有两个“主机”标头,即原始标头和新标头。 :(
    • @jlh:如果你有一个简单的可重现案例,我可以看看是否可以修复。
    • @jlh:看起来像一个错误;通过 HTTP 库采用不同的低级路径,并且不会被告知跳过主机头。
    【解决方案3】:

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

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

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

    另外,我试过了

    requests_toolbelt

    pip install requests[security]

    forcediphttpsadapter

    这里提到的所有解决方案using requests with TLS doesn't give SNI support

    直接点击 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)
    

    【讨论】:

      【解决方案4】:

      对 HTTPS/SNI 支持的回答:在requests_toolbelt module 中使用HostHeaderSSLAdapter

      上述解决方案适用于非加密 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"})
      

      【讨论】:

      • 我不确定在此期间发生了什么,但截至 2020 年 11 月,这似乎完全没有效果。
      猜你喜欢
      • 1970-01-01
      • 2021-07-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-09
      • 2017-04-11
      • 2023-03-20
      • 2015-09-17
      相关资源
      最近更新 更多