使用 Haskell,我如何处理大量的 XML?
Posted
技术标签:
【中文标题】使用 Haskell,我如何处理大量的 XML?【英文标题】:With Haskell, how do I process large volumes of XML? 【发布时间】:2011-01-18 13:28:46 【问题描述】:我一直在探索Stack Overflow data dumps,到目前为止,我一直在利用友好的 XML 和正则表达式“解析”。我尝试使用各种 Haskell XML 库来查找特定用户按文档顺序发布的第一篇文章都遇到了令人讨厌的颠簸。
TagSoup
import Control.Monad
import Text.html.TagSoup
userid = "83805"
main = do
posts <- liftM parseTags (readFile "posts.xml")
print $ head $ map (fromAttrib "Id") $
filter (~== ("<row OwnerUserId=" ++ userid ++ ">"))
posts
hxt
import Text.XML.HXT.Arrow
import Text.XML.HXT.XPath
userid = "83805"
main = do
runX $ readDoc "posts.xml" >>> posts >>> arr head
where
readDoc = readDocument [ (a_tagsoup, v_1)
, (a_parse_xml, v_1)
, (a_remove_whitespace, v_1)
, (a_issue_warnings, v_0)
, (a_trace, v_1)
]
posts :: ArrowXml a => a XmlTree String
posts = getXPathTrees byUserId >>>
getAttrValue "Id"
where byUserId = "/posts/row/@OwnerUserId='" ++ userid ++ "'"
xml
import Control.Monad
import Control.Monad.Error
import Control.Monad.Trans.Maybe
import Data.Either
import Data.Maybe
import Text.XML.Light
userid = "83805"
main = do
[posts,votes] <- forM ["posts", "votes"] $
liftM parseXML . readFile . (++ ".xml")
let ps = elemNamed "posts" posts
putStrLn $ maybe "<not present>" show
$ filterElement (byUser userid) ps
elemNamed :: String -> [Content] -> Element
elemNamed name = head . filter ((==name).qName.elName) . onlyElems
byUser :: String -> Element -> Bool
byUser id e = maybe False (==id) (findAttr creator e)
where creator = QName "OwnerUserId" Nothing Nothing
我哪里做错了?使用 Haskell 处理大量 XML 文档的正确方法是什么?
【问题讨论】:
我想支持这个问题。我有一个必须扫描的类似问题(大型 xml 数据集)。我目前使用 HXT+Expat,因为这是唯一一个在呈现 14MB XML 文件时不会内存爆炸的 HXT 后端。但是解析整个文件还需要一分半钟的时间。 【参考方案1】:我注意到您在所有这些情况下都在执行 String IO。如果您希望有效地处理大量文本,则绝对必须使用 Data.Text 或 Data.Bytestring(.Lazy),因为 String == [Char],这对于非常大的平面文件来说是不合适的表示。
这意味着您需要使用支持字节串的 Haskell XML 库。几个 xml 库在这里:http://hackage.haskell.org/packages/archive/pkg-list.html#cat:xml
我不确定哪个支持字节串,但这是您正在寻找的条件。
【讨论】:
+1 for ByteStrings,那些(仍然)没有得到应有的爱。忘记 longString
s 的糟糕表现是一个非常容易的错误——不要因为简单就将它们留在列表中,伙计们!【参考方案2】:
下面是一个使用hexpat的例子:
-# LANGUAGE PatternGuards #-
module Main where
import Text.XML.Expat.SAX
import qualified Data.ByteString.Lazy as B
userid = "83805"
main :: IO ()
main = B.readFile "posts.xml" >>= print . earliest
where earliest :: B.ByteString -> SAXEvent String String
earliest = head . filter (ownedBy userid) . parse opts
opts = ParserOptions Nothing Nothing
ownedBy :: String -> SAXEvent String String -> Bool
ownedBy uid (StartElement "row" as)
| Just ouid <- lookup "OwnerUserId" as = ouid == uid
| otherwise = False
ownedBy _ _ = False
ownedBy
的定义有点笨拙。也许是一个视图模式:
-# LANGUAGE ViewPatterns #-
module Main where
import Text.XML.Expat.SAX
import qualified Data.ByteString.Lazy as B
userid = "83805"
main :: IO ()
main = B.readFile "posts.xml" >>= print . earliest
where earliest :: B.ByteString -> SAXEvent String String
earliest = head . filter (ownedBy userid) . parse opts
opts = ParserOptions Nothing Nothing
ownedBy :: String -> SAXEvent String String -> Bool
ownedBy uid (ownerUserId -> Just ouid) = uid == ouid
ownedBy _ _ = False
ownerUserId :: SAXEvent String String -> Maybe String
ownerUserId (StartElement "row" as) = lookup "OwnerUserId" as
ownerUserId _ = Nothing
【讨论】:
【参考方案3】:你可以试试我的fast-tagsoup 库。它是 tagsoup 的简单替代品,解析速度为 20-200MB/秒。
tagsoup 包的问题在于,即使您使用 Text 或 ByteString 接口,它也可以在内部使用 String。 fast-tagsoup 使用高性能低级解析与严格的 ByteStrings 一起工作,同时仍然返回惰性标签列表作为输出。
【讨论】:
【参考方案4】:我遇到了类似的问题(使用 HXT) - 我通过使用 Expat parser with HXT 避免了内存问题。在一个 5MB 的 XML 文件上,只需读取文档并打印它:峰值内存消耗从 2Gigs 下降到大约 180MB,并且执行时间要短得多(未测量)。
【讨论】:
【参考方案5】:TagSoup 通过其 Text.StringLike 类支持 ByteString。您的示例所需的唯一更改是调用 ByteString.Lazy 的 readFile
,并将 fromString
添加到 fromAttrib
:
import Text.StringLike
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Char8 as BSC
userid = "83805"
file = "blah//posts.xml"
main = do
posts <- liftM parseTags (BSL.readFile file)
print $ head $ map (fromAttrib (fromString "Id")) $
filter (~== ("<row OwnerUserId=" ++ userid ++ ">"))
posts
您的示例为我运行(4 gig RAM),耗时 6 分钟; ByteString 版本花了 10 分钟。
【讨论】:
【参考方案6】:也许您需要一个惰性 XML 解析器:您的使用看起来像是对输入的非常简单的扫描。 HaXml 有一个惰性解析器,尽管您必须通过导入正确的模块来明确要求它。
【讨论】:
以上是关于使用 Haskell,我如何处理大量的 XML?的主要内容,如果未能解决你的问题,请参考以下文章