CURL断点续传

前一篇文章《CURL帮助文档》中简单介绍了如何用curl命令下载RedPajama数据集。在下载的过程中,前期使用huggingface里面提供的脚本下载比较顺利。但是到中后期,尤其是下载common crawl部分的数据集,就开始出现很多问题。目前下载过程中遇到的各种问题如下:

curl (18) transfer closed

主要报错代码如下:

curl (18) transfer closed with xxxx bytes remaining to read

curl (92) HTTP/2 stream not closed cleanly

主要报错代码如下:

curl (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)

curl (56) HTTP code 407

主要报错代码如下:

curl (56) Received HTTP code 407 from proxy after CONNECT

处理方案

导致以上错误代码的原因不同,但是结果就是有些数据文件已经下载成功了,有些数据文件没有下载成功,有些下载到一半。首先看一下我下载文件的最初脚本:

#!/bin/bash

while read line; do
    dload_loc=${line#https://data.together.xyz/redpajama-data-1T/v1.0.0/}
    mkdir -p $(dirname $dload_loc)
    echo $line
    curl -k "$line" -o "$dload_loc"
done < url.txt

从url列表文件url.txt中读取url并且下载保存。其中url.txt是一行一个URL的纯文本文件(注意必须是Unix格式的换行),例如:

https://data.together.xyz/redpajama-data-1T/v1.0.0/common_crawl/2022-05/en_middle_0079.json.gz.dedup.classifier.jsonl.zst
https://data.together.xyz/redpajama-data-1T/v1.0.0/common_crawl/2022-05/en_middle_0080.json.gz.dedup.classifier.jsonl.zst
https://data.together.xyz/redpajama-data-1T/v1.0.0/common_crawl/2022-05/en_middle_0081.json.gz.dedup.classifier.jsonl.zst
https://data.together.xyz/redpajama-data-1T/v1.0.0/common_crawl/2022-05/en_middle_0082.json.gz.dedup.classifier.jsonl.zst
https://data.together.xyz/redpajama-data-1T/v1.0.0/common_crawl/2022-05/en_middle_0075.json.gz.dedup.classifier.jsonl.zst

有800多个URL,有些已经下载好了,有些报错下载了一半,有些完全下载失败(本地没有文件)。因此首先使用以下命令从日志中把报错的URL都归集在一起:

grep -B4 ^curl log.txt | grep http > error.txt
grep -B3 "curl: (56)" log.txt | grep http >> error.txt

随后,修改以下原来脚本中的curl语句,加上“-C -”参数即可。这样如果本地已经有文件存在,则会断点续传,否则直接下载。

然而,世界中是那么无情的。“-C -”参数并不是每次都能断点续传。至少在下载RedPajama的common crawl数据集时,时好时坏。通常会报错:

curl: (33) HTTP server doesn't seem to support byte ranges. Cannot resume.

处理方案更新

另外,并不是每次都有日志文件的,比如说第一次运行时没有把日志保存在文件中。这时就无法通过grep提取下载失败的URL了。在这种情况下就需要对比本地文件大小和网络文件大小,决定那些文件需要重新下载。更新后的脚本如下(此方案仍未解决无法断点续传的问题):

#!/bin/bash

while read line; do
    dload_loc=${line#https://data.together.xyz/redpajama-data-1T/v1.0.0/}
    mkdir -p $(dirname $dload_loc)
    echo $line
    # 如果文件不存在ls会报错,2>/dev/null,将报错信息不要打印在屏幕上
    # -n 测试变量不为空
    fileSize=$(ls -l $dload_loc 2>/dev/null | awk '{print $5}')
    if [ -n "$fileSize" ]; then
        # 存在本地文件,比较下载进度
        remoteSize=$(curl -k --head -s "$line" | grep "content-length" | awk '{print $2}')
        if [[ "$fileSize" == "$remoteSize" ]]; then
            echo "$fileSize == $remoteSize"
        else
            # 断点续传(有时候有问题,报错curl: (33) HTTP server doesn't seem to support byte ranges. Cannot resume.)
            curl -k -C - --retry 3 "$line" -o "$dload_loc"
        fi
    else
        # 不存在本地文件,直接下载
        curl -k --retry 3 "$line" -o "$dload_loc"
    fi
done < e.txt

处理方案更新:解决-C无法续传问题

上节说道有时-C无法续传,我尝试使用–header指定range的方式下载,并把内容重定向到目标文件中。更新后的代码如下:

#!/bin/bash

while read line; do
    dload_loc=${line#https://data.together.xyz/redpajama-data-1T/v1.0.0/}
    mkdir -p $(dirname $dload_loc)
    echo $line
    # 如果文件不存在ls会报错,2>/dev/null,将报错信息不要打印在屏幕上
    # -n 测试变量不为空
    fileSize=$(ls -l $dload_loc 2>/dev/null | awk '{print $5}')
    if [ -n "$fileSize" ]; then
        # 存在本地文件,比较下载进度
        remoteSize=$(curl -k --head -s "$line" | grep "content-length" | awk '{print $2}')
        if [[ "$fileSize" == "$remoteSize" ]]; then
            echo "$fileSize == $remoteSize"
        else
            # 断点续传,使用header指定Range(有时候-C有问题,报错curl: (33) HTTP server doesn't seem to support byte ranges. Cannot resume.)
            curl -k --header "Range: bytes=$fileSize-$remoteSize" "$line" >> "$dload_loc"
        fi
    else
        # 不存在本地文件,直接下载
        curl -k --retry 3 "$line" -o "$dload_loc"
    fi
done < e.txt

文件大小错误

本来以为CURL下载显示100%就算成功了,结果在最后文件校对阶段发现并不是那么回事儿。Common Crawl的文件大小一般在1G以上,但是文件夹中发现很多4k不到,或者5M左右的文件,明显是有问题的。因此没有办法只能全量扫一遍,看看那些文件有问题。比较科学的做法应该是使用SHA256 Checksums对比,但是有点懒了,使用现成的脚本搞一下吧。

#!/bin/bash

while read line; do
    dload_loc=${line#https://data.together.xyz/redpajama-data-1T/v1.0.0/}
    mkdir -p $(dirname $dload_loc)
    echo $line
    # 如果文件不存在ls会报错,2>/dev/null,将报错信息不要打印在屏幕上
    # -n 测试变量不为空
    fileSize=$(ls -l $dload_loc 2>/dev/null | awk '{print $5}')
    if [ -n "$fileSize" ]; then
        # 存在本地文件,比较下载进度
        remoteSize=$(curl -k --head -s "$line" | grep "content-length" | awk '{print $2}')
        if [[ "$fileSize" == "$remoteSize" ]]; then
            echo "$fileSize == $remoteSize"
        else
            # 断点续传(有时候有问题,报错curl: (33) HTTP server doesn't seem to support byte ranges. Cannot resume.)
            echo "需下载 $fileSize != $remoteSize"
        fi
    else
        # 不存在本地文件,直接下载
        echo "需下载 0"
    fi
done < all.txt

扫完之后会发现一些问题,比如之前很多4k不到或者5M左右的文件最后显示实际大小超过1G。然而有些本来下载好的超过1G的文件,显示远端大小为5M。因此决定把小于1G的都重新扫描。以下是使用GREP对比文件后筛选出需要下载的URL。

grep -B1 == check.log | grep http > ok.txt
grep -v -f ok.txt all.txt > error.txt
Captain QR Code

扫码联系船长

发表回复

您的电子邮箱地址不会被公开。