You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Haskell中使用wreq解析维基百科API的JSON数据?

Haskell解析维基百科Mediawiki API JSON搜索结果的问题

我正在用Haskell编写程序,通过wreq模块调用维基百科的Mediawiki Action API获取JSON格式的搜索结果,但在解析JSON时遇到了困难。

我的函数代码如下:

requestWikiSearch :: Text -> IO (???)
requestWikiSearch searchQuery = do
    let urlParams = [ ("action", "query"), ("format", "json"), ("list", "search") ]
    let opts = defaults & params .~ urlParams & param "srsearch" .~ [searchQuery]
    r <- getWith opts wikipediaApiUrl
    return $ r ^. responseBody . key "query" . key "search" ....???

API返回的responseBody JSON示例(搜索"Aerosmith"的结果):

{
    "batchcomplete": true,
    "continue": {
        "sroffset": 10,
        "continue": "-||"
    },
    "query": {
        "searchinfo": {
            "totalhits": 3254
        },
        "search": [
            {
                "ns": 0,
                "title": "Aerosmith",
                "pageid": 149216,
                "size": 190407,
                "wordcount": 19118,
                "snippet": "&lt;span class=\"searchmatch\"&gt;Aerosmith&lt;/span&gt; is an American rock band formed in Boston in 1970. ... ",
                "timestamp": "2025-05-07T14:23:27Z"
            },
            {
                "ns": 0,
                "title": "Dream On (Aerosmith song)",
                "pageid": 2761666,
                "size": 22786,
                "wordcount": 1972,
                "snippet": "&amp;quot;Dream On&amp;quot; is a power ballad by American rock band &lt;span class=\"searchmatch\"&gt;Aerosmith&lt;/span&gt;, from their 1973 eponymous debut album...",
                "timestamp": "2025-05-12T19:32:05Z"
            },
            ...
        ]
    }
}

我需要提取query.search对应的Haskell列表,进而遍历获取page ID、标题等信息,但尝试Lens和Aeson相关操作时遇到了几个问题:

  1. 如何遍历通过r ^. responseBody . key "query" . key "search"获取到的"search"列表?
  2. 尝试用Data.Vector和Data.Aeson.KeyMap获取数组第一个元素后,无法从Data.Aeson.Types.Internal.Value类型的Number中提取数值:
    vector = r ^. responseBody . key "query" . key "search" . _Array
    KeyMap.toList $ Vector.head vector ^. _Object
    
  3. 导入Data.Aeson.Lens (_Number, nth)后尝试以下代码,GHC repl报错No instance for ‘Monoid Integer’ arising from a use of ‘key’:
    r ^. responseBody . key "query" . key "search" . nth 0 . key "size" . _Number
    

问题

  • 如何让_Number(或_Integer、_Integral)正常工作?
  • 更通用地说,使用Haskell的Aeson/Lens/Vector/KeyMap等工具解析该JSON的最佳方案是什么?

解决方案

1. 解决_Number相关的报错问题

你遇到的Monoid Integer报错,本质是key操作默认返回带Monoid约束的折叠结果,和_Number的类型推导冲突。最安全的解决方式是用^?(预览操作符,返回Maybe类型)替代^.,因为JSON字段可能不存在:

-- 提取单个字段的Double类型值,返回Maybe Double
r ^? responseBody . key "query" . key "search" . nth 0 . key "size" . _Number

-- 如果需要整数类型,用_Integral
r ^? responseBody . key "query" . key "search" . nth 0 . key "pageid" . _Integral

如果确定字段一定存在,也可以结合fromJust(不推荐,JSON结构可能变更)和类型注解:

import Data.Maybe (fromJust)

pageId :: Integer
pageId = fromJust $ r ^? responseBody . key "query" . key "search" . nth 0 . key "pageid" . _Integral

2. 解析整个search列表的最佳方案

最稳健、可读性最强的方式是定义对应JSON结构的Haskell数据类型,自动派生Aeson解析实例,再结合wreq的asJSON直接解析响应:

第一步:定义数据类型并派生解析实例
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedStrings #-}

import GHC.Generics
import Data.Aeson
import Data.Text (Text)

-- 单个搜索结果的结构
data WikiSearchResult = WikiSearchResult
  { ns :: Int
  , title :: Text
  , pageid :: Integer
  , size :: Integer
  , wordcount :: Integer
  , snippet :: Text
  , timestamp :: Text
  } deriving (Show, Generic, FromJSON)

-- 外层包含query的结构
data WikiSearchResponse = WikiSearchResponse
  { query :: Query
  } deriving (Show, Generic, FromJSON)

-- 包含search列表的query结构
data Query = Query
  { search :: [WikiSearchResult]
  } deriving (Show, Generic, FromJSON)
第二步:修改函数解析整个响应
import Network.Wreq
import Data.Text (Text)

wikipediaApiUrl :: String
wikipediaApiUrl = "https://en.wikipedia.org/w/api.php"

requestWikiSearch :: Text -> IO (Either String [WikiSearchResult])
requestWikiSearch searchQuery = do
  let urlParams = [ ("action", "query"), ("format", "json"), ("list", "search") ]
      opts = defaults & params .~ urlParams & param "srsearch" .~ [searchQuery]
  response <- getWith opts wikipediaApiUrl
  case asJSON response of
    Left err -> return $ Left $ show err
    Right (respBody :: WikiSearchResponse) -> return $ Right $ search $ query respBody

调用示例:

main :: IO ()
main = do
  result <- requestWikiSearch "Aerosmith"
  case result of
    Left err -> putStrLn $ "Error: " ++ err
    Right results -> mapM_ (\res -> putStrLn $ "Title: " ++ show (title res) ++ ", PageID: " ++ show (pageid res)) results

3. 不定义数据类型的Lens直接解析方案

如果不想定义数据类型,可以用Lens的traverse遍历整个数组,提取目标字段:

import Control.Lens
import Data.Aeson.Lens
import Data.Text (Text)
import Data.Vector (Vector)
import Data.Maybe (fromJust)

-- 提取所有搜索结果的(pageid, title)列表
extractSearchResults :: Response Value -> [(Integer, Text)]
extractSearchResults r =
  let searchVector = r ^. responseBody . key "query" . key "search" . _Array
  in searchVector ^.. traverse . to (\val -> ( fromJust $ val ^? key "pageid" . _Integral
                                            , fromJust $ val ^? key "title" . _String
                                            ))

这种方式需要手动处理字段缺失的情况,不如定义数据类型安全。


内容的提问来源于stack exchange,提问作者tonebender

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 03:34:52