在使用 Python 抓取网站时设置代理

Setting proxies when crawling websites with Python

我想为我的抓取工具设置代理。我正在使用请求模块和 Beautiful Soup。 我找到了 API 个链接列表,这些链接提供了 4 种协议的免费代理。

所有使用 3/4 协议(HTTP、SOCKS4、SOCKS5)的代理都可以工作,除了一个,那就是使用 HTTPS 协议的代理。 这是我的代码:

from bs4 import BeautifulSoup
import requests
import random
import json

# LIST OF FREE PROXY APIS, THESE PROXIES ARE LAST TIME TESTED 50 MINUTES AGO, PROTOCOLS: HTTP, HTTPS, SOCKS4 AND SOCKS5
list_of_proxy_content = ["https://proxylist.geonode.com/api/proxy-list?limit=150&page=1&sort_by=lastChecked&sort_type=desc&filterLastChecked=50&country=CH&protocols=http%2Chttps%2Csocks4%2Csocks5",
                        "https://proxylist.geonode.com/api/proxy-list?limit=150&page=1&sort_by=lastChecked&sort_type=desc&filterLastChecked=50&country=FR&protocols=http%2Chttps%2Csocks4%2Csocks5",
                        "https://proxylist.geonode.com/api/proxy-list?limit=150&page=1&sort_by=lastChecked&sort_type=desc&filterLastChecked=50&country=DE&protocols=http%2Chttps%2Csocks4%2Csocks5",
                        "https://proxylist.geonode.com/api/proxy-list?limit=1500&page=1&sort_by=lastChecked&sort_type=desc&filterLastChecked=50&country=AT&protocols=http%2Chttps%2Csocks4%2Csocks5",
                        "https://proxylist.geonode.com/api/proxy-list?limit=150&page=1&sort_by=lastChecked&sort_type=desc&filterLastChecked=50&country=IT&protocols=http%2Chttps%2Csocks4%2Csocks5"]


# EXTRACTING JSON DATA FROM THIS LIST OF PROXIES
full_proxy_list = []
for proxy_url in list_of_proxy_content:
    
    proxy_json = requests.get(proxy_url).text
    proxy_json = json.loads(proxy_json)
    proxy_json = proxy_json["data"]

    full_proxy_list.extend(proxy_json)

# CREATING PROXY DICT
final_proxy_list = []
for proxy in full_proxy_list:

    #print(proxy) # JSON VALUE FOR ALL DATA THAT GOES INTO PROXY

    protocol = proxy['protocols'][0]
    ip_ = proxy['ip']
    port = proxy['port']
        
    proxy = {protocol : protocol + '://' + ip_ + ':' + port}

    final_proxy_list.append(proxy)


# TRYING PROXY ON 3 DIFERENT WEBSITES
for proxy in final_proxy_list:

    print(proxy)
    try:
        r0 = requests.get("https://edition.cnn.com/", proxies=proxy, timeout = 15)
        if r0.status_code == 200:
            print("GOOD PROXY")
        else:
            print("BAD PROXY")
    except:
        print("proxy error")
        
    try:        
        r1 = requests.get("https://www.buelach.ch/", proxies=proxy, timeout = 15)
        if r1.status_code == 200:
            print("GOOD PROXY")        
        else:
            print("BAD PROXY")
    except:
        print("proxy error")
        
    try:      
        r2 = requests.get("https://www.blog.police.be.ch/", proxies=proxy, timeout = 15)
        if r2.status_code == 200:
            print("GOOD PROXY")        
        else:
            print("BAD PROXY")
    except:
        print("proxy error")

    print()

我的问题是,为什么 HTTPS 代理不起作用,我做错了什么?

我的代理是这样的:

{'socks4': 'socks4://185.168.173.35:5678'}
{'http': 'http://62.171.177.80:3128'}
{'https': 'http://159.89.28.169:3128'}

我看到有时人们会这样传递代理:

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

但是这个字典有2个协议,但是在链接中它只有http,为什么? 我可以只传递一个吗,我可以在这个dict中传递10个不同的IP地址吗?

我对这个主题做了一些研究,现在我很困惑为什么你想要一个 HTTPS 代理。

虽然需要 HTTP 代理是可以理解的,但(HTTP 未加密)HTTPS 是安全的。

