【问题标题】:Processing (too) many XML files (with TagSoup)处理(过多)许多 XML 文件(使用 TagSoup)
【发布时间】:2011-08-22 01:03:03
【问题描述】:

我有一个包含大约 4500 个 XML (HTML5) 文件的目录,我想为它们的数据创建一个“清单”(主要是 titlebase/@href)。

为此,我一直在使用一个函数来收集所有相关的文件路径,用 readFile 打开它们,将它们发送到基于 tagoup 的解析器中,然后输出/格式化结果列表。

这适用于文件的子集,但最终会遇到openFile: resource exhausted (Too many open files) 错误。阅读后,这并不奇怪:我使用的是mapM parseMetaDataFile files,它会立即打开所有手柄。

我不知道如何解决这个问题。我试着读了一些关于 Iteratee 的文章;我可以轻松地将其与 Tagsoup 挂钩吗?严格的 IO,无论如何我使用它的方式(呵呵),即使文件不是很大(平均 28 KB),也会冻结我的计算机。

任何指针将不胜感激。我意识到创建一个大列表的方法也可能会失败,但 4.5k 元素并没有那么长......而且,可能到处都有更少的 String 和更多的 ByteString

这里有一些代码。我为我的天真道歉:

import System.FilePath
import Text.HTML.TagSoup

data MetaData = MetaData String String deriving (Show, Eq)

-- | Given HTML input, produces a MetaData structure of its essentials.
-- Should obviously account for errors, but simplified here.
readMetaData :: String -> MetaData
readMetaData input = MetaData title base
 where
  title =
    innerText $
    (takeWhile (~/= TagClose "title") . dropWhile (~/= TagOpen "title" []))
    tags
  base = fromAttrib "href" $ head $ dropWhile (~/= TagOpen "base" []) tags
  tags = parseTags input

-- | Parses MetaData from a file.
parseMetaDataFile :: FilePath -> IO MetaData
parseMetaDataFile path = fmap readMetaData $ readFile path

-- | From a given root, gets the FilePaths of the files we are interested in.
-- Not implemented here.
getHtmlFilePaths :: FilePath -> IO [FilePath]
getHtmlFilePaths root = undefined

main :: IO
main = do
  -- Will call openFile for every file, which gives too many open files.
  metas <- mapM parseMetaDataFile =<< getHtmlFilePaths

  -- Do stuff with metas, which will cause files to actually be read.

【问题讨论】:

  • 你需要考虑你的设计,因为显然有这么多文件,你既不能同时打开它们的所有句柄(懒惰的方法),也不能同时打开它们并读取它们(完全严格的方法)。那么如何一次处理一个文件,使用严格的 IO(例如Data.Text)。
  • 我希望一次处理一个文件!不过,我不知道该怎么做……

标签: xml haskell io lazy-evaluation haskell-tagsoup


【解决方案1】:

如果你想保留当前的设计,你必须确保 parseMetaDataFile 在返回之前已经消耗了 readFile 的整个字符串。当 readFile 到达文件末尾时,文件描述符将被关闭。

【讨论】:

  • 有明显的方法吗? readMetaData 从不消耗整个文件;一旦它完成了有趣的事情,我可以以某种方式“跳过消费”吗?
  • @vicvicvic:看我的回答。一旦你得到你想要的,文件就会被关闭(withFile)。
【解决方案2】:

快速而肮脏的解决方案:

parseMetaDataFile path = withFile path $ \h -> do
    res@(MetaData x y) <- fmap readMetaData $ hGetContents h
    Control.Exception.evaluate (length (x ++ y))
    return res

更好的解决方案是为MetaData 编写一个适当的NFData 实例,而不是仅仅使用评估。

【讨论】:

  • 啊,这行得通,因为它强制 x 和 y 被评估,这会强制获取文件的内容?我之前尝试过withFile,但被...一切的懒惰评估咬住了(然后调用hGetContents就太晚了)。
  • @vicvicvic:是的——它确保您在关闭句柄之前已经实际阅读了所需的文件部分。
  • 刚刚测试了这个解决方案,它的效果和我希望的一样好(处理所有文件大约需要 4 秒)。也许它很脏,但在纯语言中陷入 IO 问题也是如此,恕我直言 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-31
相关资源
最近更新 更多