AWS Lambda:Python 3 中用 wget 下载文件并上传到 S3

在 Lambda 里下载一个大文件再存到 S3,听起来很简单——但如果你直接 pip install requests 塞进 zip 包,或者用 Lambda 层,会发现麻烦得不行。

其实 AWS Lambda 的基础镜像里已经自带了 wgetcurl,根本不用装任何第三方库。这篇教你用纯 Python + subprocess 搞定下载 + 上传到 S3 的全过程。

为什么用 wget 而不是 requests/boto3?

Lambda 的 Docker 基础镜像(Amazon Linux 2023)已经内置了 wgetcurltar 等常用命令行工具。好处:

  1. 零依赖——不用装 requests、urllib3 等第三方包,zip 包体积小
  2. 速度快——wget 底层用 C 实现,下载大文件比纯 Python 快
  3. 自带断点续传——wget 有 -c 参数,网络中断可以继续
  4. Lambda 层都不用——不需要搞 Lambda Layer 来装依赖

当然,上传到 S3 还是用 boto3,因为 boto3 是 Lambda 运行时自带的,不需要额外安装。

完整代码

直接上代码,后面再逐段讲:

import os
import subprocess
import boto3
import json
import logging
from uuid import uuid4

logger = logging.getLogger()
logger.setLevel(logging.INFO)

DOWNLOAD_URL = os.environ.get("DOWNLOAD_URL", "https://example.com/largefile.zip")
S3_BUCKET = os.environ.get("S3_BUCKET", "my-bucket")
S3_PREFIX = os.environ.get("S3_PREFIX", "downloads")
LOCAL_PATH = "/tmp/downloaded_file"


def lambda_handler(event, context):
    # 1. 下载文件
    logger.info(f"开始下载: {DOWNLOAD_URL}")
    download_file(DOWNLOAD_URL, LOCAL_PATH)
    
    # 2. 上传到 S3
    logger.info("开始上传到 S3")
    s3_key = f"{S3_PREFIX}/{os.path.basename(DOWNLOAD_URL)}"
    upload_to_s3(LOCAL_PATH, S3_BUCKET, s3_key)
    
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "下载并上传成功",
            "s3_bucket": S3_BUCKET,
            "s3_key": s3_key,
            "file_size": os.path.getsize(LOCAL_PATH)
        })
    }


def download_file(url, local_path):
    """用 wget 下载文件到本地"""
    cmd = [
        "wget",
        "-O", local_path,       # 输出路径
        "--timeout=300",        # 超时 5 分钟
        "--tries=3",            # 最多重试 3 次
        "--progress=dot:giga",  # 进度显示
        url
    ]
    
    logger.info(f'执行命令: {" ".join(cmd)}')
    
    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        timeout=300
    )
    
    if result.returncode != 0:
        logger.error(f"下载失败: {result.stderr}")
        raise RuntimeError(f"wget 下载失败: {result.stderr}")
    
    file_size = os.path.getsize(local_path)
    logger.info(f"下载完成: {file_size} bytes")
    return file_size


def upload_to_s3(local_path, bucket, key):
    """用 boto3 上传文件到 S3"""
    s3 = boto3.client("s3")
    
    s3.upload_file(
        Filename=local_path,
        Bucket=bucket,
        Key=key,
        ExtraArgs={
            "StorageClass": "STANDARD"
        }
    )
    
    logger.info(f"上传成功: s3://{bucket}/{key}")

代码拆解

1. 下载部分:subprocess.run + wget

subprocess.run(
    cmd,
    capture_output=True,   # 捕获 stdout 和 stderr
    text=True,             # 输出转为字符串而非 bytes
    timeout=300            # 超时保护
)

关键参数说明:

wget 参数 作用
-O /tmp/xxx 指定保存路径(Lambda 的 /tmp 最多 10GB)
--timeout=300 连接超时 300 秒
--tries=3 失败自动重试 3 次
--progress=dot:giga 用点号显示进度,适合日志查看

2. 上传部分:boto3 upload_file

s3.upload_file(
    Filename=local_path,    # 本地文件路径
    Bucket=bucket,          # S3 桶
    Key=key,                # S3 中的对象路径
    ExtraArgs={"StorageClass": "STANDARD"}
)

boto3 的 upload_file 会自动处理大文件分片上传(超过 8MB 自动 multipart),不需要自己写分片逻辑。

Lambda 配置步骤

Step 1:创建 Lambda 函数

运行时选 Python 3.12(或其他 3.x 版本)。

Step 2:设置环境变量

在 Lambda 控制台 -> Configuration -> Environment variables 中设置:

  • DOWNLOAD_URL:你要下载的文件 URL
  • S3_BUCKET:目标 S3 存储桶名
  • S3_PREFIX:S3 中的文件夹路径(如 downloads)

Step 3:IAM Role 权限

Lambda 执行角色需要 S3 写入权限:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::bucket-name/*"
        }
    ]
}

Step 4:超时和内存

  • 超时:设成 5-15 分钟,取决于文件大小
  • 内存:至少 512MB,大文件建议 1024MB 以上

Step 5:网络

如果 Lambda 在 VPC 私有子网中,需要 NAT Gateway 才能访问外网下载文件。如果 Lambda 不关联 VPC,默认就能访问公网。

超时和内存注意事项

限制 默认 最大
超时 3 秒 15 分钟
内存 128MB 10,240MB
/tmp 存储 512MB 10 GB
解压后包大小 250MB 250MB

Lambda 的 /tmp 默认 512MB,最大可以加到 10GB。大文件记得在控制台 Ephemeral storage 里调大。

常见问题

Q1: wget 下载报 403/401 怎么办?

加 Headers:

cmd = [
    "wget",
    "-O", local_path,
    "--header=Authorization: Bearer YOUR_TOKEN",
    "--header=Accept: application/octet-stream",
    url
]

Q2: 下载的文件名不固定?

用时间戳/UUID 避免冲突:

import time
filename = f"file_{int(time.time())}.zip"
local_path = f"/tmp/{filename}"

Q3: 下载完想删本地文件?

os.remove(local_path)
logger.info("本地临时文件已清理")

Q4: 能不能同时下载多个文件?

ThreadPoolExecutor 并行下载:

from concurrent.futures import ThreadPoolExecutor

def download_many(urls):
    with ThreadPoolExecutor(max_workers=3) as executor:
        executor.map(download_file, urls, ["/tmp/file1", "/tmp/file2", "/tmp/file3"])

Q5: 和直接用 requests 比哪个好?

对比项 wget requests
需要装包? 不需要 需要
大文件性能 更快(C 实现) 稍慢
断点续传 wget -c 需自己写
自定义 Headers 支持 支持

结论:简单下载场景首选 wget;需要复杂认证/回调逻辑时用 requests。

总结

核心思路就三步:

  1. wget 下载文件到 /tmp(零依赖,Lambda 自带)
  2. boto3 上传到 S3(boto3 运行时自带)
  3. 配置好 IAM Role超时/内存

整个方案不需要 Lambda Layer、不需要第三方依赖,zip 包就一个 Python 文件,部署极简。