Намерение: Небольшое приложение для изучения Haskell: загружает статью википедии, затем загружает все статьи, связанные с ней, затем загружает все статьи, связанные с ними, и так далее ... до указанной глубины рекурсиидостигнутоРезультат сохраняется в файл.
Подход: Используйте StateT
для отслеживания очереди загрузки, загрузки статьи и обновления очереди.Я строю список IO [WArticle]
рекурсивно, а затем распечатываю его.
Проблема: При профилировании я обнаружил, что общее количество используемой памяти пропорционально количеству загруженных статей.
Анализ: По литературе я считаю, что это проблема лени и / или строгости.BangPatterns уменьшил потребление памяти, но не решил пропорциональность.Кроме того, я знаю, что все статьи загружаются до начала вывода файла.
Возможные решения:
1) Функция getNextNode :: StateT CrawlState IO WArticle
(ниже) уже имеет IO.Одним из решений будет просто записать в него файл и только вернуть состояние.Это означало бы, что файл записан очень маленькими порциями.Не чувствует себя очень Haskell ..
2) Имейте функцию buildHelper :: CrawlState -> IO [WArticle]
(ниже) return [IO WArticle]
.Хотя я бы не знал, как переписать этот код, и в комментариях мне об этом говорили
Являются ли какие-либо из предложенных решений лучше, чем я думаю, или есть лучшие альтернативы?
import GetArticle (WArticle, getArticle, wa_links, wiki2File) -- my own
type URL = Text
data CrawlState =
CrawlState ![URL] ![(URL, Int)]
-- [Completed] [(Queue, depth)]
-- Called by user
buildDB :: URL -> Int -> IO [WArticle]
buildDB startURL recursionDepth = buildHelper cs
where cs = CrawlState [] [(startURL, recursionDepth)]
-- Builds list recursively
buildHelper :: CrawlState -> IO [WArticle]
buildHelper !cs@(CrawlState _ queue) = {-# SCC "buildHelper" #-}
if null queue
then return []
else do
(!article, !cs') <- runStateT getNextNode cs
rest <- buildHelper cs'
return (article:rest)
-- State manipulation
getNextNode :: StateT CrawlState IO WArticle
getNextNode = {-# SCC "getNextNode" #-} do
CrawlState !parsed !queue@( (url, depth):queueTail ) <- get
article <- liftIO $ getArticle url
put $ CrawlState (url:parsed) (queueTail++ ( if depth > 1
then let !newUrls = wa_links article \\ parsed
!newUrls' = newUrls \\ map fst queue
in zip newUrls' (repeat (depth-1))
else []))
return article
startUrl = pack "https://en.wikipedia.org/wiki/Haskell_(programming_language)"
recursionDepth = 3
main :: IO ()
main = {-# SCC "DbMain" #-}
buildDB startUrl recursionDepth
>>= return . wiki2File
>>= writeFile "savedArticles.txt"
Полный код на https://gitlab.com/mattias.br/sillyWikipediaSpider. Текущая версия ограничена загрузкой только первых восьми ссылок с каждой страницы, чтобы сэкономить время.Не меняя его, загрузите 55 страниц при использовании кучи ~ 600 МБ.
Спасибо за любую помощь!