做爬虫这行迟早得面对“代理”这三个字。很多新手把代理当成一个可选项觉得代码能跑就行直到某天突然发现请求返回403、验证码满天飞或者干脆连接超时——这时候才意识到代理不是加分项而是爬虫真正能“活着跑下去”的生命线。这篇内容就围绕Python爬虫中代理的完整配置展开。我尽量不堆概念直接把最常用的requests写法、selenium写法、动态代理池轮换方案以及我这些年踩过和修复过的坑全部摊开讲。内容适合刚入门requests爬虫、对IP被封毫无头绪的同学也适合已经会写基础爬虫、但还没梳理清楚代理方案的人。1. 为什么需要代理先搞懂配置背后的逻辑1.1 被限IP不是玄学是反爬机制的必经之路几乎每个有反爬意识的目标网站都会维护一个“单IP请求频率记录”。我用本机IP去写爬虫抓几千条数据前一两百条请求可能一切正常但一旦频率超过网站的阈值后端就会开始返回异常结果。常见的表现有这几种直接返回403、跳转到验证码页面、返回空数据但HTTP状态码还是200、甚至接口直接返回一段伪造的JSON。这种时候最优解就是换IP也即让请求走代理服务器由代理服务器以自己的IP地址去访问目标网站目标网站看到的是代理IP而不是我的本机IP。这样就算代理IP被封了换下一个就是不会影响到本地开发环境。很多人会困惑为什么不直接改请求头里的User-Agent和Referer就够了因为请求头能伪造的基础标识只是最表面的反爬维度。对于基于IP维度的频率统计改请求头完全无法绕过。这也是为什么代理配置是爬虫进阶绕不开的关卡。1.2 透明、匿名、高匿三种代理的真实区别挑代理前先学会分辨代理类型否则配置半天也不知道自己用的是哪一种。按HTTP协议中的头信息特征代理可以简单分为三类。代理类型是否暴露本机真实IP是否暴露代理服务器标识目标网站识别难度透明代理是是极易识别匿名代理否是会带有代理标识如Via头较易识别高匿代理否否较难识别透明代理基本等同没配目标网站能从请求头里直接拿到你的真实IP这种代理在爬虫场景中没什么用。匿名代理会把你的真实IP隐藏掉但请求头里可能会带上代理相关的标识信息遇到检查请求头的网站一样能被识别出来。高匿代理则是既不暴露真实IP也没有明显的代理痕迹是爬虫任务中优先级最高的选择。这里我补充一个平时容易忽略的细节代理是否匿名并不完全取决于代理服务商自己怎么宣传有时候需要用请求头实测才能确认。正规的代理服务商通常会在文档里标注类型但如果用的是免费代理池建议抽几个用目标网站的实际响应来验证不要只看列表描述。1.3 静态代理和动态代理两种主流形态怎么选“静态代理”和“动态代理”这两个词在爬虫圈里经常出现它俩的区别其实非常好理解。静态代理指的是固定一个或多个IP长期不变地使用。动态代理则是一个代理IP池每隔一段时间或每次请求就换一个IP。静态代理的优势是便宜、稳定配置起来也最简单适合低频抓取、对单IP频率控制得好、目标网站反爬比较宽松的场景。缺点是单个IP一旦被目标网站拉黑整个任务就中断了必换新IP才能继续。动态代理适合高频抓取和分布式爬虫本质上是把“单IP请求频率”分散到多个IP上。它带来一个问题——需要一个代理池管理模块因为IP是动态分配和回收的。我自己的建议是新手第一次验证代理配置老老实实用静态代理跑通就行等确认业务确实需要高频抓取了再切到动态代理方案。一上来就搞代理池只会把报错来源弄得更复杂。2. requests配置代理第一行可运行代码2.1 最常见的方式proxies参数的正确写法requests库配置代理非常简单核心就在一个proxies字典里。import requests proxies { http: http://user:password代理IP:代理端口, https: http://user:password代理IP:代理端口, } url https://example.com/api/data response requests.get(url, proxiesproxies, timeout10) print(response.status_code)这里有几个非常容易踩的点我一个个说。第一参数名是proxies不是proxy。别看只差一个字母requests库只认proxies写错的话代理配置根本不会生效但因为请求本身还能正常发出很多人会花很久才找到原因。第二字典里建议http和https两个key都配上。有人觉得我只请求HTTPS链接配一个https不就行了实际很多网站会发生HTTP到HTTPS的跳转如果http没有配置跳转过程中的请求就会绕开代理导致IP泄露。这是我实测中见过最多的问题。第三代理地址字符串里的协议头不能不写。http://代理IP:端口这是标准格式但如果写成代理IP:端口requests解析时会报错或直接忽略。第四如果代理本身不带认证就是http://代理IP:端口这种格式带认证就加上user:password前缀用户名和密码里有特殊字符的时候要注意URL编码比如密码里的符号必须编码成%40否则会截断认证信息。2.2 怎么确认代理真的生效了配置完了别急着跑正式爬虫先做一个“代理是否生效”的验证。这一步非常重要因为代理配置错误不会导致请求失败更多时候是静默地不生效你看到的数据还是本机IP发的请求。我常用的验证方式是请求httpbin.org的IP查询接口。import requests proxies { http: http://user:password代理IP:代理端口, https: http://user:password代理IP:代理端口, } response requests.get(https://httpbin.org/ip, proxiesproxies, timeout10) print(response.text)正常返回的内容类似这样{ origin: 203.0.113.10 }origin字段的值就是目标网站看到的IP。把这个值和代理服务商提供给你的IP对比一下一致就说明代理生效了如果显示的IP和你本机IP一致那就说明请求根本没走代理需要回头检查上一节里说的那些问题。这里有个细节httpbin.org是访问外部接口如果你的本地网络环境本身对某些域名有特殊限制可能访问不了httpbin。那种情况下可以用一个等价思路随便请求目标网站的一个公开接口在响应头的某些字段里看节点信息但这个相对麻烦不如httpbin直观。2.3 复用Session时代理怎么一起用爬虫往往需要维持会话状态比如登录后的Cookie。这时候要用requests.Session()代理配置也建议挂在Session上避免每次请求重复传。import requests proxy http://user:password代理IP:代理端口 proxies { http: proxy, https: proxy, } session requests.Session() session.proxies.update(proxies) # 后续请求都走这个session代理自动生效 response session.get(https://httpbin.org/ip, timeout10) print(response.text)Session的好处不只是自动带上Cookie还能复用底层的TCP连接配合代理使用时可以减少重复的代理握手开销请求速度会有明显提升。如果你的爬虫是多线程的每个线程尽量使用独立的Session避免多个线程共享同一个Session时出现状态错乱。3. 动态代理池从单次配置到自动轮换3.1 为什么不能老老实实用一个IP有人会问静态代理配置简单那我一直用它不行吗答案是可以但有前提——你的请求频率足够低。一旦请求频率上去了代理IP同样会被目标网站封掉。道理其实不难理解。目标网站的反爬系统做的是“单IP维度频率统计”它不关心这个IP是机房IP还是普通宽带IP只要某个IP在窗口期内的请求数超过阈值就会触发隔离机制。静态代理IP数量少、可替换性差一旦被标记你就要回代理服务商后台重新申请。高频场景下这种操作会变得非常频繁完全不现实。所以动态代理池的本质就是让一批代理IP轮流承担请求量把单IP的请求频率压低到安全阈值以下。目标网站看到的是几十个IP在轮流访问频率很分散就很难针对某一个IP做封锁。3.2 一个轻量级代理池的简单写法动态代理池的实现并不神秘核心就是维护一组可用的代理列表每次请求时按策略选一个请求失败时换一个。我写过一个最简化版本适合中小规模的爬虫任务。import random import requests class ProxyPool: def __init__(self, proxies): self.proxies proxies self.failed_counts {} def get_proxy(self): return random.choice(self.proxies) def mark_failed(self, proxy): self.failed_counts[proxy] self.failed_counts.get(proxy, 0) 1 if self.failed_counts[proxy] 3: self.proxies.remove(proxy) self.failed_counts.pop(proxy) def get_session_with_proxy(self): for _ in range(len(self.proxies) * 2): proxy self.get_proxy() proxies { http: proxy, https: proxy, } try: session requests.Session() session.proxies.update(proxies) response session.get(https://httpbin.org/ip, timeout5) if response.status_code 200: return session, proxy except Exception: self.mark_failed(proxy) raise RuntimeError(代理池中暂时没有可用代理) proxy_list [ http://user:password代理IP1:端口, http://user:password代理IP2:端口, http://user:password代理IP3:端口, ] pool ProxyPool(proxy_list) session, used_proxy pool.get_session_with_proxy()这个实现有几个值得留意的细节。第一随机选代理是最基础的选择策略。如果代理之间有质量差异可以升级成加权随机质量分高的代理被选中的概率更大。加权随机的实现思路是对每个代理维护一个权重然后用random.choices(proxies, weightsweights)选。第二失败计数要设置阈值。某个代理连续失败三次就直接从池子里移除避免一直选到坏代理。但直接移除也有问题——如果某个代理只是暂时性网络波动可能会被误杀。所以更完善的做法是被移除的代理进一个“冷却名单”过一段时间再重新加回来。第三验证代理是否可用最直接的方式是请求httpbin.org或目标站点的一个轻量接口看返回状态和时间。把这一步耗时的成本纳入代理池的“可用性分数”中更利于长期维护。3.3 代理池接入分布式爬虫时的注意事项爬虫规模到了分布式级别后代理池会从一个简单的Python列表变成独立服务。核心问题在于多台爬虫节点不能各自维护一套代理池否则会出现两个节点同时使用同一个代理IP访问同一个目标网站的情况导致IP频率被合并统计封禁概率反而上升。比较稳妥的做法是让代理池中心化。所有爬虫节点从一个中心服务获取可用代理用完归还中心服务负责记录每个代理当前被分配给了哪个节点。节点侧可以配置每次请求前拉取一个新代理或者本地缓存少量代理并设置租约过期时间。这里特别提醒一个点分布式场景下代理分配尽量做到“工作节点维度隔离”。也就是同一个代理IP尽量不要同时分给两个节点如果实在做不到也要保证分给同一个节点的代理列表和下一个节点的不重叠。换句话说IP池越大分布式爬虫的调度系统越要精细否则代理池会被自己的节点刷废。4. selenium场景下的代理配置和requests不是一回事4.1 requests能用的代理参数selenium完全不吃很多初学者在requests里配好了代理切到selenium就发现怎么都不生效原因很简单selenium驱动的是真实浏览器浏览器进程的网络请求不经过requests库所以proxies参数是一点作用都没有的。selenium配置代理要从浏览器启动参数入手。以Chrome为例from selenium import webdriver options webdriver.ChromeOptions() proxy 代理IP:端口 options.add_argument(f--proxy-server{proxy}) driver webdriver.Chrome(optionsoptions) driver.get(https://httpbin.org/ip)注意这里有个区别--proxy-server参数只接受代理IP:端口不需要像requests那样在前面加http://协议头。如果代理需要用户名密码认证直接用--proxy-server是处理不了的因为Chrome的这个启动参数不支持内联认证信息。这种情况一般需要先启动本地认证插件或在浏览器中预先处理好认证再启动selenium会话。4.2 selenium里代理失效的典型现象和解决方法selenium配代理之后最常见的现象是页面加载变慢、加载超时、验证码出现频率变高。这些现象很多时候不是代码写错了而是代理质量不行。高质量的代理IP和低质量的免费代理在页面加载场景下的延迟差异非常大。常见的规格参数可以关注两点一个是代理的“可用率”比如某批IP实测只有50%可用率那selenium每次加载页面可能有一半几率要重新刷新另一个是“并发能力”有些代理IP虽然快但同时承载几十个连接后会拒绝新的连接selenium多窗口场景下就会大量失败。还有一个证书相关的问题值得单独说。如果你在selenium启动浏览器后遇到类似net::ERR_CERT_COMMON_NAME_INVALID的报错通常是代理服务器和目标网站的SSL证书校验出了问题。排查思路是先用固定IP直接访问目标网站确认证书本身没问题再换代理访问看问题是否复现。如果是代理导致的证书异常信息报错可以看代理服务商是否提供证书专用节点或者换一个质量更稳定的代理。4.3 selenium用代理时我踩过的两个坑第一个坑是忘记退出浏览器进程。设置了代理的Chrome实例如果没调用driver.quit()进程会一直残留系统连接数会被慢慢占满导致后续新开的浏览器全连不上网络。这个在本地开发时不太明显放到服务器上跑批处理时很容易突然全部超时。第二个坑是代理设置必须在浏览器启动之前配置好。一个webdriver.Chrome实例一旦启动你再修改options里的--proxy-server参数是无效的。想要切换代理只能先关闭driver重新创建新的ChromeOptions再启动。所以动态换代理在selenium场景下的成本比requests高得多这也意味着低质量的动态代理池在selenium场景下的开销会被放大选择代理时更要谨慎。5. 踩坑实录常见问题、排查流程与自查清单5.1 常用错误信息速查表代理相关的报错信息其实就那几种每一条背后都有对应的排查路线。我整理成一个速查表基本上可以直接照着查。错误信息或现象可能原因排查方向ProxyError: Unable to connect to proxy代理IP或端口不通代理服务商节点故障或IP已失效先用浏览器手动访问代理IP的端口测试连通性ConnectionError代理连接被拒绝或代理已经失效换一个代理试确认代理池列表是否过期403 Forbidden代理IP已被目标网站拉黑或频率过高换一个新代理降低请求频率SSL Error 或证书校验失败代理导致SSL证书验证失败临时用verifyFalse排除证书问题再排查代理自身问题连接超时代理响应慢或代理IP质量差把超时时间缩短快速跳过坏代理请求成功但返回数据不对代理IP被封但还没有完全失效目标网站返回了假数据检查返回内容的长度用目标网站真实数据结构对比这几个场景里ProxyError和ConnectionError是最常见的好在也最容易解决。只要确认代理服务商的节点没宕机换一个新IP基本就能恢复。5.2 免费代理为什么不靠谱一次失败的实测记录免费代理这个话题每个爬虫程序员都绕不开。我早期也用过很多免费代理网站结果发现一个残酷的事实免费代理列表里的IP可用率通常在10%到30%之间而且响应速度普遍很慢。用这类代理去跑正式爬虫任务表现就是大量超时、大量重试、偶尔成功一两次整体效率远低于直接用本机IP。也不是说免费代理完全不能用关键是要有一个“验证机制”。我从那些年踩坑经验里总结出的筛选逻辑是这样的拿到免费代理列表后先做一次可用性检测测试接口不要用httpbin直接请求目标网站的轻量接口记录响应状态码、响应时间和返回内容是否正常。然后你可以考虑对象变量比如把所有耗时超过3秒的代理剔除状态码不是200的剔除连续两次失败率高的剔除。这样筛下来十个免费代理里能留下两三个能用的是正常现象。它们可以用于临时测试但用于正式的生产爬虫任务是绝对不行的。做了批量筛选之后你就会自然地接受“代理该花钱还是要花钱”的结论因为时间成本远超那点代理费用。5.3 一个特别想提醒的事代理和代理键是两个概念写爬虫搜索资料的时候经常有人搜到“代理键”“自然键”这类词汇然后误以为和爬虫代理有关。这两个词其实是数据库设计领域的名词代理键surrogate key是数据库为每条记录分配的一个与业务无关的唯一标识比如自增主键自然键natural key是业务上天然存在的唯一标识比如身份证号。这种概念混淆在初学阶段很容易浪费时间。搜索代理配置时如果你看到的内容在讲数据库主键设计那就说明跑偏了赶紧退出换关键词。正确的中文搜索关键词和英文搜索关键词分别是“Python爬虫代理配置”和“Python requests proxy”英文更准确照着这个找资料效率高很多。写在最后代理配置这关几乎所有做爬虫的人都要过。我自己也是一路被IP封过来的从最开始不屑于用代理到后来老老实实写轮换逻辑整个过程没什么捷径就是多试、多调、多积累经验。如果只让我留三条建议我一定会写第一任何代理配置上线前先验证是否生效不要直接跑正式任务第二动态代理池不是简单的随机换IP要有失败标记和淘汰机制第三高频、大型爬虫任务不要贪便宜用免费代理维护成本会让你得不偿失。代理只是一个起点配置好了之后如何和请求重试、请求频率控制配合又是一套系统工程。先按这篇里的基础方案跑通你就已经比大多数新手领先一步了。
阅读完成 · 觉得有帮助?