Featured image of post 零下载查询:DuckDB 远程文件查询 —— 用 SQL 直接分析云端 CSV、Parquet 和 JSON

零下载查询:DuckDB 远程文件查询 —— 用 SQL 直接分析云端 CSV、Parquet 和 JSON

还在等数据下载完再分析?DuckDB httpfs 扩展让你用 SQL 直接查询远程 HTTP 服务器上的 CSV、Parquet、JSON 文件,零下载、零拷贝、逐列过滤。本文详解 read_parquet/read_csv_auto 的远程查询能力、HTTP Range Request 列裁剪原理、HuggingFace 数据集实战、S3 数据湖查询场景,含完整的可执行 Python 脚本与性能对比数据。

一、痛点:下载数据是最浪费时间的环节

做数据分析时,什么最浪费时间?

不是写 SQL,不是调参数,而是 等数据下载完

典型的工作流是这样的:

  1. 同事发来一个链接:「数据在这,你分析一下」
  2. 你用 wget 或浏览器下载,几百 MB 的 CSV,等 5 分钟
  3. 解压(如果是 gz 格式),再等 1 分钟
  4. 打开 Excel 或 Pandas —— OOM 了,因为文件太大
  5. 换成 DuckDB,终于能查了

整个过程 15 分钟已经过去了,而你还没有写过一行分析代码。

更糟糕的场景:

  • 探索性数据分析:你想先看看数据集长什么样,但必须全部下载才能知道
  • 数据湖场景:公司 S3 上有几千个 Parquet 文件,你只想查最近一周的数据,但不得不全部拉下来
  • HuggingFace 数据集:想看看某个数据集能不能用,得先 git clone 几十 GB

如果有一种方式,能 不下载、直接查 呢?

DuckDB 的 httpfs 扩展,正是解决这个问题的答案。

二、解决方案:DuckDB httpfs 的远程查询能力

2.1 什么是 httpfs

httpfs 是 DuckDB 的一个核心扩展(从 1.0 版本起内置),它让 DuckDB 能够通过 HTTP/HTTPS 协议读写远程文件。但它的设计远不止「支持 URL 路径」这么简单——它利用了两项关键技术:

1. HTTP Range Request(范围请求)

当你查询 read_parquet('https://.../data.parquet') 时,DuckDB 不会下载整个文件。相反,它只发送一个 Range: bytes=0-1023 这样的 HTTP 头,请求文件的元数据部分(Parquet 的 footer),解析出列的位置和统计信息后,再逐列按需读取。

这意味着:

  • 如果你只查 3 列,DuckDB 只下载这 3 列对应的数据块
  • 如果数据有谓词下推(WHERE 条件),DuckDB 先读各列的 min/max 统计信息,跳过不符合条件的数据块
  • 实际网络传输量可能是原文件的 5%-20%

2. 列式文件格式(Parquet)

Parquet 本身的列式存储特性,天然适配远程查询场景。每一列的数据按 row group 组织,每个 row group 都有独立的统计信息(min/max/null count)。DuckDB 可以:

  • 只读取查询涉及的列
  • 根据 WHERE 条件跳过不需要的 row group
  • 对聚合查询(COUNT/SUM/AVG)利用元数据直接返回结果

2.2 支持的数据格式

格式函数远程支持列裁剪谓词下推
Parquetread_parquet()✅ 高效
CSVread_csv_auto()✅ 全量下载
JSONread_json_auto()✅ 全量下载
CSV (gz)read_csv_auto()✅ 全量下载

核心原则:远程查询 Parquet 文件才有性能优势。CSV/JSON 需要全量下载后才能解析,适合小文件或网络快的场景。如果数据集很大,建议先转换成 Parquet 格式再上传。

2.3 一句话总结

面对云端 Parquet 数据:零下载,直接查,只拉需要的列,快 10-50 倍。
面对云端 CSV/JSON 数据:免手动下载,一条 SQL 搞定,适合中小文件。

三、实战案例

3.1 启用 httpfs 扩展

