Featured image of post 不写爬虫,不写脚本:用 DuckDB 直接 SQL 获取全网公开数据

不写爬虫,不写脚本:用 DuckDB 直接 SQL 获取全网公开数据

还在写 Python 爬虫下载 CSV?DuckDB 的 httpfs 扩展让你直接用 SQL 从 HTTP 网址拉取数据,无需 requests、无需 BeautifulSoup、无需下载到本地。本文对比传统爬虫与 DuckDB 直连方案,演示 URL 通配符批量抓取、S3 对象存储查询、GitHub 公开数据集分析,附完整可执行 Python 脚本与变现方案。

一、痛点:获取公开数据,你还在写爬虫?

做一个数据分析项目,第一步是什么?

不是写 SQL,不是调模型——是 把数据搞到手

传统的获取公开数据的工作流:

  1. 找到数据 URL(GitHub 上的 CSV、政府开放数据集、S3 上的 Parquet)
  2. 打开浏览器下载,或者写 Python 脚本:
    import requests
    import csv
    from io import StringIO
    
    resp = requests.get('https://example.com/data.csv')
    reader = csv.DictReader(StringIO(resp.text))
    data = [row for row in reader]
    
  3. 用 Pandas/Excel 打开 → OOM 崩溃,文件太大
  4. 切到 DuckDB 终于能跑了

整个过程 20 分钟过去了,你还没写一行分析代码

更糟糕的是:

  • 网页上有 100 个 CSV 文件,你要写循环一个个下载再合并
  • 数据每天更新,你得写 cron 脚本定时抓取
  • 文件 2GB+,Pandas read_csv 直接内存溢出

如果有一种方式,不下载、不写爬虫脚本、直接查 URL 里的数据呢?

这就是 DuckDB 的「零 ETL 数据获取」能力。


二、解决方案:DuckDB 代替你的爬虫脚本

DuckDB 内置的 httpfs 扩展,允许你直接在 SQL 中读取 HTTP/HTTPS 上的 CSV、Parquet、JSON 文件。

核心思想:数据在哪儿,DuckDB 就查到哪儿,不需要先搬到本地。

2.1 三步开启远程查询

INSTALL httpfs;   -- 只需安装一次(DuckDB 1.0+ 内置)
LOAD httpfs;      -- 每次会话加载

-- 然后就可以直接查 URL 了
SELECT * FROM read_csv_auto('https://example.com/data.csv');

就这么简单。不需要 requests.get(),不需要 wget,不需要下载到临时目录。

2.2 一句话总结

传统方式DuckDB 方式
requests.get(url) 下载 → pandas.read_csv()read_csv_auto('url') 直接查
写循环合并 100 个 CSVread_csv_auto('https://.../*.csv') 通配符搞定
每天跑 cron 脚本 wget → 解压 → 分析cron → duckdb -c "SELECT ..." 一行命令
下载完整文件才知道能不能用Parquet 远程查询只需拉元数据(5-50KB)

三、实战案例

3.1 案例一:直接查询 GitHub 上的公开 CSV

GitHub 上有海量公开数据集。传统做法:git clone 整个仓库。DuckDB 做法:一条 SQL。

-- GitHub 上的 NYC 出租车样本数据
INSTALL httpfs;
LOAD httpfs;

SELECT 
    VendorID,
    COUNT(*) AS 订单数,
    ROUND(AVG(total_amount), 2) AS 平均金额
FROM 'https://github.com/duckdb/duckdb-data/raw/main/nyc-taxi-data.parquet'
WHERE total_amount > 0
GROUP BY VendorID
ORDER BY 订单数 DESC;

执行时间:3-5 秒(只传输了 Parquet 的元数据和需要的列)。

如果用传统方式:

  1. 下载 42MB Parquet → 10 秒
  2. 加载到内存 → 5 秒
  3. 执行查询 → 2 秒 总计:17 秒

DuckDB 远程查询:5 秒,零临时文件。

3.2 案例二:URL 通配符批量抓取

这是 DuckDB 最被低估的能力——对远程 URL 也支持 glob 通配符。

假设一个政府开放数据网站按日期组织文件:

https://data.gov.example/traffic/2026/01/traffic_20260101.csv
https://data.gov.example/traffic/2026/01/traffic_20260102.csv
...
https://data.gov.example/traffic/2026/05/traffic_20260524.csv

传统做法:写 Python 循环 + requests.get() + 拼接 DataFrame。

DuckDB 做法:

-- 读取某个月份的所有 CSV
SELECT 
    strftime(date, '%Y-%m-%d') AS day,
    COUNT(*) AS records,
    AVG(speed) AS avg_speed
