多进程并发爬取遇urllib.error.URLError域名解析失败问题求助
解决urllib.request.urlopen并发请求导致的DNS解析失败问题
我之前也踩过类似的坑——当一次性发起几百个并发请求时,系统的DNS解析能力很容易被耗尽,再加上urllib默认的连接管理在多进程环境下的小缺陷,就会出现你碰到的urllib.error.URLError: urlopen error Temporary failure in name resolution错误。结合你的场景,给你几个实用的解决思路:
1. 显式管理HTTP连接,避免资源泄漏
urllib的urlopen返回的响应对象如果不手动关闭,会让连接长时间处于TIME_WAIT状态,占用系统资源,间接拖慢DNS解析效率。用with语句可以自动帮你关闭连接,这是最基础的优化:
from urllib.request import urlopen def fetch_single_url(url): try: # 用with上下文管理器自动关闭连接 with urlopen(url, timeout=10) as response: return response.read() except Exception as e: print(f"请求 {url} 失败: {str(e)}") return None
2. 降低并发数,不要一次性拉满500进程
系统的文件描述符、DNS查询配额都是有限的,500个并发请求远超大多数系统的默认承载能力。把multiprocessing Pool的进程数调低到合理范围(比如20-50,具体可以根据你的系统配置调整):
from multiprocessing import Pool if __name__ == "__main__": target_urls = [...] # 你的URL列表 # 调整进程池大小,比如30 with Pool(processes=30) as pool: results = pool.map(fetch_single_url, target_urls)
这样可以减少同时发起的DNS查询和连接请求,直接缓解系统压力。
3. 复用DNS解析结果,减少重复查询
多进程环境下,每个进程都会独立做DNS解析,导致大量重复查询浪费资源。你可以自己实现一个DNS缓存,比如用lru_cache在进程内缓存域名解析结果:
from urllib.request import urlopen from urllib.parse import urlparse, urlunparse from functools import lru_cache import socket # 缓存域名解析结果,maxsize可以根据你的域名数量调整 @lru_cache(maxsize=1000) def resolve_domain(domain): return socket.gethostbyname(domain) def fetch_single_url(url): try: parsed = urlparse(url) # 先从缓存获取解析后的IP,没有再去查询 domain_ip = resolve_domain(parsed.netloc) # 把URL替换成IP直接访问,避免重复解析 ip_based_url = urlunparse(parsed._replace(netloc=domain_ip)) with urlopen(ip_based_url, timeout=10) as response: return response.read() except Exception as e: print(f"请求 {url} 失败: {str(e)}") return None
同一个域名只会被解析一次,大大减少DNS查询的次数。
4. 增加重试机制,应对临时解析失败
这个错误本身是“临时”的,所以可以给请求加上重试逻辑,遇到解析失败时等待几秒再重试:
from urllib.request import urlopen import urllib.error import time def fetch_single_url(url, max_retries=3): for attempt in range(max_retries): try: with urlopen(url, timeout=10) as response: return response.read() except urllib.error.URLError as e: if "Temporary failure in name resolution" in str(e) and attempt < max_retries - 1: print(f"请求 {url} 解析失败,第 {attempt+1} 次重试...") time.sleep(1) # 等待1秒后重试 continue print(f"请求 {url} 最终失败: {str(e)}") return None
5. 系统层面优化DNS配置(可选)
如果上面的方法还不够,你可以考虑在系统层面优化:
- Linux系统可以安装
dnsmasq作为本地DNS缓存服务,减少对外部DNS服务器的请求; - 调整
/etc/resolv.conf中的DNS服务器,换成更稳定的公共DNS(比如1.1.1.1或8.8.8.8); - 增大系统的DNS缓存TTL值,延长解析结果的缓存时间。
先从前面几个代码层面的优化开始尝试,尤其是显式关闭连接和降低并发数,这两个改动最小但效果最明显。
内容的提问来源于stack exchange,提问作者softmarshmallow
相关产品推荐
相关产品推荐