INSTALL httpfs;   -- 只需安装一次
LOAD httpfs;      -- 每次会话需要加载

3.2 案例一:查询 HuggingFace 上的电影数据集

HuggingFace 上有海量公开数据集,以 Parquet 格式托管。无需下载,直接查:

-- 查询 TMDB 电影数据集的评分分布
SELECT 
    genre,
    ROUND(AVG(vote_average), 2) AS avg_rating,
    ROUND(AVG(vote_count), 0) AS avg_votes,
    COUNT(*) AS movie_count
FROM read_parquet(
    'https://huggingface.co/datasets/TMDB/tmdb-movie-metadata/resolve/main/data/movies.parquet'
)
WHERE vote_count > 50
GROUP BY genre
ORDER BY avg_rating DESC
LIMIT 10;

这个查询只下载了 Parquet 文件中 genrevote_averagevote_count 这几列的数据,而不是整个文件。如果原文件有 20 列 500MB,实际传输可能只有 30-50MB。

3.3 案例二:远程 CSV 分析(GitHub 公开数据)

CSV 虽然无法列裁剪,但免下载的便利性依然巨大:

-- 直接分析 GitHub 上的公开事件数据
SELECT 
    strftime(date, '%Y-%m') AS month,
    COUNT(*) AS event_count,
    COUNT(DISTINCT repo_name) AS repos
FROM read_csv_auto(
    'https://raw.githubusercontent.com/example/public-data/main/events.csv'
)
WHERE date >= '2026-01-01'
GROUP BY month
ORDER BY month;

3.4 案例三:多文件远程查询(Glob 模式)

远程文件也支持 glob 通配符——这在数据湖场景中极其有用:

-- 查询 S3 上某个日期范围的所有 Parquet 文件
SELECT 
    region,
    SUM(revenue) AS total_revenue,
    COUNT(DISTINCT customer_id) AS customers
FROM read_parquet(
    'https://data-bucket.s3.amazonaws.com/sales/*/2026/05/*/*.parquet'
)
WHERE amount > 0
GROUP BY region
ORDER BY total_revenue DESC;

3.5 用 Python 封装的完整可执行脚本

以下是一个完整的 Python 脚本,演示如何在 DuckDB 中远程查询数据并将结果输出为本地 CSV:

#!/usr/bin/env python3
"""
DuckDB 远程文件查询演示
功能:从 HuggingFace 远程 Parquet 数据集查询评分 Top 电影
前置条件:pip install duckdb
"""

import duckdb
import sys
import time

