Bash中的并行wget

84

我从一个网站获取了很多比较小的页面,想知道是否可以在Bash中以并行方式处理。目前我的代码看起来像这样,但是执行起来需要一段时间(我认为导致拖慢速度的是连接延迟)。

for i in {1..42}
do
    wget "https://www.example.com/page$i.html"
done

我听说过使用xargs,但我对它一无所知,而且man手册非常令人困惑。你有什么建议吗?是否可以并行执行?还有其他方法可以解决这个问题吗?

5个回答

207

使用xargs可以达到与将wget后台运行使用&-b相同的效果,而且更好。

优点在于,xargs正确同步,无需额外工作。这意味着您可以安全地访问已下载的文件(假设没有发生错误)。所有下载将在xargs退出时完成(或失败),并且您可以通过退出代码知道一切是否顺利。这比忙等待sleep并手动测试完成要好得多。

假设URL_LIST是包含所有URL的变量(可以使用OP示例中的循环构建,但也可以是手动生成的列表),则运行以下命令:

echo $URL_LIST | xargs -n 1 -P 8 wget -q

使用-n 1参数将一个参数传递给wget,并且最多同时执行8个并行的wget进程(-P 8)。xarg会在最后一个已产生的进程完成后返回,这正是我们想要的结果,没有需要额外的技巧。

我选择的8个并行下载的“魔法数字”并不是一成不变的,但它可能是一个很好的折衷方案。有两个因素可以“最大化”一系列下载:

一是填满“电缆”,即利用可用带宽。假设“正常”情况下(服务器的带宽高于客户端),这已经是一个或最多两个下载的情况。增加更多连接只会导致数据包被丢弃和TCP拥塞控制 kicking in,并且 N 个下载每个呈现渐近于1/N的带宽,效果相同(减去丢失的数据包,减去窗口大小恢复)。在IP网络中,数据包被丢弃是正常发生的事情,这就是拥塞控制应该发挥作用的方式(即使是单个连接),通常影响几乎为零。然而,建立过多连接会放大此效应,因此可能会变得明显。不管怎样,这并不会使任何事情更快。

第二个因素是连接建立和请求处理。在这里,有几个额外的连接实际上非常有帮助。面临的问题是两个往返延迟(通常在同一地理区域内为20-40ms,在洲际之间为200-300ms)加上服务器实际需要处理请求并将其推送到套接字的奇怪的1-2毫秒。就本身而言,这并不是很长的时间,但乘以几百/千个请求,它很快就会累计起来。
拥有半打到一打请求在飞行中可以隐藏大部分或全部这种延迟(它仍然存在,但由于它重叠,所以不会总和!)。同时,只有少数并发连接不会产生负面影响,例如导致过度拥塞或强制服务器分叉新进程。


5
这绝对是最好的方法,因为它使用了通用工具xargs,而且这种方法可以应用于许多其他命令。 - SineSwiper
7
使用wget下载多个HTTP文件时,由于保持连接机制的存在,wget可以重复使用HTTP连接。但是,如果为每个文件启动新进程,则无法使用此机制,每次都必须重新建立连接(TCP三次握手)。因此,我建议将-n参数增加到大约20左右。在默认配置下,Apache HTTP服务器在一个保持连接会话中仅提供最多100个请求,因此在此处超过100个可能没有意义。 - user7610
2
很好的答案,但如果我想传递两个变量值给wget怎么办?我想指定目标路径以及URL。使用xargs技术仍然可以实现吗? - Ricky
1
@DomainsFeatured 没有这样做的 xargs 选项。这也是开发 GNU Parallel 的原因之一。 - Ole Tange
2
@Justin 你可以直接使用cat ./urls | xargs -n 1 -P 8 wget [...],或者更好的方式是xargs -a ./urls -n 1 -P 8 wget [...],而不是将文件读入变量中。 - Hitechcomputergeek
显示剩余9条评论

65

抱歉,我现在没有要下载的东西,但将来肯定会有。假设我运行seq 30 | parallel -j5 mkdir /tmp/{},它应该创建30个文件夹/tmp/1、/tmp/2等等吗?如果是这样,在我的系统上它并没有这样做。 - ka3ak
@ka3ak 你可能发现了一个错误,请按照以下步骤进行:https://www.gnu.org/software/parallel/man.html#REPORTING-BUGS - Ole Tange
1
似乎我的系统上已经预装了另一个同名工具,甚至还有“-j”选项用于作业。我刚刚运行了“sudo apt install parallel”来安装正确的工具。 - ka3ak

9
您可以使用-b选项:
wget -b "https://www.example.com/page$i.html"

如果您不需要日志文件,可以添加选项-o /dev/null

-o FILE 将日志消息记录到FILE文件中。

1
不,没关系 - 查看 man 页面('-o logfile...')。 - uzsolt
抱歉,我没有正确阅读。我以为你说的是“如果你不想要输出文件,请添加 -o 选项”。因为我这样做了,结果在 /root 目录下有成千上万个文件。感谢澄清。 - ipruthi

6

将&添加到命令中可以使其在后台运行

for i in {1..42}
do
    wget "https://www.example.com/page$i.html" &
done

3

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