使用Curl命令行工具进行并行下载

34

我想从一个网站下载一些页面,使用curl已经成功实现了,但我想知道是否可以像大多数下载管理器一样让curl同时下载多个页面,以加快下载速度。是否可以在curl命令行工具中实现?

我目前使用的命令是:

curl 'http://www...../?page=[1-10]' 2>&1 > 1.html

我正在从1到10下载网页,并将它们存储在名为1.html的文件中。

另外,是否有可能让curl将每个URL的输出写入一个单独的文件,例如URL.html,其中URL是正在处理的页面的实际URL。


预请求以查找内容长度,使用 --range 将单个下载分割成多个,运行多进程 curl,保持块的顺序并在获得有序序列后将它们连接起来,这是大多数开发者正在做的事情(例如: htcat 项目)。 - user257319
你怎么知道要下载多少页?你是随意选择1到10吗? - ghoti
相关问题:https://dev59.com/rGkw5IYBdhLWcg3wfKdA ... 尽管它是针对文件下载的,但所选答案中关于curl使用的解释可能会有用。 - ghoti
11个回答

56

我的回答可能有点晚,但我认为所有现有的答案都稍微有些不足。我处理这类问题的方式是使用 xargs,它能够在子进程中运行指定数量的命令。

我会使用的一行命令是:

$ seq 1 10 | xargs -n1 -P2 bash -c 'i=$0; url="http://example.com/?page${i}.html"; curl -O -s $url'

这需要一些解释。使用-n 1指示xargs一次处理一个输入参数。在这个例子中,数字1 ... 10分别被单独处理。而-P 2告诉xargs保持2个子进程始终运行,每个进程处理一个参数,直到所有输入参数都被处理。

你可以将其视为shell中的MapReduce。或者只是Map阶段。无论如何,这是一种有效的方法,可以在确保不fork bomb机器的同时完成大量工作。虽然也可以在shell中使用for循环做类似的事情,但最终会涉及到进程管理,一旦你意识到这种使用xargs的方式有多么强大,就会感到这样做有点毫无意义。

更新:我怀疑我的xargs示例可以改进(至少在Mac OS X和带有-J标志的BSD上)。使用GNU Parallel时,命令也稍微不那么笨重:

parallel --jobs 2 curl -O -s http://example.com/?page{}.html ::: {1..10}

4
请注意,如果您有一个功能齐全的 xargs 版本,您可以简单地执行以下操作:seq 1 10 | xargs -I{} -P2 -- curl -O -s 'http://example.com/?page{}.html' - Six
加一是因为使用xargs非常出色 - Zibri

31

实际上,curl只是一个简单的UNIX进程。您可以同时运行多个这样的curl进程,将其输出发送到不同的文件中。

curl可以使用URL的文件名部分生成本地文件。只需使用-O选项(有关详细信息,请参阅man curl)。

您可以使用以下类似代码:

urls="http://example.com/?page1.html http://example.com?page2.html" # add more URLs here

for url in $urls; do
   # run the curl job in the background so we can start another job
   # and disable the progress bar (-s)
   echo "fetching $url"
   curl $url -O -s &
done
wait #wait for all background jobs to terminate

6
假设我需要下载100页...你的脚本会同时启动100个curl实例(可能会导致网络拥塞)...我们能否做到在任何时刻只有X个curl实例正在运行,一旦其中一个完成了工作,脚本就启动另一个实例...类似于作业调度 - Ravi Gupta
Ravi,这变得更加困难了。您需要一个由多个进程提供服务的作业队列。一个简单的解决方案是将所有作业发送到UNIX batch命令(尝试使用man batch)。只有当系统负载低于某个阈值时才执行作业。因此,大多数作业会排队,一次只有几个作业在运行。 - nimrodm
@EladKarako:请注意curl命令末尾的&。这将在后台运行curl作业,而不会阻塞主进程。因此,是的,这绝对是并行的(尽管我仍然更喜欢make技巧甚至是并行的xargs)。 - nimrodm
2
GNU parallel 可以限制“作业槽”的数量。(示例) - joeytwiddle

24

从7.66.0版本开始,curl工具终于内置支持在单个非阻塞进程中并行下载多个URL。与大多数情况下的xargs和后台生成相比,这应该更快,更节省资源:

curl -Z 'http://httpbin.org/anything/[1-9].{txt,html}' -o '#1.#2'

这将会以并行的方式下载18个链接并且同时并行地写入到18个不同的文件中。这个功能的官方公告由Daniel Stenberg在此发表:https://daniel.haxx.se/blog/2019/07/22/curl-goez-parallel/


为了限制并发下载的数量,可以使用 --parallel-max [num] 标志。 - toaruScar
如何从文件中提供 URL 列表,并将仅 HTTP 状态代码写入文件列表(或一个文件)? - Andrew

8

