Обработка (слишком) многих файлов XML (с TagSoup) - PullRequest
2 голосов
/ 10 мая 2011

У меня есть каталог с примерно 4500 XML (HTML5) файлами, и я хочу создать «манифест» их данных (по существу title и base/@href).

С этой целью яиспользовал функцию для сбора всех соответствующих путей к файлам, открывая их с помощью readFile, отправляя их в анализатор на основе tagoup, а затем выводя / форматируя результирующий список.

Это работает для подмножества файлов,но в итоге сталкивается с ошибкой openFile: resource exhausted (Too many open files).После некоторого чтения, это не так удивительно: я использую mapM parseMetaDataFile files, который сразу открывает все ручки.

Что я не могу понять, так это как обойти проблему.Я попытался прочитать немного о Iteratee;Могу ли я легко связать это с Tagsoup?Строгий ввод-вывод, так как я все равно его использовал (хе), заморозил мой компьютер, хотя файлы не очень большие (в среднем 28 КБ).

Любые указатели будут очень благодарны.Я понимаю, что подход к созданию большого списка также может потерпеть неудачу, но элементы размером 4,5 тыс. Не такие длинные ... Кроме того, вероятно, должно быть меньше 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 ]

3 голосов
/ 10 мая 2011

Быстрое и грязное решение:

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

Немного более удачное решение - написать правильный NFData экземпляр для MetaData, вместо того, чтобы использовать оценку.

2 голосов
/ 10 мая 2011

Если вы хотите сохранить текущий дизайн, вы должны убедиться, что parseMetaDataFile использует всю строку из readFile перед возвратом. Когда readFile достигнет конца файла, дескриптор файла закроется.

...