使用Haskell从URL下载文件仅获取网页源码的问题咨询
原方案有效性说明
你贴的http-conduit实现核心逻辑没有错误,但默认配置下无法覆盖绝大多数公开文件下载场景,这也是你拿到HTML源码而非目标文件的根本原因,具体缺省配置导致的问题有两个:
- 默认不自动跟随HTTP重定向:绝大多数文件下载站、开源镜像站的公开链接都会先返回301/302/307状态码跳转到真实的文件存储节点,原代码只会获取跳转提示页的HTML内容,不会自动走到最终文件地址。
- 默认请求不带合法浏览器标识:几乎所有提供大文件下载的站点都会校验请求的
User-Agent头,对无标识的脚本请求直接返回访问引导页、人机验证页的HTML源码,不会返回真实文件流。
修正后可直接使用的实现
针对普通压缩包、ISO镜像等公开资源下载,只需要补全上述两个缺省配置,再加上文件写入的原子性保护即可,参考实现如下:
{-# LANGUAGE OverloadedStrings #-} import Network.HTTP.Conduit import Network.HTTP.Types.Header (hUserAgent) import System.Directory (renameFile) import System.Process (callCommand) import qualified System.Process.ListLike as PL (shellEscape) -- 入参:资源URL、本地保存路径 downloadFile :: String -> FilePath -> IO () downloadFile url savePath = do initReq <- parseRequest url let req = initReq { redirectCount = 10 -- 开启重定向跟随,设置10次上限避免跳转死循环 , requestHeaders = [ (hUserAgent, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36") ] } -- 先写入临时文件,下载完成后再重命名,避免中途断网/程序退出产生损坏的目标文件 runResourceT $ httpSink req $ \_ -> sinkFile "tmp_download_part" renameFile "tmp_download_part" savePath
这个版本可以直接覆盖Ubuntu镜像站、普通开源软件压缩包的下载需求。
复杂场景备选方案
如果遇到需要登录鉴权、断点续传、10GB以上超大文件下载的场景,不需要硬写HTTP逻辑,直接选择更成熟的方案即可:
- 调用系统原生下载工具:通过进程调用
wget/curl完成下载,这类工具已经内置了断点续传、重试、重定向、Cookie处理、大文件分块下载的全部逻辑,稳定性远高于手写实现,调用时注意对URL和路径做shell转义避免注入问题,示例调用:callCommand $ "wget -c " ++ PL.shellEscape url ++ " -O " ++ PL.shellEscape savePath - 使用绑定libcurl的Haskell库:比如
download-curl,底层依赖成熟的libcurl能力,不用手动配置请求参数即可支持绝大多数下载场景,适合不想依赖外部命令行工具的场景。
内容的提问来源于stack exchange,提问作者Thomas Meyer
相关产品推荐
相关产品推荐

