为什么 Haskell 无法处理来自特定网站的字符?
Why can Haskell not handle characters from a specific website?
我在想能不能写一个Haskell程序来点播一些小说的更新,我举的网站是this。我在显示它的内容时遇到了问题(在 mac el capitan 上)。简单代码如下:
import Network.HTTP
openURL :: String -> IO String
openURL = (>>= getResponseBody) . simpleHTTP . getRequest
display :: String -> IO ()
display = (>>= putStrLn) . openURL
然后我在ghci上运行display "http://www.piaotian.net/html/7/7430/"
的时候出现了一些奇怪的字符;第一行看起来像这样:
<title>×ß½øÐÞÏÉ×îÐÂÕ½Ú,×ß½øÐÞÏÉÎÞµ¯´°È«ÎÄÔĶÁ_Æ®ÌìÎÄѧ</title>
<meta http-equiv="Content-Type" content="text/html; charset=gbk" />
<meta name="keywords" content="×ß½øÐÞÏÉ,×ß½øÐÞÏÉ×îÐÂÕ½Ú,×ß½øÐÞÏÉÎÞµ¯´° Æ®ÌìÎÄѧ" />
<meta name="description" content="Æ®ÌìÎÄѧÍøÌṩ×ß½øÐÞÏÉ×îÐÂÕ½ÚÃâ·ÑÔĶÁ£¬Ç뽫×ß½øÐÞÏÉÕ½ÚĿ¼¼ÓÈëÊղط½±ãÏ´ÎÔĶÁ,Æ®ÌìÎÄѧС˵ÔĶÁÍø¾¡Á¦ÔÚµÚһʱ¼ä¸üÐÂС˵×ß½øÐÞÏÉ£¬Èç·¢ÏÖδ¼°Ê±¸üУ¬ÇëÁªÏµÎÒÃÇ¡£" />
<meta name="copyright" content="×ß½øÐÞÏÉ°æȨÊôÓÚ×÷ÕßÎáµÀ³¤²»¹Â" />
<meta name="author" content="ÎáµÀ³¤²»¹Â" />
<link rel="stylesheet" href="/scripts/read/list.css" type="text/css" media="all" />
<script type="text/javascript">
我也试过下载成文件如下:
import Network.HTTP
openURL :: String -> IO String
openURL = (>>= getResponseBody) . simpleHTTP . getRequest
downloading :: String -> IO ()
downloading = (>>= writeFile fileName) . openURL
但是下载文件后,就像照片中一样:
如果我通过 python 下载页面(例如使用 urllib),字符显示正常。还有,如果我写一个中文html然后解析,那好像就没有问题了。因此,问题似乎出在网站上。但是,我看不出网站上的字符和我写的有什么区别。
非常感谢任何有关这背后原因的帮助。
P.S.
python代码如下:
import urllib
urllib.urlretrieve('http://www.piaotian.net/html/7/7430/', theFic)
theFic = file_path
文件一切正常。
我很确定,如果您将 Network.HTTP
与 String
类型一起使用,它会使用您的系统编码将字节转换为字符,这通常是错误的。
这只是我不喜欢的几个原因之一 Network.HTTP
。
您的选择:
使用Bytestring
界面。由于某种原因,它更尴尬。它还需要您手动将字节解码为字符。大多数站点都会在响应 headers 中为您提供编码,但有时它们会撒谎。真是一团糟。
使用不同的 http 抓取库。我不认为任何消除处理说谎编码的混乱,但它们至少不会让不正确使用系统编码变得更加尴尬。我会调查 wreq or http-client。
此程序产生与 curl 命令相同的输出:
curl "http://www.piaotian.net/html/7/7430/"
测试:
stack program > out.html
open out.html
(如果不使用 stack
,只需安装 wreq
和 lens
软件包并使用 runhaskell
执行。)
#!/usr/bin/env stack
-- stack --resolver lts-6.0 --install-ghc runghc --package wreq --package lens --package bytestring
{-# LANGUAGE OverloadedStrings #-}
import Network.Wreq
import qualified Data.ByteString.Lazy.Char8 as LBS
import Control.Lens
main = do
r <- get "http://www.piaotian.net/html/7/7430/"
LBS.putStr (r ^. responseBody)
如果您只想下载文件,例如稍后再看,你只需要使用 ByteString 接口。最好为此使用 http-client
(如果您有一些镜头知识,则使用 wreq
)。然后你可以在浏览器中打开它,它将看到它是一个 gbk 文件。到目前为止,您只是将原始字节作为惰性字节串传输。如果我理解它,那就是 python 所做的一切。编码在这里不是问题;浏览器正在处理它们。
但是如果你想查看 ghci 中的字符,例如,主要问题是默认情况下没有任何东西可以像浏览器那样处理 gbk 编码。为此,您需要 text-icu
之类的东西和底层 C 库。下面的程序使用 http-client
库和 text-icu
- 我认为这些是解决这个问题的标准,尽管你可以使用功能较弱的 encoding
库来解决尽可能多的问题到目前为止我们已经看到了。它似乎工作正常:
import Network.HTTP.Client -- http-client
import Network.HTTP.Types.Status (statusCode)
import qualified Data.Text.Encoding as T -- text
import qualified Data.Text.IO as T
import qualified Data.Text as T
import qualified Data.Text.ICU.Convert as ICU -- text-icu
import qualified Data.Text.ICU as ICU
import qualified Data.ByteString.Lazy as BL
main :: IO ()
main = do
manager <- newManager defaultManagerSettings
request <- parseRequest "http://www.piaotian.net/html/7/7430/"
response <- httpLbs request manager
gbk <- ICU.open "gbk" Nothing
let txt :: T.Text
txt = ICU.toUnicode gbk $ BL.toStrict $ responseBody response
T.putStrLn txt
这里 txt
是一个 Text
值,即基本上就是 'code points'。最后一位 T.putStrLn txt
将使用系统编码将文本呈现给您。您还可以使用 Data.Text.Encoding
中的函数或 text-icu
中更复杂的 material 中的函数显式处理编码。例如,如果您想以 utf8 编码保存文本,您可以使用 T.encodeUtf8
所以在我的 ghci 中输出看起来像这样
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>走进修仙最新章节,走进修仙无弹窗全文阅读_飘天文学</title>
<meta http-equiv="Content-Type" content="text/html; charset=gbk" />
...
这样看起来对吗?在我的 ghci 中,我看到的是通过 utf8 传输的,因为这是我的系统编码,但请注意,该文件当然是说它是一个 gbk 文件。那么,如果您想进行一些 Text
转换,然后将其保存为 html 文件——那么您当然需要确保文件中提到的字符集与您使用的编码相匹配将您写入文件的字节串。
你当然也可以通过将最后三行替换为
来将其变成 Haskell String
的形状
let str :: String
str = T.unpack $ ICU.toUnicode gbk $ BL.toStrict $ responseBody response
putStrLn str
这是使用 encoding
包的更新答案
将 GBK 编码的内容转换为 Unicode。
#!/usr/bin/env stack
{- stack
--resolver lts-6.0 --install-ghc runghc
--package wreq --package lens --package encoding --package binary
-}
{-# LANGUAGE OverloadedStrings #-}
import Network.Wreq
import qualified Data.ByteString.Lazy.Char8 as LBS
import Control.Lens
import qualified Data.Encoding as E
import qualified Data.Encoding.GB18030 as E
import Data.Binary.Get
main = do
r <- get "http://www.piaotian.net/html/7/7430/"
let body = r ^. responseBody :: LBS.ByteString
foo = runGet (E.decode E.GB18030) body
putStrLn foo
既然你说只对链接感兴趣,那么就没有必要把GBK编码转成Unicode了。
这是一个打印出文档中所有链接如“123456.html”的版本:
#!/usr/bin/env stack
{- stack
--resolver lts-6.0 --install-ghc runghc
--package wreq --package lens
--package tagsoup
-}
{-# LANGUAGE OverloadedStrings #-}
import Network.Wreq
import qualified Data.ByteString.Lazy.Char8 as LBS
import Control.Lens
import Text.HTML.TagSoup
import Data.Char
import Control.Monad
-- match \d+\.html
isNumberHtml lbs = (LBS.dropWhile isDigit lbs) == ".html"
wanted t = isTagOpenName "a" t && isNumberHtml (fromAttrib "href" t)
main = do
r <- get "http://www.piaotian.net/html/7/7430/"
let body = r ^. responseBody :: LBS.ByteString
tags = parseTags body
links = filter wanted tags
hrefs = map (fromAttrib "href") links
forM_ hrefs LBS.putStrLn
我在想能不能写一个Haskell程序来点播一些小说的更新,我举的网站是this。我在显示它的内容时遇到了问题(在 mac el capitan 上)。简单代码如下:
import Network.HTTP
openURL :: String -> IO String
openURL = (>>= getResponseBody) . simpleHTTP . getRequest
display :: String -> IO ()
display = (>>= putStrLn) . openURL
然后我在ghci上运行display "http://www.piaotian.net/html/7/7430/"
的时候出现了一些奇怪的字符;第一行看起来像这样:
<title>×ß½øÐÞÏÉ×îÐÂÕ½Ú,×ß½øÐÞÏÉÎÞµ¯´°È«ÎÄÔĶÁ_Æ®ÌìÎÄѧ</title>
<meta http-equiv="Content-Type" content="text/html; charset=gbk" />
<meta name="keywords" content="×ß½øÐÞÏÉ,×ß½øÐÞÏÉ×îÐÂÕ½Ú,×ß½øÐÞÏÉÎÞµ¯´° Æ®ÌìÎÄѧ" />
<meta name="description" content="Æ®ÌìÎÄѧÍøÌṩ×ß½øÐÞÏÉ×îÐÂÕ½ÚÃâ·ÑÔĶÁ£¬Ç뽫×ß½øÐÞÏÉÕ½ÚĿ¼¼ÓÈëÊղط½±ãÏ´ÎÔĶÁ,Æ®ÌìÎÄѧС˵ÔĶÁÍø¾¡Á¦ÔÚµÚһʱ¼ä¸üÐÂС˵×ß½øÐÞÏÉ£¬Èç·¢ÏÖδ¼°Ê±¸üУ¬ÇëÁªÏµÎÒÃÇ¡£" />
<meta name="copyright" content="×ß½øÐÞÏÉ°æȨÊôÓÚ×÷ÕßÎáµÀ³¤²»¹Â" />
<meta name="author" content="ÎáµÀ³¤²»¹Â" />
<link rel="stylesheet" href="/scripts/read/list.css" type="text/css" media="all" />
<script type="text/javascript">
我也试过下载成文件如下:
import Network.HTTP
openURL :: String -> IO String
openURL = (>>= getResponseBody) . simpleHTTP . getRequest
downloading :: String -> IO ()
downloading = (>>= writeFile fileName) . openURL
但是下载文件后,就像照片中一样:
如果我通过 python 下载页面(例如使用 urllib),字符显示正常。还有,如果我写一个中文html然后解析,那好像就没有问题了。因此,问题似乎出在网站上。但是,我看不出网站上的字符和我写的有什么区别。
非常感谢任何有关这背后原因的帮助。
P.S.
python代码如下:
import urllib
urllib.urlretrieve('http://www.piaotian.net/html/7/7430/', theFic)
theFic = file_path
文件一切正常。
我很确定,如果您将 Network.HTTP
与 String
类型一起使用,它会使用您的系统编码将字节转换为字符,这通常是错误的。
这只是我不喜欢的几个原因之一 Network.HTTP
。
您的选择:
使用
Bytestring
界面。由于某种原因,它更尴尬。它还需要您手动将字节解码为字符。大多数站点都会在响应 headers 中为您提供编码,但有时它们会撒谎。真是一团糟。使用不同的 http 抓取库。我不认为任何消除处理说谎编码的混乱,但它们至少不会让不正确使用系统编码变得更加尴尬。我会调查 wreq or http-client。
此程序产生与 curl 命令相同的输出:
curl "http://www.piaotian.net/html/7/7430/"
测试:
stack program > out.html
open out.html
(如果不使用 stack
,只需安装 wreq
和 lens
软件包并使用 runhaskell
执行。)
#!/usr/bin/env stack
-- stack --resolver lts-6.0 --install-ghc runghc --package wreq --package lens --package bytestring
{-# LANGUAGE OverloadedStrings #-}
import Network.Wreq
import qualified Data.ByteString.Lazy.Char8 as LBS
import Control.Lens
main = do
r <- get "http://www.piaotian.net/html/7/7430/"
LBS.putStr (r ^. responseBody)
如果您只想下载文件,例如稍后再看,你只需要使用 ByteString 接口。最好为此使用 http-client
(如果您有一些镜头知识,则使用 wreq
)。然后你可以在浏览器中打开它,它将看到它是一个 gbk 文件。到目前为止,您只是将原始字节作为惰性字节串传输。如果我理解它,那就是 python 所做的一切。编码在这里不是问题;浏览器正在处理它们。
但是如果你想查看 ghci 中的字符,例如,主要问题是默认情况下没有任何东西可以像浏览器那样处理 gbk 编码。为此,您需要 text-icu
之类的东西和底层 C 库。下面的程序使用 http-client
库和 text-icu
- 我认为这些是解决这个问题的标准,尽管你可以使用功能较弱的 encoding
库来解决尽可能多的问题到目前为止我们已经看到了。它似乎工作正常:
import Network.HTTP.Client -- http-client
import Network.HTTP.Types.Status (statusCode)
import qualified Data.Text.Encoding as T -- text
import qualified Data.Text.IO as T
import qualified Data.Text as T
import qualified Data.Text.ICU.Convert as ICU -- text-icu
import qualified Data.Text.ICU as ICU
import qualified Data.ByteString.Lazy as BL
main :: IO ()
main = do
manager <- newManager defaultManagerSettings
request <- parseRequest "http://www.piaotian.net/html/7/7430/"
response <- httpLbs request manager
gbk <- ICU.open "gbk" Nothing
let txt :: T.Text
txt = ICU.toUnicode gbk $ BL.toStrict $ responseBody response
T.putStrLn txt
这里 txt
是一个 Text
值,即基本上就是 'code points'。最后一位 T.putStrLn txt
将使用系统编码将文本呈现给您。您还可以使用 Data.Text.Encoding
中的函数或 text-icu
中更复杂的 material 中的函数显式处理编码。例如,如果您想以 utf8 编码保存文本,您可以使用 T.encodeUtf8
所以在我的 ghci 中输出看起来像这样
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>走进修仙最新章节,走进修仙无弹窗全文阅读_飘天文学</title>
<meta http-equiv="Content-Type" content="text/html; charset=gbk" />
...
这样看起来对吗?在我的 ghci 中,我看到的是通过 utf8 传输的,因为这是我的系统编码,但请注意,该文件当然是说它是一个 gbk 文件。那么,如果您想进行一些 Text
转换,然后将其保存为 html 文件——那么您当然需要确保文件中提到的字符集与您使用的编码相匹配将您写入文件的字节串。
你当然也可以通过将最后三行替换为
来将其变成 HaskellString
的形状
let str :: String
str = T.unpack $ ICU.toUnicode gbk $ BL.toStrict $ responseBody response
putStrLn str
这是使用 encoding
包的更新答案
将 GBK 编码的内容转换为 Unicode。
#!/usr/bin/env stack
{- stack
--resolver lts-6.0 --install-ghc runghc
--package wreq --package lens --package encoding --package binary
-}
{-# LANGUAGE OverloadedStrings #-}
import Network.Wreq
import qualified Data.ByteString.Lazy.Char8 as LBS
import Control.Lens
import qualified Data.Encoding as E
import qualified Data.Encoding.GB18030 as E
import Data.Binary.Get
main = do
r <- get "http://www.piaotian.net/html/7/7430/"
let body = r ^. responseBody :: LBS.ByteString
foo = runGet (E.decode E.GB18030) body
putStrLn foo
既然你说只对链接感兴趣,那么就没有必要把GBK编码转成Unicode了。
这是一个打印出文档中所有链接如“123456.html”的版本:
#!/usr/bin/env stack
{- stack
--resolver lts-6.0 --install-ghc runghc
--package wreq --package lens
--package tagsoup
-}
{-# LANGUAGE OverloadedStrings #-}
import Network.Wreq
import qualified Data.ByteString.Lazy.Char8 as LBS
import Control.Lens
import Text.HTML.TagSoup
import Data.Char
import Control.Monad
-- match \d+\.html
isNumberHtml lbs = (LBS.dropWhile isDigit lbs) == ".html"
wanted t = isTagOpenName "a" t && isNumberHtml (fromAttrib "href" t)
main = do
r <- get "http://www.piaotian.net/html/7/7430/"
let body = r ^. responseBody :: LBS.ByteString
tags = parseTags body
links = filter wanted tags
hrefs = map (fromAttrib "href") links
forM_ hrefs LBS.putStrLn