随机更换IP地址和用户代理抓取网站数据

在很多情况下,我们需要抓取海量的网络数据来进行数据汇总、市场分析、模型训练…这样的业务场景相当多。不过,当您运行网络爬虫时,可能会遇到这样的问题——短时间内从同一IP和设备向目标网站发送太多请求时,该站点可能会出现验证码,甚至屏蔽您的IP地址以阻止您抓取数据。

那么,抓取网站数据的时候如何规避验证码或者避免被屏蔽呢?我们将在本文使用 Python 通过两种不同的方法来解决此问题:

  1. 随机更换IP地址
  2. 更改用户代理(User-Agent)

随机更换IP地址

本方法主要思路是为每个请求提供不同的网络代理。如果您继续使用同一个IP,网站监测到后会屏蔽该地址。要解决这个问题,我们可以频繁的更换IP地址,并为每个请求使用不同的IP地址。这种方式虽然可能会降低数据抓取效率,却能够帮助您避免被目标站点屏蔽。

当然,要达成此目标,您可以使用 tor 浏览器来进行。但这种手动方式显然效率极其低下。这里我们将使用一个名为 torpy 的 Python tor 客户端,该客户端不需要您在系统中下载安装 tor 浏览器。

torpy库的 GitHub 链接如下:

您可以在终端使用以下命令进行安装:

pip install torpy

假设我们想从以下网站爬取数据:

urls = [ "https://www.google.com", "https://www.facebook.com", "https://www.youtube.com", "https://www.amazon.com", "https://www.reddit.com", "https://www.instagram.com", "https://www.linkedin.com", "https://www.wikipedia.org", "https://www.twitter.com"]

我们将编写一个函数,并为每个URL请求启用一个新会话,然后循环抓取以上所有地址。代码如下:


from torpy.http.requests import TorRequests
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def send_request(url):
    with TorRequests() as tor_requests:
        with tor_requests.get_session() as sess:
            # print the IP address of the proxy
            print(sess.get("http://httpbin.org/ip").json())
            html_content = sess.get(url, timeout=10).text
            # your scraping code here ..


if __name__ == "__main__":
    # put some random urls
    urls = [
        "https://www.google.com",
        "https://www.facebook.com",
        "https://www.youtube.com",
        "https://www.amazon.com",
        "https://www.reddit.com",
        "https://www.instagram.com",
        "https://www.linkedin.com",
        "https://www.wikipedia.org",
        "https://www.twitter.com",
    ]

    for link in urls:
        try:
            send_request(link)
        except Exception as e:
            print(e)
            pass

上面代码中,我们通过 https://httpbin.org/ip 验证IP地址,并在第11行代码输出当前会话所属IP地址,方便我们查看每个请求的IP地址。

输出如下:

{'origin': '107.189.7.175'}
{'origin': '185.220.101.162'}
{'origin': '185.220.101.79'}
{'origin': '103.236.201.88'}
{'origin': '185.220.100.242'}
{'origin': '209.141.53.20'}
{'origin': '198.98.62.79'}
{'origin': '184.105.220.24'}
{'origin': '193.218.118.167'}

如您所见,每个请求都使用了不同的IP地址。

更改用户代理(User-Agent)

由于多数网站都会屏蔽来自未知/非标准浏览器的请求,因此,我们通常在每次请求时通过指定用户代理(User-Agent)的形式来传递浏览器信息,如下所示:

import requests
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/600.4.10 (KHTML, like Gecko) Version/8.0.4 Safari/600.4.10"
HEADERS = {"User-Agent": USER_AGENT}
html_content = requests.get(url, headers=HEADERS, timeout=40).text

用户代理通常包含应用程序类型、操作系统及相关版本等信息。

如果您通过以上代码持续使用同一用户代理发送请求,目标站点可能会检测到来自同一设备的频繁访问从而进行阻断。为避免此类情况,我们必须为每次请求指定不同的用户代理。您可以从这个网站找到许多有效的用户代理信息。

然后,我们整理一个用户代理列表,并在每个请求中随机选择一个用户代理。我们先来列出这个用户代理列表:

import random
AGENT_LIST = [
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
"Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:24.0) Gecko/20100101 Firefox/24.0",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/91.0.4472.114 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0"
]
USER_AGENT = random.choice(AGENT_LIST)

然后使用以下代码为每个请求随机更改IP地址和用户代理:

from email.header import Header
from wsgiref import headers
from torpy.http.requests import TorRequests
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import random


# https://developers.whatismybrowser.com/useragents/explore/operating_system_name/linux/
# https://developers.whatismybrowser.com/useragents/explore/operating_system_name/mac-os-x/
AGENT_LIST = [
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
    "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:24.0) Gecko/20100101 Firefox/24.0",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/91.0.4472.114 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:77.0) Gecko/20100101 Firefox/77.0",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/78.0.3904.70 Safari/537.36",
    "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36",
]


def send_request(url, HEADERS):
    with TorRequests() as tor_requests:
        with tor_requests.get_session() as sess:
            # print the IP address of the proxy
            print(sess.get("http://httpbin.org/ip").json())
            html_content = sess.get(url, headers=HEADERS, timeout=10).text
            # your scraping code here ..
            print(HEADERS["User-Agent"])


if __name__ == "__main__":
    # put some random urls
    urls = [
        "https://www.google.com",
        "https://www.facebook.com",
        "https://www.youtube.com",
        "https://www.amazon.com",
        "https://www.reddit.com",
        "https://www.instagram.com",
        "https://www.linkedin.com",
        "https://www.wikipedia.org",
        "https://www.twitter.com",
    ]

    for link in urls:
        HEADERS = {"User-Agent": random.choice(AGENT_LIST)}
        try:
            send_request(link, HEADERS)
        except Exception as e:
            print(e)
            pass

为避免触发网站验证码机制,我们可以在每个请求之前加上 time.sleep()

def send_request(url, HEADERS):
    with TorRequests() as tor_requests:
        with tor_requests.get_session() as sess:
            # print the IP address of the proxy
            print(sess.get("http://httpbin.org/ip").json())
            # pause randomly between 1 to 3 seconds
            time.sleep(random.randint(1, 3))
            html_content = sess.get(url, headers=HEADERS, timeout=10).text
            # your scraping code here ..
            print(HEADERS["User-Agent"])

如上所示,我们在第 7 行添加了 time.sleep() 语句,随机选择1-3之间的整数。

本文介绍的方法虽然会降低您抓取数据的速度,但有助于避免被目标网站屏蔽,并能够绕过网站验证码。将此思路应用到一些成熟的开源网络爬虫项目,堪当大任。