您的代理可能没有连接是因为您不需要它吗?

我不是代理专家,所以如果我发布了一些完全愚蠢的东西,我深表歉意。


虽然我不想让你完全空手而归。 如果您正在寻找完全的隐私,我建议您使用 VPN。 Windscribe 和 RiseUpVPN 都是免费的,并且会加密您计算机上的所有数据。 (桌面版,不是浏览器扩展。)

虽然这不是一个完全自动化的过程,但它仍然非常有效。

您正在寻找的是 class 来保存代理,这些代理在您的爬虫操作后定期轮换(取决于时间或取决于指令),以掩盖您的身份。

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

这些类型的 IP 地址引用一个域,该域在内部刷新基础 IP 地址并将您重定向到该域下的 IP 地址之一。

其中大部分是付费服务。

您的代码有几处错误。我会先解决容易实现的问题。

首先,您的 SOCKS 代理也没有工作。这就是为什么。 代理字典的正确写法可以参考requests documentation.

# your way
proxy = {'socks4': 'socks4://ip:port'}

# the correct way
proxy = {'https': 'socks4://ip:port'}   # note the s in https

# or another correct way
proxy = {'http': 'socks4://ip:port'}  # Note the http with no s

# best correct way if your urls are mixed http:// https://
proxies = {
  'http': 'socks4://ip:port',
  'https': 'socks4://ip:port',
}

这些条目中的 httphttps 不是代理服务器的协议,而是您的 url。

例如:https://www.example.com vs http://www.example.com.

https:// url 的请求将转到 https 条目,而对 http:// url 的请求将通过 http条目。如果您只提供一个条目 {'http': 'socks4://ip:port'},而 url 请求是针对 https:// url,则该请求 不会 得到代理,你自己的ip就会暴露。由于浏览时没有 socks4://www.example.com 这样的东西,所以您发出的请求没有被代理。

当通过代理和 VPN 进行任何工作时,我不喜欢测试代码和向服务器发送请求,我将 运行 最后的代码。我喜欢使用 ipinfo.io。他们的 json 响应包括有关连接 ip 的信息。这样,我可以确保连接通过代理,而不是发送误报。

注意: 由于负载平衡器,连接 IP 与代理 IP 不同的情况并不少见。只要确保连接的 ip 不是你自己的。您可以使用浏览器访问下面代码中的 url 查看您自己的 ip。

因为您使用的是 {'socks4': 'socks4://ip:port'} 而不是正确的 {'https': 'socks4://ip:port'},您仍然会收到 200 个状态代码并且您的代码返回误报。它返回 200 是因为您确实连接了,但使用的是您自己的 IP,而不是通过代理。

