Python中requests多线程处理时遇到无法访问的URL问题如何解决?

使用 requests 时,发现一个问题。

一个基本的请求。

requests.get('http://www.xxx.com', headers=headers)

当用多线程时: 效率会明显提高,但是如果这个 url 是打不开的,那多线程会变得与单线程一样,会卡在那个不能打开的 url 上一直等待到报错。 timeout 参数只对可以打开的 url 有效果。

多线程:

def getHtml(url):
    try:
        requests.get(url)
        print(url)
    except:
        print("wrong: {0}".format(url))

0 (仅测试。)

import threading
for url in urls:
    worker = threading.Thread(target=getHtml, args=(url,))
    worker.start()

问题依旧。

1 使用封装的线程池。

from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(max_workers=10) as t: for url in urls: t.submit(getHtml, url)

查到用 map 方法可以设置 timeout ,不过设置后发现没用。。

with ThreadPoolExecutor(max_workers=10) as t:
    t.map(getHtml, urls, timeout=1)

2 使用 multiprocessing.dummy 的线程池。

发现一篇用这个库的文章。 https://segmentfault.com/a/1190000000382873

from multiprocessing.dummy import Pool as ThraedPool
pool = ThreadPool(10)
pool.map(getHtml, urls)
pool.close()
pool.join()

还是一样。遇到打不开的网址都会等待。

测试数据:

urls = [
'http://huahao917.com',
'http://huanreshebei.net',
'http://hyjsbj.com',
'http://hzjfwj.com',
'http://kitairu.net',
'http://jy-qj.com.cn',
'http://luosi580.com',
'http://lyljbj.com',
'http://psxti.com',
'http://pt-ti.cn']

其中 http://jy-qj.com.cnhttp://hzjfwj.com 是无法访问的。

urllib.urlrequest.urlopen 与 requests 一样会等待,有什么办法可以不在那个无法访问的网址上等待?

还是我的多线程姿势用错了?望指教。


Python中requests多线程处理时遇到无法访问的URL问题如何解决?

21 回复

pool.join() 的意思不就是等待所有线程结束吗?


问题核心: requests库本身不是线程安全的,直接在多线程中使用可能导致连接池冲突或异常。你需要为每个线程创建独立的Session对象,或者使用线程安全的连接适配器。

解决方案:

import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock
import logging

# 设置日志,方便查看错误
logging.basicConfig(level=logging.INFO)

def fetch_url(url, timeout=5):
    """
    每个线程使用独立的Session对象访问URL
    """
    session = requests.Session()  # 关键:每个线程有自己的Session
    try:
        response = session.get(url, timeout=timeout)
        response.raise_for_status()  # 检查HTTP错误
        return url, response.status_code, len(response.content)
    except requests.exceptions.RequestException as e:
        return url, f"ERROR: {type(e).__name__}", str(e)
    finally:
        session.close()  # 关闭会话释放资源

def main():
    urls = [
        "https://httpbin.org/get",
        "https://httpbin.org/status/404",
        "https://invalid.url.that.does.not.exist",
        "https://httpbin.org/delay/2"
    ]
    
    results = []
    # 使用线程池控制并发数
    with ThreadPoolExecutor(max_workers=4) as executor:
        # 提交所有任务
        future_to_url = {executor.submit(fetch_url, url): url for url in urls}
        
        # 收集结果
        for future in as_completed(future_to_url):
            url = future_to_url[future]
            try:
                result = future.result()
                results.append(result)
                logging.info(f"完成: {result}")
            except Exception as e:
                logging.error(f"任务异常 {url}: {e}")
    
    # 输出汇总结果
    print("\n=== 结果汇总 ===")
    for url, status, info in results:
        print(f"{url[:50]:<50} | {str(status):<15} | {info}")

if __name__ == "__main__":
    main()

关键点说明:

  1. 独立Sessionrequests.Session() 不是线程安全的。上面的代码在每个线程内部创建独立的Session实例,避免共享导致的竞争条件。

  2. 异常处理:网络请求可能因各种原因失败(超时、DNS解析失败、连接拒绝等)。代码通过捕获requests.exceptions.RequestException来妥善处理这些情况,避免整个程序崩溃。

  3. 资源清理:使用finally块确保Session被关闭,防止连接泄漏。

  4. 线程池控制ThreadPoolExecutor可以方便地管理线程数量,避免创建过多线程。

如果你的程序需要更高的并发性能,可以考虑:

  • 使用aiohttp进行异步IO(更适合高并发场景)
  • 使用requests配合urllib3的连接池配置

简单建议: 为每个线程创建独立的Session对象是解决此问题最直接有效的方法。

[xiaoyu@MacBook-Pro:~]$ ping hzjfwj.com
PING hzjfwj.com (210.209.82.181): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
^C
hzjfwj.com ping statistics —
4 packets transmitted, 0 packets received, 100.0% packet loss
[xiaoyu@MacBook-Pro:~]$ ping jy-qj.com.cn
ping: cannot resolve jy-qj.com.cn: Unknown host

你是 requests hzjfwj.com 的时候卡住了吧,设置 response = requests.get(‘http://hzjfwj.com’, timeout=5)超时就好了


pool.join()是等待所有线程结束不过运行到不能打开的网址时会一直等待那一个线程。


http://jy-qj.com.cn 这个网址设置 timeout 无效。。

无效是什么意思,我运行了没发现问题啊


requests.get(‘http://jy-qj.com.cn’, timeout=1.5)
还是会等待老长时间然后报这个错
requests.exceptions.ConnectionError: HTTPConnectionPool(host=‘jy-qj.com.cn’, port=80): Max retries exceeded with url: / (Caused by NewConnectionError(’<requests.packages.urllib3.connection.HTTPConnection object at 0x00000000032ED358>: Failed to establish a new connection: [Errno 11004] getaddrinfo failed’,))

python3.4.1 requests2.9.1

http://jy-qj.com.cn 这个不管你设置多少都是秒报错啊,他域名都没解析,更新下 requests 版本吧,我测试没出现你这情况

这个是正解,在 requests 设置 timeout

或者 requests 前先 ping 如果 Unknown host 直接 break =,=||

更新成 2.13.0 ,还是要等老长时间。在虚拟机 32 位 win7 32 位 python 测试等待的时间少些,没达到秒报错。
先用 ping 检测下了。

我不是 win 系统😊

不过还有个问题,既然是多线程,那就让哪一个线程等待就是了,为什么会卡在一个线程上呢。。

不知道,好像没卡在一个线程上啊,只是程序好像要等待所有线程结束,你可以用 BoundedSemaphore 来控制阻塞

我试怎么没问题?



你们的 python 都是什么版本。我更新下 python 看看是不是 python 的问题。
我这边只要不是无法解析的网址都是正常运行,一有无法解析的就卡主一会。。

Python 2.7.11 |Anaconda 2.5.0 (x86_64)| (default, Dec 6 2015, 18:57:58)
[GCC 4.2.1 (Apple Inc. build 5577)] on darwin

requests timeout + 线程 timeout, 用 requests 有时确实会发生永久阻塞不解析的问题 原因比较难找 多半可能和系统环境有关系 保险的方式还是给每个线程都加 timeout

抛开自己写,有没有可以自带线程超时的包,
from concurrent.futures import ThreadPoolExecutor
一般用这个线程池,他的 map 方法有一个 timeout ,不过尝试后发现没效果。

python 标准库 threading 的 join 自带 timeout 用起来很简单的,还有就是看看 gevent 这个是用协程实现的并发框架非常的好用, timeout 也有好几种实现,具体细节请自行搜索相关 sample 和文档。

谢谢。

回到顶部