提问人:stats_noob 提问时间:7/28/2022 最后编辑:stats_noob 更新时间:8/3/2022 访问量:143
R:两种不同的网络抓取方法会产生两种不同的结果?
R : Two Different Methods of Webscraping Produce Two Different Results?
问:
我正在尝试抓取网站上每个名称的名称、地址和经度/纬度坐标(例如 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
答:
问题
问题在于,第二个代码片段没有过滤掉包含坐标但不提供坐标的字符串。"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 。这意味着 和 中包含的值将不同。df1
page_n
1:10
df2
page_n
0:9
all_websites
1:10
0:9
all_websites
0:55
page_n == 0
page_n == 1
1:10
c(1,1:9)
df1
df2
但是,这无法解释两个对象的维度差异,因为它们仍然需要返回 90 行!
评论
df_1
评论