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连接。而建立scoket连接,本身网络又没有问题,不可能那么慢,那么问题出在哪里了呢?
往上看,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’, 443, 0, 0)

在这里插入图片描述
从函数名大概可以猜测socket.getaddrinfo()的功能,用host、port、family等解析出服务器的IP地址,2401:b180:2000:60::f是IPv6地址,再加上前面分析的family作用,大概可以推导出这个问题的原因所在了:

如果服务器支持IPv6,那么requests默认会使用IPv6去连接服务器,而由于网络原因致使IPv6连接建立很慢,socket.socket()又是阻塞的,所以程序就会一直卡在这里,导致问题出现。

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

解决办法

知道原因,解决起来就简单了,既然requests默认使用IPv6,那么强制让丫使用IPv4不就行了吗,直接重写allowed_gai_family()

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

猜你喜欢

转载自blog.csdn.net/as23751782/article/details/127243514