2013-02-25 18 views
8

5分ごとにWebページから情報をスクレイプするデーモンをHaskellに書いた。Haskell http-conduit Web-Scrapingデーモンがメモリ不足エラーでクラッシュする

デーモンはもともと約50分間正常に実行されましたが、予期せずout of memory (requested 1048576 bytes)で終了しました。私がそれを走らせるたびに、それは同じ時間の後に亡くなりました。 30秒しかスリープ状態にならず、代わりに8分後に亡くなった。

ウェブサイトをスクラップするコードがメモリが非常に非効率的であることを認識しました(9Mのhtmlを解析している間に約30Mから250Mにスリープしています)ので、これを書き換えて解析しました。問題は修正されたと思って、夜通しデーモンを走らせました。私が目を覚ますと、実際にはその夜よりもメモリが少なくなっていました。私は終わったと思ったが、始まってから約20時間後、同じエラーで墜落した。

私はghcプロファイリングを調べ始めましたが、それを動作させることができませんでした。次にrts optionsを使いこなし始めたところで、-H64mを設定して、私のプログラムが使用していたよりも大きいデフォルトのヒープサイズを設定しました。また、-Ksizeを使ってスタックの最大サイズを縮めて早くクラッシュさせるかどうかを確認しました。

私が作ったすべての変更にもかかわらず、デーモンは一定の繰り返し回数の後でもクラッシュするようです。解析のメモリをより効率的にすると、この値は高くなりますが、それでもクラッシュします。これは私には分かりません。なぜなら、これらのどれもが私の記憶のすべてを使うことに近づいていないし、スワップスペースもずっと少ないからです。ヒープサイズはデフォルトでは無限に設定されていますが、スタックサイズを縮小しても差はなく、私のulimitはすべてデーモンが使用しているものよりも無制限または大幅に高くなっています。

元のコードでは、html解析のどこかにクラッシュを突き止めましたが、20時間以上かかるため、より効率的なバージョンのメモリでも同じ処理を行っていません。プログラムの特定の部分がクラッシュする前に何十回も繰​​り返し実行されたために壊れているように見えないため、これが知るのに役立つかどうかはわかりません。

私はこのエラーのためにghc source codeを調べましたが、それは問題の根本ではないと私は考えていないので、非常に役に立たなかったmmapへの呼び出しに失敗したようです。

(編集:コードを書き換え、ポストの最後に移動)

私はHaskellのではかなり新しいので、私は、これは遅延評価やクイックフィックスを持っている何か他のいくつかのいたずらである願っています。そうでなければ、私はアイデアが新鮮です。

私は、FreeBSD 9.1

編集上のGHCのバージョン7.4.2を使用しています:私はどのように問題を処分した静的なHTMLでダウンロードを交換

ので、私はそれを絞り込むましたhttp-conduitを使用します。上記のコードを編集してネットワークコードを追加しました。私がそれをしたので、ハッカーのドキュメントはマネージャーを共有するために言及しています。また、httpについては明示的に接続を閉じる必要があると言いますが、私はhttpLbsのためにそれを行う必要はないと言います。

ここに私のコードです。

import Control.Monad.IO.Class (liftIO) 
import qualified Data.Text as T 
import qualified Data.ByteString.Lazy as BL 
import Text.Regex.PCRE 
import Network.HTTP.Conduit 

main :: IO() 
main = do 
    manager <- newManager def 
    daemonLoop manager 

daemonLoop :: Manager -> IO() 
daemonLoop manager = do 
    rows <- scrapeWebpage manager 
    putStrLn $ "number of rows parsed: " ++ (show $ length rows) 
    doSleep 
    daemonLoop manager 

scrapeWebpage :: Manager -> IO [[BL.ByteString]] 
scrapeWebpage manager = do 
    putStrLn "before makeRequest" 
    html <- makeRequest manager 
    -- Force evaluation of html. 
    putStrLn $ "html length: " ++ (show $ BL.length html) 
    putStrLn "after makeRequest" 
    -- Breaks ~10M html table into 2d list of bytestrings. 
    -- Max memory usage is about 45M, which is about 15M more than when sleeping. 
    return $ map tail $ html =~ pattern 
    where 
     pattern :: BL.ByteString 
     pattern = BL.concat $ replicate 12 "<td[^>]*>([^<]+)</td>\\s*" 

