用Python写爬虫,代理IP的设置方式由选用的库决定。requests、urllib、aiohttp、Scrapy写法各不相同,但底层逻辑一致:把网络请求出口从本机IP替换成代理服务器IP。先从requests说起,大部分人入门爬虫都会优先使用这个库。
requests依靠proxies参数配置代理,接收字典类型,键代表协议,值填写代理地址。示例代码:
import requests
proxies = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080",
}
resp = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(resp.json())
代理需要账号密码认证,直接把认证信息写进URL:http://user:pass@127.0.0.1:8080。密码包含特殊字符时,要先做URL编码,否则解析报错。也可以使用requests.auth.HTTPProxyAuth,不过proxies参数不方便挂载auth对象,大部分开发者直接选择URL内嵌认证。
只单独配置http或者https代理,会出现遗漏。比如只填写http代理,访问https站点,requests可能不会走代理。稳妥做法是两个协议都配置,地址保持相同。
代理连接失败或者超时,requests抛出ProxyError或者ConnectTimeout。爬虫代码不能因为单个失效代理直接终止任务,用try捕获异常,请求失败就切换下一个代理。
urllib写起来比requests繁琐,依靠ProxyHandler和build_opener实现代理:
from urllib.request import ProxyHandler, build_opener, Request
proxy = ProxyHandler({
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080",
})
opener = build_opener(proxy)
req = Request("https://httpbin.org/ip")
resp = opener.open(req, timeout=10)
print(resp.read().decode())
urllib的代理认证同样可以在URL写入user:pass@host:port,或者搭配HTTPPasswordMgrWithDefaultRealm、ProxyBasicAuthHandler,但后者代码冗长,日常开发直接用URL内嵌方式足够。
aiohttp多用于异步爬虫,代理设置比较直接:
import aiohttp
import asyncio
async def fetch():
async with aiohttp.ClientSession() as session:
async with session.get(
"https://httpbin.org/ip",
proxy="http://127.0.0.1:8080",
timeout=aiohttp.ClientTimeout(total=10)
) as resp:
print(await resp.json())
asyncio.run(fetch())
aiohttp代理认证写在URL内。如果使用SOCKS5代理,需要额外安装aiohttp-socks,借助ProxyConnector,不能直接传入proxy参数。
Scrapy配置方式略有区别。默认HttpProxyMiddleware读取环境变量http_proxy和https_proxy,更常用的方式是在settings.py启用中间件,通过request.meta['proxy']单独为每一条请求指定代理:
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110,
}
spider内代码:
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(
url,
meta={"proxy": "http://127.0.0.1:8080"},
callback=self.parse
)
代理需要账号密码,Scrapy支持在proxy URL写入user:pass@host:port。部分Scrapy版本对认证代理支持不佳,可以使用第三方中间件scrapy-proxies,自动从代理列表随机选取,支持账号认证,配置简单。
代理IP来源方面,网上免费代理资源很多,但可用率很低。从GitHub拉取代理列表,几百条IP里能正常使用的寥寥无几,存活时间很短。免费代理只适合测试,正式跑爬虫任务,付费代理或者自建代理稳定性更好。付费代理按流量或者IP数量计费,品质参差不齐,购买前先用测试额度测试延迟和成功率。
拿到代理之后第一件事就是验证。最简单的方式,用代理访问httpbin.org/ip,查看返回的origin是否为代理IP。如果返回本机IP,代表代理没有生效,或是透明代理。透明代理会暴露真实本机IP,等同于没有更换IP。匿名代理至少隐藏本机IP,高匿代理不会携带Via请求头。
批量校验代理可以用多线程或者异步,不要逐个串行测试。简易检测脚本:
import requests
from concurrent.futures import ThreadPoolExecutor
def check(proxy):
try:
r = requests.get(
"https://httpbin.org/ip",
proxies={"http": proxy, "https": proxy},
timeout=5
)
if r.status_code == 200:
return proxy, r.json()["origin"]
except Exception:
pass
return proxy, None
proxies = ["http://1.2.3.4:8080", "http://5.6.7.8:8080"]
with ThreadPoolExecutor(max_workers=20) as pool:
for proxy, ip in pool.map(check, proxies):
if ip:
print(f"{proxy} -> {ip}")
爬虫任务规模扩大之后,就需要搭建代理池。最简单方案维护代理列表,每次请求随机挑选,请求失败就更换。进阶方案用Redis存储代理,后台定时任务每隔几分钟检测全部代理有效性,剔除失效IP,保留可用代理。爬虫从Redis随机获取代理,使用完毕放回池内,有效提升代理可用率。
代理池代码逻辑不算复杂,核心两个函数:抓取代理、验证代理。抓取可以从免费代理站点或者付费代理API拉取数据,验证就是上面的check函数。校验通过的代理存入Redis集合,爬虫使用SRANDMEMBER随机取出。取出的代理请求失败,直接从集合删除,重新获取新代理。
SOCKS代理和HTTP代理写法不一样。requests使用SOCKS需要先安装requests[socks],proxies配置如下:
proxies = {
"http": "socks5://127.0.0.1:1080",
"https": "socks5://127.0.0.1:1080",
}
SOCKS5支持UDP、TCP,底层能力比HTTP代理更强,但速度有时不如HTTP代理。aiohttp使用SOCKS依赖aiohttp-socks,Scrapy需要scrapy-socks中间件,都不是原生支持,需要额外安装依赖。
有不少坑需要留意。HTTPS代理采用CONNECT方法,不是普通GET请求。部分代理只支持HTTP,访问HTTPS网站会直接报错。不同代理IP响应速度差距很大,同一个IP不同时段延迟波动明显,代理池要定期刷新。并发过高,代理服务器负载压力大,要控制并发数量,避免打垮代理服务。另外爬虫使用代理,需要遵守目标网站robots.txt以及当地法律法规,不要用来开展违规操作。
代理没有一劳永逸的方案。免费代理需要频繁更换,付费代理要仔细挑选服务商,自建代理成本高但可控。先从requests的proxies参数上手,把代理校验、轮换逻辑调试通,再迁移到Scrapy或者异步爬虫,慢慢积累实操经验。