IterableDataset 的 __iter__ 必须返回真正迭代器,而 requests.get 返回响应对象非迭代器;需在 __iter__ 内用 requests.Session 流式请求、异常容错、逐条 yield 样本,并注意多进程下分页分流或改用 IterDataPipe。

IterableDataset 为什么不能直接接 requests.get
因为 IterableDataset 的 __iter__ 方法必须返回一个**真正的迭代器对象**(支持 next()、可被 for 消费),而 requests.get(url).text 是一次性加载全部响应体的字符串,requests.get(url, stream=True) 返回的 Response 对象本身不是迭代器——它只是支持按块读取,但你需要自己封装成 yield 风格。
常见错误是写成这样:
def __iter__(self):
r = requests.get(self.url, stream=True)
return r.iter_lines() # ❌ 表面像迭代器,但实际可能解码失败、丢首行、乱码、没处理 chunked 编码边界
- HTTP 流式响应可能含 chunked transfer encoding,
iter_lines()在某些编码/换行组合下会截断或合并数据 - 没有重试、超时、连接复用控制,爬虫易被封或卡死
- 无法按需触发新请求(比如翻页、分页 API),
IterableDataset期望的是“无限可拉取”的源头,不是单次 HTTP 响应
怎么构造真正可持续 yield 的数据流
核心思路:把“爬取逻辑”下沉到 __iter__ 内部,每次 yield 一条清洗后的样本(dict 或 tuple),而不是先攒一批再返回 list。
示例场景:抓取某新闻 API 的 JSON 流(每行一个 JSON 对象,类似 JSONL):
class NewsStreamDataset(torch.utils.data.IterableDataset):
def __init__(self, base_url: str, max_pages: int = 100):
self.base_url = base_url
self.max_pages = max_pages
<pre class="brush:php;toolbar:false;">def __iter__(self):
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0"})
for page in range(1, self.max_pages + 1):
try:
r = session.get(f"{self.base_url}?page={page}", timeout=10)
r.raise_for_status()
data = r.json() # 假设返回 {"items": [...]}
for item in data.get("items", []):
# 清洗:只取需要字段,转类型,过滤空值
if not item.get("title") or not item.get("content"):
continue
yield {
"title": item["title"].strip(),
"content": item["content"][:2048], # 截断防 OOM
"label": int(item.get("category_id", 0))
}
except (requests.RequestException, ValueError, KeyError) as e:
# 不 raise!跳过当前页,继续下一页,保证流不断
continue- 用
requests.Session()复用 TCP 连接,显著提升吞吐 - 每个
yield是一个样本,PyTorch DataLoader 会自动批量化,无需你做collate_fn特殊处理(除非字段不齐) - 异常捕获粒度在“页”级别,避免单条失败导致整个 dataloader 崩溃
和 MapDataset 混用时的坑:shuffle 和 worker 问题
IterableDataset 默认不支持 shuffle=True(因为无长度、不可随机索引),但你在多进程 DataLoader 中仍可能遇到诡异重复或漏样本。
- 如果开了
num_workers > 0,每个 worker 会独立调用一次__iter__——意味着每页被请求 4 次(worker=4 时) - 解决方法一:在
__iter__开头加torch.utils.data.get_worker_info()分流,例如按 worker_id 取模分页 - 解决方法二:改用
torchdata.datapipes.iter.IterDataPipe(PyTorch 2.0+),它原生支持 fork、shuffle、sharding,比手写IterableDataset更可靠 - 千万别在
__init__里预抓数据存 list —— 这就退化成 MapDataset,失去流式意义,且内存爆炸
何时该放弃 requests 改用 aiohttp 或 scrapy
当你的爬取目标有以下特征时,同步 requests 会成为瓶颈:
- 单域名并发请求数 > 10,CPU 等在
recv上(I/O wait 高) - 需要登录态维持 + JS 渲染(得上 Playwright / Selenium,但它们和 IterableDataset 天然冲突)
- 反爬强(需轮换代理、headers、延时),手动控制节奏太碎
这时推荐:用 aiohttp 写一个异步生产者队列,再用 threading.Queue 桥接到 __iter__;或者干脆把爬虫和训练拆成两个进程,用 Redis 或文件队列中转 JSONL。硬塞进 IterableDataset 只会让问题更隐蔽。
流式的核心不是“代码里有 yield”,而是“内存驻留样本数可控、失败不影响后续、吞吐能随硬件线性增长”。很多团队卡在这一步,其实是把“爬虫逻辑”和“数据管道逻辑”混在了一起。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











