2011-05-09 17 views
2

我有一个包含大约4500个XML(HTML5)文件的目录,我想创建其数据的“清单”(实质上为titlebase/@href)。处理(太)许多XML文件(使用TagSoup)

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

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

我无法弄清楚的是如何解决这个问题。我试着读一下关于Iteratee的内容;我可以轻松地将它与Tagsoup挂钩吗?严格的IO,无论如何我都用它(heh),即使文件不是很大(平均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. 
+0

你需要考虑你的设计,因为显然有很多文件你不能同时打开所有的手柄(懒惰的方法),也不能打开它们并同时读取它们(完全严格的方法) 。那么如何使用严格的IO(例如'Data.Text')一次处理一个文件。 – 2011-05-09 22:38:42

+0

我很乐意一次处理一个文件!我不知道如何做到这一点,虽然... – vicvicvic 2011-05-09 22:55:16

回答

3

快速和肮脏的解决方案:

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

稍微更好的解决办法是写一个适当的NFData实例MetaData,而不是仅仅使用评估。

+0

啊,这是有效的,因为它*强制* x和y被评估,这迫使文件的内容被获取?我之前尝试过使用“文件”,但却被一切懒惰的评价所困扰(hGetContents将会被调用得太晚)。 – vicvicvic 2011-05-09 22:53:07

+0

@vicvicvic:是的 - 它确保您在关闭句柄之前实际阅读了需要的文件部分。 – sclv 2011-05-09 22:54:15

+1

刚刚测试过这个解决方案,它的工作原理和我所希望的一样(大约4秒处理所有文件)。也许它很脏,但是在纯语言中遇到IO问题也是如此,恕我直言:) – vicvicvic 2011-05-09 23:14:51

2

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

+0

有没有一个明显的方式做到这一点? 'readMetaData'从不消耗整个文件;一旦它完成了有趣的事情,我可以“跳跃 - 消耗”吗? – vicvicvic 2011-05-09 22:45:15

+0

@vicvicvic:查看我的回答。那里的文件刚刚关闭(通过''文件')一旦你得到你想要的。 – sclv 2011-05-09 22:52:33