使用 Haskell Conduits 提取 Open Library 作者名

几乎所有高效使用 Haskell 的人都曾遇到过 Michael Snoyman 的 Conduit 库。

在这篇文章中,我将展示如何利用 Conduit 交错 IO 的力量从 Open Library 数据转储解析作者名。

使用 Java 或 C 等传统语言,交错以下处理管道操作将明显更困难且容易出错:

因为解压缩的转储是多 GiB 文件,所以需要流式解析器。在这个简单示例中,使用管道或 FIFO 从 stdin 下载和解析当然是可能的,然而

虽然其他语言有促进原生交错 IO 的库,但集成 gunzip 等第三方功能证明相当困难,特别是如果试图避免性能瓶颈。当关注性能时,你经常被困在手动操作内存缓冲区,产生复杂、容易出错的代码。

Conduits 为这些问题提供了即用型解决方案。使用 conduit-extras 等库,还可以轻松集成流式 gunzip,同时仍满足近乎恒定的内存要求和确定性资源使用。

在保持高性能的同时,它们允许程序员不必考虑将处理管道的各个步骤拼接在一起的具体细节。

parse_open_library_authors.hs
{-# LANGUAGE OverloadedStrings #-}
{-|

流式解析 open library 作者转储并提取作者名列表的程序。

(C) 2014 Uli Koehler

在 Apache License v2.0 下发布

-}

import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Data.Conduit.Zlib (ungzip)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as LB
import Network.HTTP.Conduit (parseUrl, withManager, http, responseBody)
import Data.Conduit
import qualified Data.Conduit.Combinators as CC
import qualified Data.Conduit.List as CL
import qualified Data.Conduit.Binary as CB
import Control.Applicative
import Data.Char
import Data.Aeson
import Data.Maybe
import Control.Monad

data Author = Author {authorKey :: Text, -- ^ Open library 作者键,例如 /authors/OL5900296A
                      authorName :: Text -- ^ 作者人名
                      } deriving (Show, Eq)

instance FromJSON Author where
    parseJSON (Object v) = Author <$>
                            fmap T.pack (v .: "key") <*>
                            fmap T.pack (v .: "name")
    parseJSON _ = mzero

main :: IO ()
main = do
    -- 定义我们的 conduit 处理链:
    --  - 跳过无法解析的行
    --  - 也跳过每行中 JSON 开始之前的所有内容
    let parseConduit = CL.mapMaybe (decode . LB.fromStrict .  B.dropWhile ('{' /=))
    let showAuthor = CL.map (TE.encodeUtf8 . authorName)
    let processingConduit = ungzip =$= CB.lines =$= parseConduit =$= showAuthor =$= CC.unlinesAscii
    -- 初始化 HTTP 请求
    request <- parseUrl "http://openlibrary.org/data/ol_dump_authors_latest.txt.gz"
    withManager $ \manager -> do
        response <- http request manager
        -- 使用交错 IO 增量获取和处理
        responseBody response $$+- processingConduit =$ CB.sinkFile "authors.txt"

Check out similar posts by category: Haskell