我正在尝试使用 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]
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 -->"
违规行的十六进制转储:描述内容中的最后一个单词占用了 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] "Глава Одессы просит власти страны сесть за стол переговоров с Россией"