curlwget无法将单个文件分块并行下载,但有替代方案:

  • aria2 (written in C++, available in Deb and Cygwin repo's)

    aria2c -x 5 <url>
    
  • axel (written in C, available in Deb repo)

    axel -n 5 <url>
    
  • wget2 (written in C, available in Deb repo)

    wget2 --max-threads=5 <url>
    
  • lftp (written in C++, available in Deb repo)

    lftp -n 5 <url>
    
  • hget (written in Go)

    hget -n 5 <url>
    
  • pget (written in Go)

    pget -p 5 <url>
    

aria2也可作为homebrew安装。 - james-see

7

7.68.0开始,curl能够并行获取多个URL。此示例将使用3个并行连接从urls.txt文件中获取URL:

curl --parallel --parallel-immediate --parallel-max 3 --config urls.txt

urls.txt:

url = "example1.com"
output = "example1.html"
url = "example2.com"
output = "example2.html"
url = "example3.com"
output = "example3.html"
url = "example4.com"
output = "example4.html"
url = "example5.com"
output = "example5.html"

6
Curl还可以通过将文件分成几个部分来加速下载:
$ man curl |grep -A2 '\--range'
       -r/--range <range>
              (HTTP/FTP/SFTP/FILE)  Retrieve a byte range (i.e a partial docu-
              ment) from a HTTP/1.1, FTP or  SFTP  server  or  a  local  FILE.

这是一个自动启动curl并设置所需的并发进程数量的脚本:https://github.com/axelabs/splitcurl

6
为了启动并行命令,为什么不使用备受推崇的make命令行实用程序。它支持并行执行、依赖跟踪等功能。
如何操作?在下载文件的目录中,创建一个名为Makefile的新文件,并将以下内容复制到其中:
# which page numbers to fetch
numbers := $(shell seq 1 10)

# default target which depends on files 1.html .. 10.html
# (patsubst replaces % with %.html for each number)
all: $(patsubst %,%.html,$(numbers))

# the rule which tells how to generate a %.html dependency
# $@ is the target filename e.g. 1.html
%.html:
        curl -C - 'http://www...../?page='$(patsubst %.html,%,$@) -o $@.tmp
        mv $@.tmp $@

注意:最后两行应该以TAB字符开头(而不是8个空格),否则make不会接受该文件。

现在你只需要运行:

make -k -j 5

我使用的curl命令将输出存储在1.html.tmp中,只有当curl命令成功时,它才会被重命名为1.html(由下一行上的mv命令执行)。因此,如果某些下载失败,您可以重新运行相同的make命令,它将恢复/重试第一次下载失败的文件。一旦所有文件都成功下载,make将报告没有更多需要做的内容,所以运行它多一次也是“安全”的。(-k开关告诉make保持下载其余文件,即使一个文件下载失败。)

“-j 5” 告诉 make 最多并行运行 5 个 curl 命令。 - Jonas Berlin
真的是最好的解决方案,因为它允许恢复失败的下载并使用“make”,这既稳健又在任何Unix系统上都可用。 - nimrodm
这是一个很棒的答案。详细解释并展示了make的一些不错特性。 - Matt Greer
使用这种方法唯一的问题是我真的记不住 $(patsubst %,%.html,$(numbers)) 部分。这比 tar 要难得多。 - Mayli

2

我基于 fmtxargs 提出了一种解决方案。这个想法是在大括号内指定多个URL,如:http://example.com/page{1,2,3}.html,然后使用 xargs 并行运行它们。以下命令将启动3个下载进程:

seq 1 50 | fmt -w40 | tr ' ' ',' \
| awk -v url="http://example.com/" '{print url "page{" $1 "}.html"}' \
| xargs -P3 -n1 curl -o

因此,将生成4行可下载的URL,并将其发送到xargs

curl -o http://example.com/page{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}.html
curl -o http://example.com/page{17,18,19,20,21,22,23,24,25,26,27,28,29}.html
curl -o http://example.com/page{30,31,32,33,34,35,36,37,38,39,40,41,42}.html
curl -o http://example.com/page{43,44,45,46,47,48,49,50}.html

2

如果你的系统有像pidofpgrep这样的命令,那么运行有限数量的进程就很容易了。给定一个进程名,这些命令会返回pid(pid的计数告诉你有多少个正在运行)。

类似于以下内容:

#!/bin/sh
max=4
running_curl() {
    set -- $(pidof curl)
    echo $#
}
while [ $# -gt 0 ]; do
    while [ $(running_curl) -ge $max ] ; do
        sleep 1
    done
    curl "$1" --create-dirs -o "${1##*://}" &
    shift
done

要这样调用:

script.sh $(for i in `seq 1 10`; do printf "http://example/%s.html " "$i"; done)

脚本中的curl命令未经测试。

0

Bash 3或更高版本允许您使用扩展序列表达式填充数组以包含多个值:

$ urls=( "" http://example.com?page={1..4} )
$ unset urls[0]

请注意[0]的值,它是提供的一种简便方式,使索引与页面编号对齐,因为bash数组从0开始自动编号。显然,这种策略并不总是有效。无论如何,在此示例中,您可以取消设置它。
现在你有了一个数组,可以使用declare -p验证其内容:
$ declare -p urls
declare -a urls=([1]="http://example.com?Page=1" [2]="http://example.com?Page=2" [3]="http://example.com?Page=3" [4]="http://example.com?Page=4")

现在您已经有了一个URL列表数组,请将该数组扩展为curl命令行:
$ curl $(for i in ${!urls[@]}; do echo "-o $i.html ${urls[$i]}"; done)

curl 命令可以接受多个 URL 并获取它们,重复使用现有连接(HTTP/1.1)到一个公共服务器,但需要在每个 URL 前加上 -o 选项以便下载和保存每个目标。请注意,某些 URL 中的字符可能需要转义以避免与您的 shell 交互。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接