玩爬虫这么久,我踩过最多的坑,不是解析难、不是异步慢,而是IP 被疯狂封禁。
真的太烦了:本地IP跑一会儿就403、访问超时、直接拉黑,爬虫动不动就罢工,每次都得停手换网络。
后面我干脆自己手写了一套轻量代理IP轮换池,不用 Redis、不用复杂框架,纯原生 Requests 就能跑。自动换IP、自动剔除坏IP、失败自动重试,算是彻底解决了普通爬虫被封IP的问题。
今天把我自己日常在用的这套完整方案分享出来,新手直接复制就能跑,不用瞎折腾。
先说说:我们为什么一定要用代理池?
其实道理特别简单:
你一直用同一个IP高频请求网站,服务器一眼就能识别出你是爬虫,直接风控、封禁。
代理池的作用就是每次请求换个新IP,伪装成不同的普通用户在访问,网站根本抓不到你的真实地址,被封概率直接降到很低。
我这套代码主打一个:轻量、省心、全自动,不用手动管IP失效、不用自己切换,全部程序自动处理。
环境超级简单
全程只需要一个依赖,就是 requests,直接装就行:
pip install requests代理IP这块我说句实话:
新手练习随便找免费代理就行,够用;但如果是长期跑项目、正式爬数据,一定要用付费代理。免费代理通病就是寿命短、延迟高,只适合练手学习。
我理解的代理池核心逻辑(通俗版)
我自己写代码的时候,就遵循四个核心步骤,特别好理解:
1.捞IP:从代理接口批量拿一堆代理IP出来
2.筛IP:逐个测试,把超时、连不上的坏IP全部删掉
3.存IP:把能用的优质IP存在列表里备用
4.自动换IP:每次发请求随机挑一个,坏了自动丢、空了自动更新
整套流程全自动,完全不用人工干预。
直接上我日常在用的完整源码
这是我精简优化后的版本,容错、重试、删坏IP、自动更新全部写好了,注释我也写得很直白,大家直接照搬运行就行。
import requests import random import time class ProxyPool: def __init__(self): # 存放可用代理IP self.proxy_list = [] # 用来测试代理是否正常的公共接口 self.test_url = "http://httpbin.org/ip" # 超时5秒直接判定失效 self.timeout = 5 # 最多重试3次 self.max_retry = 3 def get_free_proxies(self): """获取代理(站大爷API)""" proxy_api = "https://www.zdaye.com/get" try: res = requests.get(proxy_api, timeout=10) if res.status_code == 200: proxy_data = res.json() return proxy_data.get("proxy_list", []) except Exception as e: print("暂时获取不到新代理IP") return [] def check_proxy(self, proxy): """检测单个代理是否能用""" try: proxies = { "http": f"http://{proxy}", "https": f"http://{proxy}" } res = requests.get(self.test_url, proxies=proxies, timeout=self.timeout) if res.status_code == 200: print(f"可用代理:{proxy}") return True except Exception: print(f"失效代理:{proxy}") return False def init_proxy_pool(self): """初始化并清洗代理池""" print("正在刷新代理池,筛选可用IP...") raw_proxies = self.get_free_proxies() if not raw_proxies: print("本次未获取到任何代理IP!") return # 只保留有效IP valid_proxies = [p for p in raw_proxies if self.check_proxy(p)] self.proxy_list = valid_proxies print(f"代理池刷新完成,当前可用IP数量:{len(self.proxy_list)}") def get_random_proxy(self): """随机拿一个可用代理,空了就自动刷新""" if not self.proxy_list: print("代理池空了,自动重新更新中...") self.init_proxy_pool() return random.choice(self.proxy_list) if self.proxy_list else None def remove_invalid_proxy(self, proxy): """删掉失效的代理""" if proxy in self.proxy_list: self.proxy_list.remove(proxy) print(f"移除失效IP:{proxy},剩余可用:{len(self.proxy_list)}") def proxy_request(self, url, method="GET", **kwargs): """自带IP轮换、重试的请求方法,直接替代原生requests.get/post""" retry_count = 0 while retry_count < self.max_retry: proxy = self.get_random_proxy() if not proxy: raise Exception("当前无可用代理") proxies = { "http": f"http://{proxy}", "https": f"http://{proxy}" } try: kwargs["proxies"] = proxies kwargs["timeout"] = self.timeout if method.upper() == "GET": res = requests.get(url, **kwargs) elif method.upper() == "POST": res = requests.post(url, **kwargs) else: raise Exception("仅支持 GET / POST") print(f"请求成功,本次IP:{proxy}") return res except Exception: retry_count += 1 self.remove_invalid_proxy(proxy) print(f"请求失败,换IP重试,剩余次数:{self.max_retry - retry_count}") time.sleep(1) raise Exception("多次重试全部失败,暂无可用代理") # 全局实例化,全程复用 proxy_pool = ProxyPool() proxy_pool.init_proxy_pool()简单测试一下,看IP是不是真的在轮换
写个几行测试代码,跑五次请求就能明显看到,每次IP都不一样:
if __name__ == "__main__": test_url = "http://httpbin.org/ip" for i in range(5): try: resp = proxy_pool.proxy_request(test_url) print(f"第{i+1}次请求IP:{resp.text}\n") except Exception as e: print(f"第{i+1}次请求失败:{e}\n") time.sleep(1)跑通之后你会发现:
失效IP自动剔除、空池自动更新、每次请求随机换IP,整个过程完全不用自己管,极其省心。
长期使用我加的几个实用优化(非常有用)
1. 后台定时自动刷新代理池
代理IP都是短命的,长期跑爬虫必须定时更新,我一般10分钟刷一次:
import threading def refresh_proxy_pool(): while True: time.sleep(600) proxy_pool.init_proxy_pool() # 后台线程默默运行,不影响主程序 threading.Thread(target=refresh_proxy_pool, daemon=True).start()2. 免费代理仅学习用,生产务必换付费
这点我真心建议大家:
免费代理玩玩就行,存活率极低、经常超时。真正做项目直接对接付费代理API,替换掉代码里的代理接口即可,稳定性直接翻倍。
3. 别高频狂请求
就算有代理池,也别一秒几十次乱请求。适当加随机休眠,模拟真人浏览节奏,风控概率会更低。
平时最常见的问题汇总
Q:为什么我成功率很低、老是超时?
基本都是免费代理本身质量差,换付费代理、稍微调大超时时间就能解决。
Q:为什么有时候代理池直接空了?
免费接口IP数量有限、失效快,开启定时刷新,或者直接换付费接口就稳了。
Q:HTTPS 网站请求失败怎么回事?
部分老旧免费代理不支持HTTPS,尽量筛选高匿通用代理即可。
最后小结
说实话,对于绝大多数个人爬虫、中小型项目,根本不用上 Redis 分布式代理池。
我这套轻量轮换方案,零额外组件、代码少、好维护、全自动,完全够用。
只要你不是超大规模分布式爬虫,这套代码直接拿去长期跑项目完全没问题,彻底告别爬虫被封IP的烦恼。