Python requests请求极慢

问题发现

调用钉钉机器人API发送消息,速度极慢,经多次测试,平均耗时2分钟。
在这里插入图片描述

​​同样的接口使用postman却只耗时200毫秒~
在这里插入图片描述
后来发现,凡是钉钉的API oapi.dingtalk.com,使用requests就特别慢,而postman就正常。可以排除网络原因,那么是什么因素导致requests这么慢呢?

原因分析

先看下requests源码:

# requests实际调用的urllib3实现,而urllib3最终使用socket实现真正的网络通信
# 以下是urllib3 create_connection()部分源码
def create_connection(
    address,
    timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
    source_address=None,
    socket_options=None,
):
	host, port = address
    if host.startswith("["):
        host = host.strip("[]")
    err = None
	
	# 注意这一行
    family = allowed_gai_family()

    try:
        host.encode("idna")
    except UnicodeError:
        return six.raise_from(
            LocationParseError(u"'%s', label empty or too long" % host), None
        )

    for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
        af, socktype, proto, canonname, sa = res
        sock = None
        try:
        	# 经过跟踪,代码卡在了这里
            sock = socket.socket(af, socktype, proto)
   # ...
            

create_connection()做的事情其实很简单,设置参数,建立socket连接。
往上看,host没什么可说的,那么很明显了,问题出在这一行:

	# Using the value from allowed_gai_family() in the context of getaddrinfo lets
    # us select whether to work with IPv4 DNS records, IPv6 records, or both.
    # The original create_connection function always returns all records.
    family = allowed_gai_family()

通过注释可以知道,family的作用是确定requests使用IPv4还是IPv6:

def allowed_gai_family():
    """This function is designed to work in the context of
    getaddrinfo, where family=socket.AF_UNSPEC is the default and
    will perform a DNS search for both IPv6 and IPv4 records."""

    family = socket.AF_INET
    if HAS_IPV6:
        family = socket.AF_UNSPEC
    return family

注意if语句,若系统支持IPv6,那么HAS_IPV6=True。而现代计算机基本都支持IPv6,那么HAS_IPV6相当于常量,换句话说:requests默认使用IPv6去连接服务器

socket.getaddrinfo()返回的结果也验证了这个猜想:
在这里插入图片描述

2401:b180:2000:60::f是IPv6地址,再加上前面分析的family作用,可以确定问题出现的根本原因了:
如果服务器支持IPv6,那么requests默认会使用IPv6去连接服务器,某些外部因素导致IPv6连接建立很慢(比如IPv6被禁用),socket.socket()又是阻塞的,导致此问题出现。

分别在家和公司(两个不同网络)下测试同一脚本,发现公司网络连接IPv6速度极慢,而家里则正常连接,证实是网络原因导致此问题。

解决办法

知道原因,解决起来就简单了,直接重写allowed_gai_family(),强制使用IPv4:

import socket
import urllib3

def allowed_gai_family():
    return socket.AF_INET
    
urllib3.util.connection.allowed_gai_family = allowed_gai_family

再次运行:
在这里插入图片描述
搞定!

参考文档

网上大部分答案都是用session,根本没用。后来在stackoverflow上找到一点灵感,大家有兴趣可以去看看:

  • https://stackoverflow.com/questions/62599036/python-requests-is-slow-and-takes-very-long-to-complete-http-or-https-request
  • https://stackoverflow.com/questions/33046733/force-requests-to-use-ipv4-ipv6/46972341#46972341
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值