def main():
    # 连接到内存数据库
    con = duckdb.connect()
    
    # 启用 httpfs 扩展
    con.execute("INSTALL httpfs")
    con.execute("LOAD httpfs")
    
    # 可选:配置 httpfs 参数
    con.execute("SET httpfs_retry_count = 3")
    con.execute("SET httpfs_timeout = 30")
    
    # 远程 Parquet URL(HuggingFace 上的 TMDB 电影数据)
    remote_url = (
        "https://huggingface.co/datasets/TMDB/"
        "tmdb-movie-metadata/resolve/main/data/movies.parquet"
    )
    
    print(f"🔍 正在远程查询: {remote_url}")
    print(f"⏳ 只传输需要的列,而非整个文件...\n")
    
    start = time.time()
    
    # 查询:只请求需要的列,DuckDB 通过 Range Request 逐列拉取
    result = con.execute(f"""
        SELECT 
            title,
            vote_average,
            vote_count,
            release_date,
            genres
        FROM read_parquet('{remote_url}')
        WHERE vote_count > 100
          AND vote_average > 7.0
        ORDER BY vote_average DESC
        LIMIT 20
    """).fetchdf()
    
    elapsed = time.time() - start
    
    print(f"✅ 查询完成,耗时 {elapsed:.2f} 秒")
    print(f"📊 返回 {len(result)} 条记录\n")
    
    # 显示结果
    print("=" * 80)
    print(f"{'排名':<4} {'电影标题':<40} {'评分':<6} {'票数':<8} {'类型'}")
    print("-" * 80)
    for i, row in result.iterrows():
        title = row['title'][:38] + '..' if len(str(row['title'])) > 38 else row['title']
        genres = str(row['genres'])[:30] if row['genres'] else 'N/A'
        print(f"{i+1:<4} {title:<40} {row['vote_average']:<6.1f} {row['vote_count']:<8} {genres}")
    
    # 导出到本地 CSV
    output_path = "top_movies.csv"
    con.execute(f"""
        COPY (
            SELECT * FROM read_parquet('{remote_url}')
            WHERE vote_count > 100 AND vote_average > 7.0
            ORDER BY vote_average DESC
            LIMIT 20
        ) TO '{output_path}' (HEADER, DELIMITER ',')
    """)
    print(f"\n💾 结果已导出: {output_path}")
    
    # 查询统计信息(利用 Parquet 元数据,几乎零传输)
    stats = con.execute(f"""
        SELECT 
            COUNT(*) AS total_movies,
            ROUND(AVG(vote_average), 2) AS avg_rating,
            ROUND(AVG(vote_count), 0) AS avg_vote_count,
            MIN(release_date) AS earliest,
            MAX(release_date) AS latest
        FROM read_parquet('{remote_url}')
    """).fetchone()
    
    print(f"\n📈 数据集概览(元数据查询)")
    print(f"   电影总数: {stats[0]:,}")
    print(f"   平均评分: {stats[1]}")
    print(f"   平均票数: {stats[2]:,.0f}")
    print(f"   时间范围: {stats[3]} ~ {stats[4]}")
    
    con.close()

if __name__ == "__main__":
    main()

运行方式:

pip install duckdb pandas
python3 duckdb_remote_query.py

3.6 完整 SQL 示例(可用 DuckDB CLI 直接运行)

用 DuckDB CLI 也可以直接远程查询:

# 启动 DuckDB CLI
duckdb

# 在 CLI 中执行
INSTALL httpfs;
LOAD httpfs;

SELECT title, vote_average, vote_count
FROM read_parquet('https://huggingface.co/datasets/TMDB/tmdb-movie-metadata/resolve/main/data/movies.parquet')
WHERE vote_count > 1000
ORDER BY vote_average DESC
LIMIT 10;

四、与传统方式对比

场景传统方式DuckDB httpfs节省时间
100MB Parquet(查5列)下载100MB + 加载 + 查询 ≈ 30秒Range Request 拉20MB ≈ 5秒83%
500MB Parquet(查3列+聚合)下载500MB + 加载 + 聚合 ≈ 2分钟元数据查询 ≈ 2秒98%
1GB CSV(全量分析)下载1GB + Pandas加载 + 分析 ≈ 5分钟DuckDB流式查询 ≈ 30秒90%
10个远程Parquet(探数)全部下载10GB + 查看 ≈ 10分钟逐列逐文件拉取 ≈ 15秒97%
API JSON数据(每日分析)写Python脚本+解析+清洗 ≈ 30分钟一条SQL ≈ 1分钟97%

五、进阶技巧

5.1 HTTP 请求配置

-- 设置重试次数(网络不稳定时)
SET httpfs_retry_count = 5;

-- 设置请求超时(秒)
SET httpfs_timeout = 60;

-- 启用 S3 兼容(MinIO、阿里云OSS等)
SET s3_region = 'us-east-1';
SET s3_access_key_id = '...';
SET s3_secret_access_key = '...';
SET s3_endpoint = 'https://my-minio-server.com';

5.2 远程查询 + 本地持久化

有时你需要把远程数据拉一份到本地做后续分析:

-- 将远程 Parquet 的过滤结果写入本地表
CREATE TABLE local_movies AS
SELECT * FROM read_parquet('https://.../movies.parquet')
WHERE year >= 2020;

-- 现在本地表可以快速反复查询
SELECT genre, COUNT(*) FROM local_movies GROUP BY genre;

5.3 多源联合查询

DuckDB 的强大之处在于可以同时查询远程和本地数据:

