【问题标题】:R : Two Different Methods of Webscraping Produce Two Different Results?R:两种不同的网页抓取方法会产生两种不同的结果?
【发布时间】:2022-08-03 09:24:28
【问题描述】:

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

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

标签: html r loops web-scraping data-manipulation


【解决方案1】:

问题

问题是您的第二个代码 sn-p 没有过滤掉包含 "LatLng" 但不提供坐标的字符串。

在您的第二个代码 sn-p 完成页面缩放后,您执行以下操作:

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

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

我想也许你用剩下的代码完成了这个,但你从来没有真正删除它们。例如,下面的代码只生成一个填充有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 坐标。您可以知道这一点,因为每个提供在df_1 中都有一个地址,并且坐标只是将这些地址传递给 Maps API。

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

但是,所有这些几乎都无关紧要,因为您甚至不需要一开始就抓取坐标。您有一个地址列表:您可以自己对它们进行地理编码!有不同的方法可以做到这一点,但是您可以通过简单地将它们传递给 Google Maps API 来复制站点正在执行的操作!有关如何执行此操作的分步说明,see here

识别问题

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

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

如果问题是名称缺少坐标,我们期望nrow(df1) &gt; nrow(df2)。但是,您报告了相反的情况:nrow(df2) &gt; nrow(df1)

为什么问题不是第一个代码sn-p

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

为什么问题不在于页面

查看您的代码,我注意到您正在抓取不同的页面。您生成df1 的代码在1:10 区间内迭代page_n 的值。相反,生成df2 的代码在0:9 区间内迭代page_n 的值。这是因为后一个代码在索引1:10 处提取了all_websites 的值,而这恰好是0:9 的值,因为all_websites 只是向量0:55。由于page_n == 0 返回与page_n == 1 相同的页面,因此您的第一个代码是对页面1:10 进行翻页,而后一个代码是对页面c(1,1:9) 进行刮页。这意味着df1df2 中包含的值会有所不同。

然而,这并不能解释这两个对象的维度差异,因为它们仍会返回 90 行!

【讨论】:

  • @socialscientist:非常感谢您的回答!当我尝试您提出的解决方案时,df_2 中的行数从 96 减少到 86。是否有可能确定哪对纬度/经度对应于哪个名称?也许像 LEFT JOIN (如果有一个通用的 ID KEY)?非常感谢您的帮助!
  • @stats_noob 我修改了我的回复以解释可能的问题。简而言之,您正在从 Google 地图上的标记中抓取坐标。没有理由期望标记的数量必须与提供者的数量相匹配(地图通常提供标记的子集以便于查看)。我的建议是自己对来自df_1 的地址进行地理编码,而不是为弄清楚这一点而头疼。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-23
  • 1970-01-01
  • 2023-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-15
相关资源
最近更新 更多