Question Pourquoi Haskell ne gère-t-il pas les caractères d'un site Web spécifique?


Je me demandais si je pouvais écrire un programme Haskell pour vérifier les mises à jour de certains romans à la demande, et le site Web que j'utilise comme exemple est ce. Et j'ai eu un problème en affichant son contenu (sur un mac el capitan). Les codes simples suivent:

import Network.HTTP

openURL :: String -> IO String
openURL = (>>= getResponseBody) . simpleHTTP . getRequest

display :: String -> IO ()
display = (>>= putStrLn) . openURL

Ensuite, quand je cours display "http://www.piaotian.net/html/7/7430/" sur ghci, des caractères étranges apparaissent; les premières lignes ressemblent à ceci:

<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">

J'ai également essayé de télécharger en fichier comme suit:

import Network.HTTP

openURL :: String -> IO String
openURL = (>>= getResponseBody) . simpleHTTP . getRequest

downloading :: String -> IO ()
downloading = (>>= writeFile fileName) . openURL

Mais après avoir téléchargé le fichier, c'est comme sur la photo: enter image description here

Si je télécharge la page en python (en utilisant urllib par exemple), les caractères sont affichés normalement. De plus, si j'écris un fichier HTML chinois et que je l’analyse, il ne semble pas y avoir de problème. Il semble donc que le problème soit sur le site Web. Cependant, je ne vois aucune différence entre les personnages du site et ceux que j'écris.

Toute aide sur la raison derrière cela est bien appréciée.

P.S.
Le code python est le suivant:

import urllib

urllib.urlretrieve('http://www.piaotian.net/html/7/7430/', theFic)

theFic = file_path

Et le fichier est très bien et bien.


15
2017-08-01 14:08


origine


Réponses:


Puisque vous avez dit que seuls les liens vous intéressaient, vous n'avez pas besoin de convertir le codage GBK en Unicode.

Voici une version qui imprime tous les liens comme "123456.html" dans le document:

#!/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

1
2017-08-01 17:05



Je suis sûr que si vous utilisez Network.HTTP avec le String type, il convertit les octets en caractères en utilisant le codage de votre système, ce qui est généralement faux.

Ce n'est qu'une des raisons pour lesquelles je n'aime pas Network.HTTP.

Vos options:

  1. Utilisez le Bytestring interface. C'est plus gênant pour une raison quelconque. Il vous faudra également décoder les octets en caractères manuellement. La plupart des sites vous donnent un encodage dans les en-têtes de réponse, mais parfois ils se trouvent. C'est un gâchis géant, vraiment.

  2. Utilisez une autre bibliothèque de récupération http. Je ne pense pas que cela supprime le désordre de traiter avec les encodages menteurs, mais au moins ils ne rendent pas plus difficile de ne pas utiliser correctement le codage du système. Je regarder dans wreq ou client http au lieu.


8
2017-08-01 14:30



Voici une réponse mise à jour qui utilise le encoding paquet convertir le contenu encodé en GBK en 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

6
2017-08-01 16:44



Si vous souhaitez simplement télécharger le fichier, par exemple pour regarder plus tard, il vous suffit d'utiliser l'interface ByteString. Il serait préférable d'utiliser http-client pour cela (ou wreq si vous avez des connaissances en lentilles). Ensuite, vous pouvez l'ouvrir dans votre navigateur, qui verra qu'il s'agit d'un fichier gbk. Jusqu'ici, vous ne feriez que transférer les octets bruts sous forme de bytestring paresseux. Si je le comprends, c'est tout ce que fait Python. Les encodages ne sont pas un problème ici; le navigateur les gère.

Mais si vous voulez voir les caractères à l'intérieur de ghci, par exemple, le problème principal est que rien ne gèrera l'encodage gbk par défaut comme le fait le navigateur. Pour cela vous avez besoin de quelque chose comme text-icu et les bibliothèques C sous-jacentes. Le programme ci-dessous utilise le http-client bibliothèque avec text-icu - ce sont je pense assez standard pour ce problème, même si vous pouvez utiliser le moins puissant encodingbibliothèque pour autant du problème que nous l’avons vu jusqu’à présent. Cela semble fonctionner correctement:

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

Ici txt est un Text valeur, c’est-à-dire simplement les «points de code». Le dernier bit T.putStrLn txt utilisera l'encodage du système pour vous présenter le texte. Vous pouvez également gérer explicitement l'encodage avec les fonctions de Data.Text.Encoding ou le matériel plus sophistiqué dans text-icu Par exemple, si vous souhaitez enregistrer le texte dans l’encodage utf8, vous utiliserez T.encodeUtf8 

Donc, dans mon ghci, la sortie ressemble donc à

<!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" />

...

Est-ce que ça a l'air bien? Dans mon ghci, ce que je vois passe via utf8, puisque c’est mon encodage système, mais notez bien sûr que le fichier est un fichier gbk. Si, alors, vous vouliez en faire Text transformation puis enregistrez-le sous la forme d'un fichier html - bien sûr, vous devrez vous assurer que le jeu de caractères mentionné dans le fichier correspond à l'encodage utilisé pour écrire le bytestring dans un fichier.

Vous pourriez aussi bien sûr prendre la forme d'un Haskell String en remplaçant les trois dernières lignes par

let str :: String
    str = T.unpack $ ICU.toUnicode gbk $ BL.toStrict $ responseBody response
putStrLn str

6
2017-08-01 16:19



Ce programme produit le même résultat que la commande curl:

curl "http://www.piaotian.net/html/7/7430/"

Test avec:

stack program > out.html
open out.html

(Si vous n'utilisez pas stack, installez simplement le wreq et lens paquets et exécuter avec 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)

1
2017-08-01 15:05