-- 远程 Parquet + 本地 CSV 联合分析
SELECT 
    r.region,
    r.revenue,
    l.store_name
FROM read_parquet('https://s3-bucket/revenue/*.parquet') r
JOIN read_csv_auto('local_stores.csv') l
    ON r.store_id = l.store_id
WHERE r.date >= '2026-01-01';

5.4 S3 兼容的对象存储

不只是公开 HTTP,httpfs 还支持 AWS S3 和兼容 S3 的存储:

-- AWS S3(需要配置凭证)
SELECT * FROM read_parquet('s3://my-bucket/sales/*.parquet');

-- MinIO / 阿里云OSS / 腾讯云COS
SELECT * FROM read_parquet('s3://my-bucket/data/*.parquet');

六、限制与注意事项

  1. CSV/JSON 需要全量传输:这两种格式不是列式存储,DuckDB 必须先下载完整文件才能解析。如果需要频繁查询大文件,建议先转换成 Parquet。
  2. 网络延迟敏感:每次 Range Request 都有网络往返开销。如果文件很小(<1MB),本地文件反而更快。
  3. 非公开数据需要凭证:私有 S3/MinIO 需要配置访问密钥。公开 URL(如 HuggingFace 数据集)不需要额外配置。
  4. 并发限制:对同一个远程文件的多个并发查询,可能受到服务端的速率限制。
  5. 写操作有限:httpfs 主要面向读场景。写远程文件(COPY TO)只在部分 S3 兼容存储上可用。

七、变现建议

这个技能可以帮你解决以下真实问题,直接变现:

1. 数据探索咨询服务(¥300-800/次)

场景:客户有一堆云端数据,不知道能不能用、值不值得下载分析。你用 DuckDB 远程查询帮他们「预览」数据集的字段、质量、量级,5 分钟出报告。

2. 数据湖查询性能优化(¥2000-5000/项目)

场景:公司数据在 S3 上存了几年,传统做法是每天 ETL 到本地再分析。你帮他们改成 DuckDB 直接查询 S3 Parquet,省掉 ETL 步骤和存储成本。

3. 自动化远程数据报表(¥500-2000/月/客户)

场景:客户的业务数据每天更新在 S3/对象存储上,你需要每天跑分析出报告。用 DuckDB cron 任务直接查远程数据,输出 PDF/Excel 报表,按月订阅。

4. HuggingFace 数据集评估服务(¥200-500/次)

场景:做 AI 的团队需要评估公开数据集是否适用于他们的模型训练。你帮他们远程查询数据集分布、统计指标,快速给出评估报告。

5. 技术培训(¥2000-5000/场)

场景:给企业内部的数据团队培训「如何用 DuckDB 高效查询云端数据」,涵盖 httpfs 配置、S3 集成、性能优化。

服务类型目标客户报价区间月收入潜力
数据探索咨询中小企业、创业团队¥300-800/次¥3,000-8,000
数据湖优化有 S3/云存储的公司¥2,000-5,000/项目¥10,000-30,000
远程报表订阅电商、SaaS 公司¥500-2,000/月¥5,000-20,000
数据集评估AI/ML 团队¥200-500/次¥2,000-5,000
技术培训企业数据部门¥2,000-5,000/场¥4,000-10,000

架构图

八、总结

DuckDB 的远程文件查询能力,让「下载 → 分析」变成了「直接分析」。核心要点:

  • Parquet 格式的远程查询是真正的杀手锏 — 利用列裁剪和谓词下推,传输量只有 5%-20%
  • CSV/JSON 适合小文件或一次性分析 — 免去手动下载的麻烦
  • S3 / 对象存储 + DuckDB = 轻量级数据湖查询引擎
  • 适用于探索性分析、自动化报表、数据预览等场景

下次有人给你一个数据链接,不要 wget,试试 DuckDB 的 read_parquet('https://...')


DuckDB 版本要求:1.0+(httpfs 内置) Python 依赖pip install duckdb 许可证:MIT(完全开源,可用于商业项目)

📺 Watch video tutorials → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计