2011-05-09 11 views
2

約4500個のXML(HTML5)ファイルがあるディレクトリがあり、データのマニフェストを作成したいと考えています(実質titlebase/@href)。多くのXMLファイル(TagSoup付き)を扱っています

この目的のために、関連するすべてのファイルパスを収集し、それらをreadFileで開き、tagsoupベースのパーサーに送信し、結果リストを出力/書式設定する関数を使用しています。

これはファイルのサブセットで動作しますが、最終的には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')を使用して、一度に1つのファイルを処理する方法について説明します。 –

+0

私は一度に1つのファイルを処理したいです!私はそれをどうやって行うのか分かりません... – vicvicvic

答えて

3

迅速かつ汚いソリューション:

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

少しよりよい解決策だけではなく、評価使用するのでは、MetaDataのために適切なNFDataインスタンスを作成することです。

+0

ああ、これは* xとyを強制的に評価して強制的にファイルの内容を取得するために機能しますか?私は前に 'withFile'を試しましたが、すべての怠惰な評価で噛まれました(hGetContentsはあまりに遅く呼び出されます)。 – vicvicvic

+0

@vicvicvic:これは、ハンドルを閉じる前に必要なファイルの部分を実際に読み取ったことを保証します。 – sclv

+1

ちょうどこのソリューションをテストしたところ、すべてのファイルを処理するのに約4秒かかりました。たぶんそれは汚いですが、純粋な言語でIO問題に悩まされている、imho :) – vicvicvic

2

あなたは現在のデザインを維持したい場合は、parseMetaDataFileが戻る前に、readFileのから文字列全体を消費していることを確認しなければなりません。 readFileがファイルの終わりに達すると、ファイル記述子は閉じられます。

+0

これを実行する明白な方法はありますか? 'readMetaData'はファイル全体を消費することはありません。面白いものをやった後、何とかスキップしてもいいですか? – vicvicvic

+0

@vicvicvic:私の答えを見てください。そこにあるファイルは、あなたが望むものを得ると( 'withFile'によって)閉じられます。 – sclv

関連する問題