由于您没有提供实际发生的具体情况,我在您的代码中添加了一些快速而肮脏的错误处理以查明发生了什么。一些错误与服务器端配置有关,因为大多数 https 代理都需要某种身份验证,例如证书或登录(尽管它们是“免费的”和“public”。

我的不完美但有效的代码如下。在 Python 3.8.12 上测试。下面是关于代理连接错误的一些信息。

提示:检查您的 url。第一个中的 country=CH 应该是 country=CNcountry=AT 应该是 country=AR。我的代码反映了这一点。

from bs4 import BeautifulSoup
import requests
import json
import time

# LIST OF FREE PROXY APIS, THESE PROXIES ARE LAST TIME TESTED 50 MINUTES AGO
# PROTOCOLS: HTTP, HTTPS, SOCKS4 AND SOCKS5
list_of_proxy_content = [
    "https://proxylist.geonode.com/api/proxy-list?limit=150&page=1&sort_by=lastChecked&sort_type=desc&filterLastChecked=50&country=CN&protocols=http%2Chttps%2Csocks4%2Csocks5",
    "https://proxylist.geonode.com/api/proxy-list?limit=150&page=1&sort_by=lastChecked&sort_type=desc&filterLastChecked=50&country=FR&protocols=http%2Chttps%2Csocks4%2Csocks5",
    "https://proxylist.geonode.com/api/proxy-list?limit=150&page=1&sort_by=lastChecked&sort_type=desc&filterLastChecked=50&country=DE&protocols=http%2Chttps%2Csocks4%2Csocks5",
    "https://proxylist.geonode.com/api/proxy-list?limit=1500&page=1&sort_by=lastChecked&sort_type=desc&filterLastChecked=50&country=AR&protocols=http%2Chttps%2Csocks4%2Csocks5",
    "https://proxylist.geonode.com/api/proxy-list?limit=150&page=1&sort_by=lastChecked&sort_type=desc&filterLastChecked=50&country=IT&protocols=http%2Chttps%2Csocks4%2Csocks5",
]


# EXTRACTING JSON DATA FROM THIS LIST OF PROXIES
full_proxy_list = []
for proxy_url in list_of_proxy_content:

    proxy_json = requests.get(proxy_url).text
    proxy_json = json.loads(proxy_json)
    proxy_json = proxy_json["data"]

    full_proxy_list.extend(proxy_json)

    if not full_proxy_list:
        print("No proxies to check. Exiting...")
        exit
    else:
        print(f"Found {len(full_proxy_list)} proxy servers. Checking...\n")

# CREATING PROXY DICT
final_proxy_list = []
for proxy in full_proxy_list:

    # print(proxy)  # JSON VALUE FOR ALL DATA THAT GOES INTO PROXY

    protocol = proxy["protocols"][0]
    ip_ = proxy["ip"]
    port = proxy["port"]

    proxy = {
        "https": protocol + "://" + ip_ + ":" + port,
        "http": protocol + "://" + ip_ + ":" + port,
    }

    final_proxy_list.append(proxy)

# TRYING PROXY ON 3 DIFERENT WEBSITES
for proxy in final_proxy_list:

    print(proxy)
    try:
        # Use ipinfo.io to test proxy ip
        url = "https://ipinfo.io/json?token=67e01402d14101"
        r0 = requests.get(url, proxies=proxy, timeout=15)

        if r0.status_code == 200:
            # The 3-line block below only works on ipinfo.io
            output = r0.json()
            real_ip = output["ip"]
            print(f"GOOD PROXY [IP = {real_ip}] {proxy}\n")

            # Do something with the response
            html_page = r0.text
            soup = BeautifulSoup(r0.text, "html.parser")
            print(soup, "\n")

            r0.close()  # close the connection so it can be reused

            # Break out of the proxy loop so we do not send multiple successful
            # requests to the same url. Info needed was already obtained.
            # Comment out to check all possible proxies during testing.
            break
        else:
            # If the response code is something other than 200,
            # it means the proxy worked, but the website did not.
            print(f"BAD URL: [status code: {r0.status_code}]\n{r0.headers}\n")
            r0.close()

        time.sleep(5)  # Don't overload the server

    except Exception as error:
        print(f"BAD PROXY: Reason: {str(error)}\n")

您看到的大多数错误都是超时错误,这应该是不言自明的。

其他错误是由于其配置阻止您连接而导致的服务器端错误。

没有太技术性的短名单:

  • Remote end closed connection without response为服务器端 尽管已连接到它,但还是完全拒绝发送您的请求。

  • 407 Proxy Authentication Required 是我的错误之一 上文提到的。这要么希望您提供 user/pass 要么 证书.

  • [Errno 111] Connection refused 是我的错误之一 如上所述。

重要提示: 如果您在 运行 之后看到以下错误 check_hostname requires server_hostnameEOF occurred in violation of protocolSSL: WRONG_VERSION_NUMBER上面的代码,降级你的 urllib3 库。在一些最新版本中存在代理错误以及其他一些错误。您可以使用命令 pip install -U urllib3==1.25.11python3 -m pip install -U urllib3==1.25.11.

来执行此操作

您必须像下面这样介绍您的证书。这个对我有用。我不知道免费代理服务是否提供证书,但您可以在 SSL 服务或代理提供商处获得证书。我的代理提供商(zyte)也提供CA证书。

verify='C:/Python39/zyte-proxy-ca.crt' 

一个例子;

import requests
from bs4 import BeautifulSoup   

response = requests.get(
    "https://www.whatismyip.com/",
    proxies={
        "http": "http://proxy:port/",
        "https": "http://proxy:port/",
    },
    verify='C:/Python39/zyte-proxy-ca.crt' 
)

print("Scrape Process Has Been Successfuly...")


soup = BeautifulSoup(response.text, 'lxml')
print(soup.title)