R:两种不同的网络抓取方法会产生两种不同的结果?

R : Two Different Methods of Webscraping Produce Two Different Results?

提问人:stats_noob 提问时间:7/28/2022 最后编辑:stats_noob 更新时间:8/3/2022 访问量:143

问:

我正在尝试抓取网站上每个名称的名称、地址和经度/纬度坐标(例如 www.mywebsite.com)。我使用以下代码根据此 SO 帖子获取地址和名称

library(tidyverse)
library(rvest)
library(httr)
library(XML)

# Define function to scrape 1 page
get_info <- function(page_n) {
  
  cat("Scraping page ", page_n, "\n")
  
  page <- paste0("mywebsite.com",
    page_n, "?extension") %>% read_html
  
  tibble(title = page %>%
           html_elements(".title a") %>%
           html_text2(),
         adress = page %>%  
           html_elements(".marker") %>% 
           html_text2(),
         page = page_n)
}

# Apply function to pages 1:10
df_1 <- map_dfr(1:10, get_info)

# Check dimensions
dim(df_1)
[1] 90 

由于我不知道如何修改上面的代码来提取坐标,所以我写了一个单独的脚本来抓取它们:

# Recognize pattern in websites
part1 = "www.mywebsite.com"
part2 = c(0:55)
part3 = "?extension"
temp = data.frame(part1, part2, part3)

# Create list of websites
temp$all_websites = paste0(temp$part1, temp$part2, temp$part3)

# Scrape
df_2 <- list()

for (i in 1:10)
    
{tryCatch({
    
    url_i <-temp$all_websites[i]
    
    page_i <-read_html(url_i)
    
    b_i = page_i %>% html_nodes("head")
    
    listanswer_i <- b_i %>% html_text() %>% strsplit("\\n")
    
    df_2[[i]] <- listanswer_i
    
    print(listanswer_i)
    
}, error = function(e){})
    
}

# Extract long/lat from results

lat_long = grep("LatLng", unlist(df_2[]), value = TRUE)


 df_2 = data.frame(str_match(lat_long, "LatLng(\\s*(.*?)\\s*);"))

最后,抓取前 10 页的名称/地址会得到 90 个条目,但抓取相同的 10 页的经度/纬度会得到 96 个条目:

dim(df_1)
[1] 90 

dim(df_2)
[1] 96  3

有人可以帮我了解为什么会发生这种情况以及我能做些什么来解决这个问题吗?

最后,我会制作一个最终表(使用 df_1 和 df_2),如下所示:

 id  name  address  long  lat
1  1 name1 address1 long1 lat1
2  2 name2 address2 long2 lat2
3  3 name3 address3 long3 lat3

谢谢!

注意:我知道有些名称可能缺少纬度/经度,并且“df_1”的尺寸可能无法与“df_2”的尺寸匹配。如果是这种情况,是否有可能以某种方式找出哪些名称缺少纬度/经度(例如,在这些情况下将纬度/经度条目替换为 NULL)?例如,假设纬度/经度不适用于“name3”:

 id  name  address  long  lat
1  1 name1 address1 long1 lat1
2  2 name2 address2 long2 lat2
3  3 name3 address3   NA   NA
HTML R 循环 Web-抓取 、数据操作

评论

1赞 socialscientist 7/28/2022
如果您对我在下面提供的答案有任何疑问(即,如果这不是一个可接受的答案),请告诉我。您应该特别注意的一件事是“为什么问题不在于页面”中的要点。
0赞 stats_noob 7/28/2022
@社会科学家:非常感谢你的回答 - 我真的很感激!我正在重新阅读你写的所有内容,以确保我理解正确!
0赞 socialscientist 7/28/2022
我看到你修改了你的问题内容,添加了一个全新的部分,询问如何从你的抓取输出中生成一个整洁的数据集。这是一个与你问的问题完全不同的问题(这就是为什么两个物体的维度不同)。请删除此添加内容,以遵循每个帖子一个问题的规则。您可以随时创建另一个帖子,询问如何从您的输出移动到该输出。
0赞 stats_noob 7/28/2022
@Socialscientist:我并不是要添加一个新部分 - 我只是想提供一些关于最终结果应该如何的参考。如果你愿意 - 我可以删除它。
0赞 socialscientist 7/28/2022
您的问题是关于两种不同的网络抓取方法如何产生两种不同的结果,之前以“有人可以帮我了解为什么会发生这种情况以及我能做些什么来解决这个问题吗?相比之下,“我如何抓取这个网站以产生这个特定的结果?”是一个完全不同的问题。建议把它放在一个单独的问题中,这样其他人可以更有效地帮助你,你的帖子将来对其他人更有用。

答:

2赞 socialscientist 7/28/2022 #1

问题

问题在于,第二个代码片段没有过滤掉包含坐标但不提供坐标的字符串。"LatLng"