makeRequest :: Manager -> IO BL.ByteString 
makeRequest manager = runResourceT $ do 
    defReq <- parseUrl url 
    let request = urlEncodedBody params $ defReq 
        -- Don't throw errors for bad statuses. 
        { checkStatus = \_ _ -> Nothing 
        -- 1 minute. 
        , responseTimeout = Just 60000000 
        } 
    response <- httpLbs request manager 
    return $ responseBody response 

と、それは出力です:正規表現の計算退治

before makeRequest 
html length: 1555212 
after makeRequest 
number of rows parsed: 3608 
... 
before makeRequest 
html length: 1555212 
after makeRequest 
bannerstalkerd: out of memory (requested 2097152 bytes) 

は、問題を修正しましたが、エラーがネットワーキング後と正規表現の間に起こると思われ、おそらく私は何かをhttp-conduitで間違っています。何か案は?

Could not find module `Network.HTTP.Conduit' 
Perhaps you haven't installed the profiling libraries for package `http-conduit-1.8.9'? 

は確かに、私はhttp-conduitのためのライブラリをプロファイリングインストールしていないと私は方法がわからない:。また

、私はプロファイリングでコンパイルしようと、私はこのエラーを取得する有効

+0

DB全体をレイジーテキストファイルに置き換えて、実際にdbであるかどうか確認できますか? –

+0

私は実際にデータベース全体を削除しましたが、それでも同じ問題があります。私はそれを反映するために投稿を編集します。 – nejstastnejsistene

+2

ダウンロード部分を 'let page =" " –

答えて

3

私は自分自身の問題を解決しました。これはFreeBSDのGHCバグのようです。私はバグレポートを提出し、Linuxに切り替えました。そして、ここ数日は完璧に動いています。

4

あなた自身が漏れていることがわかりました。コンパイラのオプションとメモリ設定を騙してしまうと、プログラムがクラッシュした瞬間を延期することしかできませんが、問題の原因を取り除くことはできないため、そこに何を設定しても、最終的にはメモリ不足になります。

すべての非純粋なコードを慎重に歩き、リソースを扱っているパーツをプライマリにすることをお勧めします。すべてのリソースが正しく解放されるかどうかを確認してください。 uboundedチャンネルのように、蓄積状態になっているかどうかを確認してください。そして、もちろん、n.m.によって示唆されたように、profile it

私は、ファイルを一時停止したりダウンロードしたりせずにページを解析して、すべて同時に処理するスクレーパーを持っています。私は〜60M以上のメモリを使用してそれを見たことがない。私はGHC 7.4.2、GHC 7.6.1、GHC 7.6.2でコンパイルしていますが、どちらにも問題はありませんでした。

問題の根本は、使用しているライブラリにある可能性があります。 私のスクレーパーでは、http-conduithttp-conduit-browserHandsomeSoupおよびHXTを使用します。

+0

これは正しいトラックのようです。私はwebscrapingのために 'http-conduit'と' regex-pcre'を使用しています。私は使用しているすべてのコードを編集しました。私のプログラムは、約45M以上を使用することはありませんが、何らかの理由でまだ死んでいます。私の 'http-conduit'コードはかなり素朴な骨です。私はどこでリソースを誤って処理するのか分かりません。 – nejstastnejsistene

+0

@北日本コードを共有することは可能ですか?私はHaskellとのWebスクレイピングもやっており、あなたのコードから学びたいと思っています。 – osager

+0

@osager申し訳ありません。それは私的なプロジェクトです。しかしそこには多くのチュートリアルがあります。例:[this](http://egonschiele.github.io/HandsomeSoup/)と[this](http://adit.io/posts/2012-03-10-building_a_concurrent_web_scraper_with_haskell.html)。 –