在 R 中使用 rvest 抓取的西里尔文文本的编码问题

问题描述 投票:0回答:1

我正在尝试使用 R 中的 rvest 包抓取包含西里尔文字的俄罗斯媒体网页。

但是,对于某些页面(并非全部由于某种原因),我遇到了编码问题,文本在抓取后无法正确显示。我看到的不是预期的西里尔字符,而是乱码的文本输出,例如:

Ðлава ÐдеÑÑÑ Ð¿ п

以本页为例:

url <- "https://news-front.su/2022/08/29/glava-odessy-prosit-vlasti-strany-sest-za-stol-peregovorov-s-rossiej/"


页面标题 (

httr::headers(httr::HEAD(url))

) 和 html 脚本中的所有参数都告诉我编码应该是 UTF-8 并且页面是静态的。

最初,我的scraper没有指定编码(所以如果UTF-8抛出错误,我猜它应该使用UTF-16?!),导致上面的字符混乱。

在read_html()中指定编码:

我尝试直接在 read_html() 函数中指定编码:

text <- rvest::read_html(url, encoding = "UTF-8") %>% html_elements(".entry-title") %>% html_text2()
导致

read_xml.raw(raw,encoding=encoding,base_url=base_url,as_html=as_html,中的错误: 输入的不是正确的UTF-8,请指示编码! 字节:0xD0 0x27 0x20 0x2F [9]

我还尝试了其他编码,例如“windows-1251”和“UTF-16”(并循环了

stringi::stri_enc_list()

中的所有编码),但这并没有让我更接近解决问题。

事后字符串操作:

这是我能得到的最接近预期结果的结果(尽管仍然不完美,并且如果刮刀可以首先处理编码,那么理想情况下没有必要)。

我直接在 MariaDB 中执行此操作,我使用 SQL 将抓取的文本写入其中:

CREATE TEMPORARY TABLE ttable (text VARCHAR(255) CHARACTER SET utf8); INSERT INTO ttable (text) VALUES ('Ðлава ÐдеÑÑÑ Ð¿ÑоÑÐ¸Ñ Ð²Ð»Ð°ÑÑи ÑÑÑÐ°Ð½Ñ ÑеÑÑÑ Ð·Ð° ÑÑол пеÑеговоÑов Ñ Ð Ð¾ÑÑией'); SELECT text, CONVERT(CAST(CONVERT(text USING latin1) AS BINARY) USING utf8) AS corrected_text FROM ttable;
并且接近了:

??лава ??десс?? п??оси?? влас??и с???ан??是吗???? за с??ол пе??егово??ов с ? оссией

而不是

Глава Одессы просит власти страны сесть за стол переговоров с Россией

如网站上所示。我可能可以使用法学硕士左右从这里得到正确的结果,但更愿意首先避免抓取混乱的数据......

有人在抓取西里尔文文本时遇到过类似的编码问题吗? 任何帮助将不胜感激!

[我在 Windows 10 上使用 R 4.3.2 和 rvest 1.0.3]

r web-scraping character-encoding rvest cyrillic
1个回答
1
投票
修复
rvest
 的无效 UTF-8 字节序列
显然

<meta>

 元素之一包含无效的 UTF-8 字节序列。它们似乎在字节处截断
描述内容,而不是在字符或字边界处,因此在某些情况下,只有多字节字符的第一部分挂在那里,从而创建无效的 UTF-8 字符串。

要“修复”或替换

rvest

 / 
xml2
 的错误代码点,我们可以通过 
httr2
 将内容作为原始字节加载,用 
stringi::stri_conv()
 替换有问题的字节,然后才让 
rvest
 解析它.

library(rvest) library(httr2) library(stringi) url_ <- "https://news-front.su/2022/08/29/glava-odessy-prosit-vlasti-strany-sest-za-stol-peregovorov-s-rossiej/" resp <- request(url_) |> req_perform() resp_header(resp, "Content-Type") #> [1] "text/html; charset=UTF-8" # attempt to parse response as-is # ( resp_body_html just calls resp_body_raw(resp) |> xml2::read_html() ) resp_body_html(resp) |> html_elements(".entry-title") |> html_text2() #> [1] "Ð\u0093лава Ð\u009eдеÑ\u0081Ñ\u0081Ñ\u008b пÑ\u0080оÑ\u0081иÑ\u0082 влаÑ\u0081Ñ\u0082и Ñ\u0081Ñ\u0082Ñ\u0080анÑ\u008b Ñ\u0081еÑ\u0081Ñ\u0082Ñ\u008c за Ñ\u0081Ñ\u0082ол пеÑ\u0080еговоÑ\u0080ов Ñ\u0081 РоÑ\u0081Ñ\u0081ией" # replace incorrect code points with 'missing/erroneous' character, # warning is expected, `?stringi::stri_conv` for details text_utf8 <- resp_body_raw(resp) |> stri_conv(from = "UTF-8", to = "UTF-8") #> Warning in stri_conv(resp_body_raw(resp), from = "UTF-8", to = "UTF-8"): input #> data \xffffffd0 in the current source encoding could not be converted to #> Unicode read_html(text_utf8) |> html_elements(".entry-title") |> html_text2() #> [1] "Глава Одессы просит власти страны сесть за стол переговоров с Россией"


检查无效字节序列。
我们可以通过在输出字符串中查找替换(

[\ufffd\u001a]

正则表达式)来定位错误:

# locate replacement(s) (err <- stri_locate_all_regex(text_utf8, '[\ufffd\u001a]')) #> [[1]] #> start end #> [1,] 27152 27152 stri_sub(text_utf8, err[[1]][,"start"] - 200, err[[1]][,"end"] + 10) |> stri_extract_last_regex("<[^/]+/>") #> [1] "<meta name='description' content='<strong>Мэр Одессы Геннадий Труханов призвал киевские власти вести пер�' />"
或者我们可以尝试将响应内容分割成行,用 

stringi::stri_enc_isutf8()

 检查那些行,以获取仍然存在无效序列的违规行。虽然字符串操作不适用于无效的字节序列,但 
vroom
/
readr
 仍然可以处理这个原始向量并将其拆分为我们的行:

l <- resp_body_raw(resp) |> vroom::vroom_lines() (err_idx <- which(!stri_enc_isutf8(l))) #> [1] 133 l[err_idx] #> [1] "<meta name=\"verification\" content=\"f612c7d25f5690ad41496fcfdbf8d1\" /><meta name='description' content='<strong>Мэр Одессы Геннадий Труханов призвал киевские власти вести пер\xd0' /> <!-- Yandex.Metrica counter -->"
违规行的十六进制转储:


hexdump screenshot

描述内容

中的最后一个单词占用了 7 个字节,在这种情况下看起来不正确。最后一个字节值是 0xD0,它

指示
它应该是 2 字节字符的第一个字节。

原始答案,解决方法为
chromote
一种可能的解决方法是使用 
chromote

来获取页面内容。可以通过

rvest::read_html_live()
来完成,但这也会加载所有链接的资源并评估 JavaScript。
要仅抓取主页面内容,我们可以在 Chrome 会话中评估我们自己的 JavaScript 以使用 Fetch API,例如:

library(rvest) library(chromote) url_ <- "https://news-front.su/2022/08/29/glava-odessy-prosit-vlasti-strany-sest-za-stol-peregovorov-s-rossiej/" fetch_content <- function(chromote_session, url_, timeout = 10000){ chromote_session$Runtime$evaluate( glue::glue('fetch("{url_}").then(response => response.text());'), awaitPromise = TRUE, timeout = timeout )$result$value } b <- ChromoteSession$new() fetch_content(b, url_) |> read_html() |> html_elements(".entry-title") |> html_text2() #> [1] "Глава Одессы просит власти страны сесть за стол переговоров с Россией"

创建于 2024 年 11 月 13 日,使用 

reprex v2.1.1

© www.soinside.com 2019 - 2024. All rights reserved.