处理(也)许多XML文件(使用TagSoup)

时间:2011-05-09 22:29:22

标签: xml haskell io lazy-evaluation tag-soup

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

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

这适用于文件的子集,但最终会遇到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.

2 个答案:

答案 0 :(得分:3)

快速而肮脏的解决方案:

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

一个稍微好一点的解决方案是为NFData编写一个正确的MetaData实例,而不仅仅是使用evaluate。

答案 1 :(得分:2)

如果要保留当前设计,必须确保parseMetaDataFile在返回之前已从readFile中使用了整个字符串。当readFile到达文件结尾时,文件描述符将被关闭。