FROM read_csv_auto(
    'https://data.gov.example/traffic/2026/05/*.csv'
)
GROUP BY day
ORDER BY day;

* 通配符:匹配该目录下所有 CSV 文件。

-- 递归读取所有目录下的 CSV
SELECT *
FROM read_csv_auto(
    'https://data.gov.example/traffic/**/*.csv'
);

** 递归通配符:匹配所有子目录,适合多层目录结构的数据仓库。

-- 更精确的模式:只取 2026 年 5 月的数据
SELECT *
FROM read_csv_auto(
    'https://data.gov.example/traffic/2026/05/traffic_*.csv'
);

3.3 案例三:S3 / 对象存储 + Parquet 列裁剪

当数据存储在 AWS S3 或兼容 S3 的对象存储上时,Parquet 格式的远程查询才是真正的杀手锏。

-- 查询 S3 上销售数据的汇总
SELECT 
    region,
    SUM(revenue) AS total_revenue,
    COUNT(DISTINCT customer_id) AS customers
FROM read_parquet(
    's3://my-bucket/sales/2026/*/*.parquet'
)
WHERE revenue > 0
GROUP BY region
ORDER BY total_revenue DESC;

Parquet 列裁剪原理:DuckDB 通过 HTTP Range Request 只拉取 regionrevenuecustomer_id 这 3 列的数据块。如果原文件有 30 列 1GB,实际传输可能只有 30-80MB——传输量减少 90%+

而且 DuckDB 还会利用 Parquet 的 row group 统计信息进行 谓词下推WHERE revenue > 0),跳过不满足条件的 row group,进一步减少传输量。

3.4 案例四:完整的 Python 脚本(复制即用)

以下是一个完整的 Python 脚本,演示从远程 GitHub URL 拉取数据、分析、输出报表的全流程:

#!/usr/bin/env python3
"""
DuckDB 数据获取 + 分析演示
功能:从远程 URL 拉取 Parquet/CSV 数据,执行 SQL 分析,输出 HTML 报表
前置条件:pip install duckdb pandas
"""

import duckdb
import time
import os

def main():
    # 连接内存数据库
    con = duckdb.connect()
    
    # 启用 httpfs 扩展
    con.execute("INSTALL httpfs")
    con.execute("LOAD httpfs")
    
    # 配置(可选):网络超时和重试
    con.execute("SET httpfs_timeout = 30")
    con.execute("SET httpfs_retry_count = 3")
    
    # ========== 案例 1:GitHub 公开数据 ==========
    print("=" * 60)
    print("📦 案例 1:GitHub 公开数据集查询")
    print("=" * 60)
    
    start = time.time()
    
    # NYC 出租车数据(Parquet 格式,支持列裁剪)
    result = con.execute("""
        SELECT 
            VendorID,
            payment_type,
            COUNT(*) AS 订单数,
            ROUND(AVG(total_amount), 2) AS 平均金额,
            ROUND(SUM(total_amount), 2) AS 总金额
        FROM 'https://github.com/duckdb/duckdb-data/raw/main/nyc-taxi-data.parquet'
        WHERE total_amount > 0
          AND total_amount < 500
        GROUP BY VendorID, payment_type
        ORDER BY 总金额 DESC
        LIMIT 15
    """).fetchdf()
    
    elapsed = time.time() - start
    print(f"✅ 查询完成,耗时 {elapsed:.2f} 秒")
    print(f"📊 返回 {len(result)} 行数据\n")
    print(result.to_string(index=False))
    print()
    
    # ========== 案例 2:URL 通配符模拟 ==========
    # 注意:以下 URL 是演示结构,实际使用时替换为你自己的数据源
    print("=" * 60)
    print("🌐 案例 2:多文件远程通配符查询")
    print("=" * 60)
    print("""
    -- 实际用法(替换为真实 URL):
    SELECT region, COUNT(*) AS orders
    FROM read_parquet('https://your-bucket.s3.amazonaws.com/sales/2026/05/*.parquet')
    WHERE amount > 0
    GROUP BY region;
    """)
    
    # ========== 案例 3:远程 CSV 直接分析 ==========
    print("=" * 60)
    print("📄 案例 3:远程 CSV 流式分析")
    print("=" * 60)
    
    start = time.time()
    
    # 使用一个真实的公开 CSV(世界银行数据示例)
    # 这里使用 DuckDB 官方示例数据
    result2 = con.execute("""
        SELECT 
            column0 AS year,
            COUNT(*) AS records
        FROM read_csv_auto(
            'https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv'
        )
        WHERE column0 > 2000
        GROUP BY year
        ORDER BY year
    """).fetchdf()
    
    elapsed2 = time.time() - start
    print(f"✅ CSV 远程查询完成,耗时 {elapsed2:.2f} 秒")
    print(f"📊 返回 {len(result2)} 行数据\n")
    print(result2.to_string(index=False))
    print()
    
    # ========== 输出汇总报表 ==========
    print("=" * 60)
    print("📝 生成 HTML 报表")
    print("=" * 60)
    
    # 将结果导出为 HTML
    html_report = f"""
    <!DOCTYPE html>
    <html>
    <head><meta charset="utf-8">
    <title>DuckDB 数据获取报表</title>
    <style>
        body {{ font-family: -apple-system, sans-serif; max-width: 900px; margin: 40px auto; padding: 0 20px; }}
        h1 {{ color: #0d9488; }}
        table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }}
        th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
        th {{ background: #0d9488; color: white; }}
        tr:nth-child(even) {{ background: #f5f5f5; }}
        .summary {{ background: #f0fdf4; padding: 15px; border-radius: 8px; margin: 20px 0; }}
    </style>
    </head>
    <body>
    <h1>🦆 DuckDB 数据获取 & 分析报表</h1>
    <p>生成时间:{time.strftime('%Y-%m-%d %H:%M:%S')}</p>
    
    <div class="summary">
        <h2>案例 1:NYC 出租车数据分析</h2>
        <p>查询耗时:{elapsed:.2f} 秒 | 结果行数:{len(result)}</p>
    </div>
    
    {result.to_html(index=False)}
    
    <div class="summary">
        <h2>案例 2:远程 CSV 分析</h2>
        <p>查询耗时:{elapsed2:.2f} 秒 | 结果行数:{len(result2)}</p>
    </div>
    
    {result2.to_html(index=False)}
    
    <hr>
    <p><em>使用 DuckDB httpfs 扩展,零下载直接查询远程数据</em></p>
    </body>
    </html>
    """
    
    output_path = "duckdb_remote_report.html"
    with open(output_path, "w", encoding="utf-8") as f:
        f.write(html_report)
    
    print(f"✅ 报表已生成: {os.path.abspath(output_path)}")
    
    con.close()
    print("\n🎉 全部完成!")

