欢迎访问宙启技术站
智能推送

Python爬虫实现搭建代理ip池

发布时间:2023-05-16 04:08:17

在进行爬虫开发时,我们经常需要使用代理 IP 来避免被目标网站封禁或反爬虫。但是,使用免费代理 IP 很容易被封禁,所以我们需要自己搭建一个代理 IP 池,以便更好地应对反爬虫策略。

本文将介绍用 Python 实现搭建代理 IP 池的步骤。具体来说,我们将介绍如何爬取免费代理 IP,并将其存储到数据库中,然后编写一个代理 IP 池模块,以便我们在爬虫中使用这些代理 IP。

## 爬取免费代理 IP

首先,我们需要爬取免费代理 IP。以西刺代理(https://www.xicidaili.com/)为例,它提供了免费的 HTTP 和 HTTPS 代理 IP。我们可以使用 requests 库和 Beautiful Soup 库来抓取代理 IP。

import requests
from bs4 import BeautifulSoup

def get_proxy_ips():
    """从西刺代理抓取 HTTP 和 HTTPS 代理 IP"""
    url = 'https://www.xicidaili.com/wn/'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
    }
    proxies = []
    for i in range(1, 6): # 爬取前五页的代理 IP
        resp = requests.get(url + str(i), headers=headers)
        soup = BeautifulSoup(resp.text, 'html.parser')
        table = soup.find(name='table', attrs={'id': 'ip_list'})
        tr_list = table.find_all(name='tr')[1:] #       个 tr 是表头,忽略掉
        for tr in tr_list:
            td_list = tr.find_all(name='td')
            ip = td_list[1].text
            port = td_list[2].text
            protocol = td_list[5].text.lower()
            proxies.append(f'{protocol}://{ip}:{port}')
    return proxies

以上代码中,我们定义了一个 get_proxy_ips 函数,它从西刺代理抓取前 5 页的 HTTP 和 HTTPS 代理 IP。我们使用 requests 库发送请求,并使用 Beautiful Soup 库解析 HTML 页面。然后,我们从页面的表格中提取代理 IP 的部分信息(ip, port, protocol),并将其存储到一个列表中。最后,我们返回这个代理 IP 列表。

## 存储代理 IP 到数据库

我们需要一个数据库,来存储从网上获取的代理 IP。我们将使用 MongoDB,一个 NoSQL 数据库。首先,我们安装 pymongo 库:pip install pymongo

然后,我们创建一个名为 proxypool 的数据库,并在其中创建一个名为 proxies 的集合,用于存储代理 IP。在 PyMongo 中,一个 MongoDB 数据库对应一个 MongoClient 对象,一个 MongoDB 集合对应一个 Collection 对象。

import pymongo

class MongoDBProxyPool:
    """基于 MongoDB 的代理 IP 池"""
    def __init__(self):
        self.client = pymongo.MongoClient('localhost', 27017)
        self.db = self.client['proxypool']
        self.collection = self.db['proxies']

    def add_proxies(self, proxies):
        """将代理 IP 添加到 MongoDB 数据库"""
        for proxy in proxies:
            self.collection.update_one({'ip': proxy}, {'$set': {'ip': proxy}}, upsert=True)

    def get_proxies(self):
        """从 MongoDB 数据库获取代理 IP"""
        proxies = []
        cursor = self.collection.find()
        for c in cursor:
            proxies.append(c['ip'])
        return proxies
        
    def clear_proxies(self):
        """清空 MongoDB 数据库"""
        self.collection.delete_many({})

以上代码中,我们定义了一个 MongoDBProxyPool 类。在构造函数中,我们初始化了一个 MongoDB 客户端、一个数据库对象和一个集合对象。我们实现了三个方法:

- add_proxies:将一个代理 IP 添加到 MongoDB 集合中。MongoDB 的 update_one 方法会尝试更新一个文档,如果文档不存在,将插入一个新文档。upsert=True 用于控制文档不存在时自动插入。

- get_proxies:从 MongoDB 集合中获取所有的代理 IP,并返回它们的列表。

- clear_proxies:清空 MongoDB 集合中的内容。

## 搭建代理 IP 池

我们需要一个代理 IP 池模块,使我们可以在爬虫中使用免费代理 IP。实现一个代理 IP 池有许多细节问题,比如:

- 如何检测代理 IP 是否可用?

- 如何保证代理 IP 足够多?

- 如何实现轮询代理 IP?

以下是一个简单的代理 IP 池实现,它可以帮助你理解这些问题。

import random
import requests
import time
from multiprocessing import Process, Lock
from ProxyPool import MongoDBProxyPool

class ProxyPool:
    """代理 IP 池"""
    def __init__(self, capacity=100):
        self.capacity = capacity # 代理 IP 池的容量
        self.pool = [] # 代理 IP 池
        self.lock = Lock() # 进程锁
        self.mongo = MongoDBProxyPool()

    def get_random_proxy(self):
        """随机从代理 IP 池中获取一个代理 IP"""
        with self.lock:
            if len(self.pool) == 0:
                self.pool = self.mongo.get_proxies()
            if len(self.pool) == 0:
                raise ValueError('Proxy pool is empty')
            return random.choice(self.pool)

    def fetch_proxies(self):
        """更新代理 IP 池中的代理 IP"""
        with self.lock:
            self.mongo.clear_proxies()
            while True:
                proxies = get_proxy_ips()
                self.mongo.add_proxies(proxies)
                self.pool = self.mongo.get_proxies()
                if len(self.pool) >= self.capacity:
                    break
                time.sleep(10)

    def start(self):
        """定期更新代理 IP 池"""
        p = Process(target=self.fetch_proxies)
        p.start()

    def close(self):
        """关闭代理 IP 池"""
        with self.lock:
            self.mongo.client.close()

if __name__ == '__main__':
    proxy_pool = ProxyPool()
    proxy_pool.start()
    time.sleep(1)
    while True:
        proxy = proxy_pool.get_random_proxy()
        print(proxy)
        resp = requests.get('http://httpbin.org/ip', proxies={'http': proxy})
        print(resp.text)
        time.sleep(3)

以上代码中,我们定义了一个 ProxyPool 类。在构造函数中,我们初始化了代理 IP 池、进程锁和 MongoDBProxyPool 对象。我们实现了以下方法:

- get_random_proxy:随机从代理 IP 池中获取一个代理 IP。如果代理 IP 池为空,则从 MongoDB 中获取代理 IP,并将它们添加到代理 IP 池中。

- fetch_proxies:更新代理 IP 池中的代理 IP。这个方法在后台进程中运行。首先,我们调用 clear_proxies 方法清空 MongoDB 数据库中的 Proxy 集合。然后,我们循环调用 get_proxy_ips 函数,获取代理 IP,并存储到 MongoDB 中。我们不断循环这个过程,直到代理 IP 池中的代理 IP 数量达到了设定的容量,并调用 time.sleep(10) 让当前进程挂起 10 秒。

- start:启动一个后台进程,定期更新代理 IP 池。在本例中,我们定期更新代理 IP 池的时间间隔是 10 秒。

- close:关闭代理 IP 池。我们调用 MongoDBProxyPool 对象的 close 方法,关闭 MongoDB 客户端连接。

在 main 函数中