Почему Haskell не обрабатывает персонажей с определенного сайта?

Мне было интересно, могу ли я написать программу Haskell для проверки обновлений некоторых романов по запросу, а веб-сайт, который я использую в качестве примера, - this. И у меня возникла проблема при отображении его содержимого (на mac el capitan). Простые коды:

import Network.HTTP

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

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

Затем, когда я запускаю display "http://www.piaotian.net/html/7/7430/" на ghci, появляются некоторые странные символы; первые строки выглядят так:

<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

И файл все в порядке и хорошо.

Ответы

Ответ 1

Поскольку вы сказали, что вас интересуют только ссылки, нет необходимости конвертировать кодировку 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

Ответ 2

Я уверен, что если вы используете Network.HTTP с типом String, он преобразует байты в символы, используя вашу системную кодировку, которая, как правило, неверна.

Это только одна из нескольких причин, по которым мне не нравится Network.HTTP.

Ваши варианты:

  • Используйте интерфейс Bytestring. Почему-то это неудобно. Это также потребует, чтобы вы вручную декодировали байты в символы. Большинство сайтов дают вам кодировку в заголовках ответов, но иногда они лгут. На самом деле это гигантский беспорядок.

  • Используйте другую библиотеку для извлечения http. Я не думаю, что кто-то удаляет беспорядок, имея дело с ложными кодировками, но они по крайней мере не затрудняют неправильное использование системного кодирования. Вместо этого я рассмотрю wreq или http-client.

Ответ 3

Если вы просто хотите загрузить файл, например. чтобы посмотреть позже, вам просто нужно использовать интерфейс 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, т.е. в основном просто "кодовые точки". Последний бит T.putStrLn txt будет использовать системную кодировку для предоставления вам текста. Вы также можете явно обрабатывать кодировку с функциями в Data.Text.Encoding или более сложным материалом в text-icu Например, если вы хотите сохранить текст в кодировке 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 файл - тогда, конечно, вам нужно убедиться, что кодировка, указанная внутри файла, соответствует кодировке, которую вы использовали, чтобы сделать bytestring, которую вы пишете, файл.

Вы также можете получить это в форме Haskell String, заменив последние три строки на

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

Ответ 4

Вот обновленный ответ, в котором используется пакет 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

Ответ 5

Эта программа производит тот же вывод, что и команда 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)