if __name__ == "__main__":
    main()

运行方式:

pip install duckdb pandas
python3 duckdb_remote_data.py

3.5 DuckDB CLI 一行命令执行

如果你不想写 Python 脚本,DuckDB CLI 也能直接跑 SQL 并输出结果:

# 查询远程 Parquet,输出为 CSV
duckdb -c "INSTALL httpfs; LOAD httpfs; COPY (
    SELECT VendorID, COUNT(*) AS cnt
    FROM 'https://github.com/duckdb/duckdb-data/raw/main/nyc-taxi-data.parquet'
    GROUP BY VendorID
) TO '/tmp/results.csv' (HEADER, DELIMITER ',');"

# 或者直接输出到终端
duckdb -c "
INSTALL httpfs; LOAD httpfs;
SELECT VendorID, COUNT(*) AS cnt
FROM 'https://github.com/duckdb/duckdb-data/raw/main/nyc-taxi-data.parquet'
GROUP BY VendorID;
"

这完全可以替代每天跑的 cron 爬虫脚本

# /etc/crontab 中的一行:每天早上 8 点拉取最新数据并生成报表
0 8 * * * duckdb -c "INSTALL httpfs; LOAD httpfs; COPY (SELECT * FROM read_parquet('https://data-bucket.s3.amazonaws.com/daily/*.parquet') WHERE date = current_date) TO '/tmp/daily_report.csv' (HEADER);"

四、与传统爬虫方案对比

对比维度传统 Python 爬虫DuckDB 直接查询
代码量30-100 行(requests + pandas + 错误处理)1 行 SQL
学习成本需要学 requests、BeautifulSoup、反爬会 SQL 就行
磁盘占用下载文件占用磁盘,不及时清理会爆零临时文件
内存占用大文件 Pandas OOM流式处理,内存友好
传输效率全量下载Parquet 列裁剪,只拉需要的
批量处理写循环 + 合并逻辑URL 通配符一站式搞定
定时执行cron + Python 脚本(依赖 Python 环境)cron + duckdb CLI,零依赖
数据格式支持需要手动处理 CSV/JSON/Parquet 解析自动推断格式+类型

结论:对于公开数据获取 + 分析的场景,DuckDB 是一条龙方案。不需要 Python 环境、不需要第三方库、不需要中间文件。