在第二个代码段完成页面填充后,执行以下操作:

lat_long = grep("LatLng", unlist(df_2[]), value = TRUE)

如果你用 看这个的输出,你会看到一堆带有坐标的行。事实上,你会看到 90 个这样的行,因为这是所有这些页面上出现的数量。但是,您还会看到带有字符串 的行。如果你回到你抓取的原始HTML,你会看到它偶尔会出现。因此,您需要删除这些行。print(lat_long)"\t\t\t\tvar bounds = new google.maps.LatLngBounds();"

我以为也许你用剩下的代码完成了这个,但你从来没有真正删除它们。例如,下面的代码只生成一个填充了值的对象。我不认为这符合您的要求:NA

as.numeric(gsub("([0-9]+).*$", "\\1", lat_long))

此外,以下内容还保留了这些值:

data.frame(str_match(lat_long, "LatLng(\\s*(.*?)\\s*);"))

解决方案

您需要删除没有坐标的元素。您会注意到这些元素都包含子字符串,因此一旦它们如下所示,或者使用正则表达式,您就可以将它们过滤掉。"LatLngBounds();"data.frame

df_2 %>% filter(X1 != "LatLngBounds();")

请注意,这实际上将生成 86 行,而不是 90 行。所以,现在我们实际上很短 4 行。这是因为您实际上并没有收集提供商页面上每个人的所有 GPS 坐标。您可以知道这一点,因为每个提供都有一个地址,而坐标只是将这些地址传递给 Maps API。df_1

为什么你没有得到所有的坐标?我的猜测有两个原因。首先,您正在根据子字符串抓取坐标。此标记表示地图上的标记/图钉。由于地图上的图钉数量不必等于页面上的提供程序数量,因此您将错过一些提供程序。不太可能的问题可能与 Google Maps API 有关。如果您访问要从中抓取的网址(示例),您会在左下角看到 Google 地图小组件包含错误“此页面未正确加载 Google 地图。有关技术详细信息,请参阅 JavaScript 控制台”。如果您查看 JS 控制台,您会看到提供的 Google Maps API 密钥无效。这似乎是一个可能的问题,因为 (a) 您正在抓取每页一行,并且 (b) 每行之后的行包含的坐标不一定在提供商附近的任何地方(我的初始值在美国西海岸,而提供商在加拿大)。我不知道这是否有任何后果,但如果标记问题不是驱动程序,它会解释它。marker"LatLngBounds"

但是,所有这些都是无关紧要的,因为您甚至不需要首先抓取坐标。您有一个地址列表:您可以自己对它们进行地理编码!有不同的方法可以做到这一点,但你可以通过简单地将它们传递给谷歌地图API来复制网站正在做的事情!有关如何执行此操作的分步说明,请参阅此处

识别问题

为了更好地了解将来如何处理类似的问题,我将展示我是如何解决这个问题的。解决此类问题的一种方法是从排除可能的解释开始。

为什么问题不是“缺少坐标”

如果问题是名称缺少坐标,我们会期望.但是,您报告的情况恰恰相反:.nrow(df1) > nrow(df2)nrow(df2) > nrow(df1)

为什么问题不是第一个代码片段

由于每个页面包含 9 个提供程序(至少直到最后一页),并且您正在抓取 10 个页面,因此我们希望返回元素。如前所述,第一个代码段返回一个包含 90 行的对象,而第二个代码段返回一个包含 96 行的对象。第二个代码片段一定是问题所在。9*10 = 90

为什么问题不在于页面

查看您的代码,我注意到您正在抓取不同的页面。生成的代码将遍历 in 区间 的值。相反,要生成的代码会迭代 in interval 的值。这是因为后一种代码提取了 at 索引的值,而这些值恰好是 因为 只是向量。由于返回与 相同的页面,因此您的第一个代码是 scaping pages,而后一个代码是 scraping page 。这意味着 和 中包含的值将不同。df1page_n1:10df2page_n0:9all_websites1:100:9all_websites0:55page_n == 0page_n == 11:10c(1,1:9)df1df2

但是,这无法解释两个对象的维度差异,因为它们仍然需要返回 90 行!

评论

0赞 stats_noob 7/28/2022
@社会科学家:非常感谢你的回答!当我尝试您提出的解决方案时,df_2中的行数从 96 减少到 86。是否有可能以某种方式确定哪对纬度/经度对应于哪些名称?也许像 LEFT JOIN 之类的东西(如果有一个通用的 ID KEY)?非常感谢您的帮助!
0赞 socialscientist 7/28/2022
@stats_noob我修改了我的回复以解释可能的问题。简而言之,您正在从 Google 地图上的标记中抓取坐标。没有理由期望标记的数量必须与提供者的数量相匹配(地图通常提供标记的子集,以便于查看)。我的建议是自己对地址进行地理编码,而不是处理令人头疼的问题。df_1