五、限制与注意事项

不是所有场景都适合 DuckDB 直接查询远程数据。以下是需要注意的限制:

5.1 CSV/JSON 需要全量传输

CSV 和 JSON 不是列式存储格式,DuckDB 必须先下载完整文件才能解析。对于大文件(500MB+ 的 CSV),传输时间会比本地方案慢。

对策:如果是经常查询的大文件,建议先转成 Parquet 再上传到服务器/S3。

5.2 需要服务器支持 HTTP Range Request

Parquet 的列裁剪依赖 HTTP Range Request。大部分 CDN 和对象存储(AWS S3、Cloudflare R2、MinIO)都支持。但某些简单的 HTTP 服务器可能不支持。

验证方法:

curl -I -H "Range: bytes=0-100" https://your-data-url

如果返回 206 Partial Content,说明支持。

5.3 网络延迟

每次 HTTP Range Request 都有网络往返开销。对于小文件(<1MB),本地文件反而更快。

建议:1MB 以下的文件直接下载到本地,100MB+ 的 Parquet 用远程查询。

5.4 认证和凭证

私有数据源需要配置访问凭证:

-- S3 认证
SET s3_region = 'us-east-1';
SET s3_access_key_id = 'your_key';
SET s3_secret_access_key = 'your_secret';

-- 或者使用 Bearer Token(某些 API)
SET httpfs_bearer_token = 'your_token';

六、变现建议

1. 公开数据采集服务(¥300-800/次)

目标客户:需要特定行业公开数据但不会编程的小老板、市场分析师

场景:客户说「帮我拉一下某政府网站的所有房价数据」「帮我分析 GitHub 上所有 AI 项目的趋势」

交付:用 DuckDB 一条 SQL 搞定,输出 Excel/CSV 报表。不需要写爬虫,不需要维护脚本。

报价:按数据源数量计费,¥300-800/次,批量包月 ¥2000-5000/月

2. 数据整合 + 自动化报表(¥500-2000/月/客户)

目标客户:电商卖家、SaaS 公司,数据分散在多个平台

场景:客户的销售数据在 Shopify(后台导出 CSV)、广告数据在 Google Ads(API 转 CSV)、库存数据在本地 Excel

方案:DuckDB 直接远程读取这些公开/半公开的 CSV URL,每天自动出日报

交付:cron + DuckDB CLI,每天定时拉取 → 分析 → 发邮件/钉钉/企微

报价:¥500-2000/月/客户,维护成本极低

3. 数据湖轻量化改造(¥2000-8000/项目)

目标客户:中小公司,数据在 S3/MinIO 上,传统做法是每天 ETL 到本地

方案:改为 DuckDB 直接查询 S3 Parquet,省掉 ETL 步骤和中间存储成本

交付:配置 DuckDB httpfs + S3 凭证 + 编写远程查询 SQL

报价:¥2000-8000/项目(取决于数据规模和复杂度)

4. 技术培训与咨询(¥2000-5000/场)

目标客户:公司的数据分析团队、IT 部门

内容:教团队如何用 DuckDB 替代传统的 ETL 和爬虫流程

报价:¥2000-5000/场(2-3 小时线上/线下)


服务目标客户单价区间月收入潜力
公开数据采集小老板、分析师¥300-800/次¥3,000-8,000
自动化报表订阅电商、SaaS 公司¥500-2,000/月¥5,000-20,000
数据湖改造咨询中小企业¥2,000-8,000/项目¥8,000-16,000
技术培训数据团队¥2,000-5,000/场¥4,000-15,000

七、总结

DuckDB 的远程文件查询能力,最被低估的价值不是「查询性能」,而是 「数据获取的零成本」

  • 以前需要写爬虫才能拿到的公开数据 → 一条 SQL
  • 以前需要下载到本地才能分析的远程 CSV → 直接 read_csv_auto('url')
  • 以前需要 ETL 才能查询的 S3 数据 → read_parquet('s3://...')
  • 以前需要定时脚本维护的数据采集 → cron + duckdb -c "SELECT ..."

核心原则:数据在哪儿,DuckDB 就查到哪儿。

下次有人给你一个数据链接,不要 wget,不要写 requests.get(),试试 DuckDB 的 read_csv_auto('URL')。5 秒钟,数据就摆在你面前。


DuckDB 版本要求:1.0+(httpfs 内置)
Python 依赖pip install duckdb pandas
CLI 版本duckdb -c "SELECT ..." 无需 Python
许可证:MIT(完全开源,可用于商业项目)

📺 Watch video tutorials → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计