尝试通过抓取找到超链接

时间:2019-12-10 04:29:05

标签: html r web-scraping

因此,我对网络爬虫这个话题还很陌生。我正在尝试查找以下页面的html代码包含的所有超链接: https://www.exito.com/mercado/lacteos-huevos-y-refrigerados/leches

这就是我尝试过的:

    <div id="hotjob">
     <hotjob-component :projects="{{ json_encode($Projects) }}"></hotjob-component>
    </div>

结果仅包含6个链接,但是仅通过查看页面,您可以看到还有更多的超链接。

例如,第一张图片后面的代码类似:url <- "https://www.exito.com/mercado/lacteos-huevos-y-refrigerados/leches" webpage <- read_html(url) html_attr(html_nodes(webpage, "a"), "href") ...

我在做什么错了?

2 个答案:

答案 0 :(得分:1)

由于html / xml解析器看不到网站的该部分,因此您将无法获得a标签。这是因为,如果您选择网站的另一部分,它是网站的动态部分,因此会发生变化;网站的唯一“静态”部分是顶部标题,这就是为什么您只获得6个a标签的原因:标题中有6个a标签。

为此,我们需要模仿浏览器的行为(Firefox,Chrome等),进入网站(请注意,我们不是以html / xml解析器的形式进入网站,而是以“用户”),然后从那里阅读html / xml源代码。

为此,我们需要R包RSelenium。确保installdocker一起正确使用,因为如果没有它,下面的代码将无法正常工作。

安装RSeleniumdocker后,从终端运行docker run -d -p 4445:4444 selenium/standalone-firefox:2.53.1(如果在Linux上,则可以在终端上运行;如果在Windows上,则必须下载docker终端,在那里运行)。之后,您都准备好再现下面的代码。

为什么你的方法行不通

我们需要从下图访问第5个div标签:

div_args

如您所见,第5个div标记内部有三个点(...),表示内部有代码:这正是底部 all 的所有位置网站的名称(包括您所关注的a标签)。如果我们尝试使用rvestxml2访问此第5个标签,则找不到任何内容:

library(xml2)
library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

lnk <- "https://www.exito.com/mercado/lacteos-huevos-y-refrigerados/leches?page=2"

# Note how the 5th div element is empty and it should contain the lower
# part of the website
lnk %>%
  read_html() %>%
  xml_find_all("//div[@class='flex flex-grow-1 w-100 flex-column']") %>%
  xml_children()
#> {xml_nodeset (6)}
#> [1] <div class=""></div>\n
#> [2] <div class=""></div>\n
#> [3] <div class=""></div>\n
#> [4] <div class=""></div>\n
#> [5] <div class=""></div>\n
#> [6] <div class=""></div>

请注意,第5个div标记内部没有任何代码。一个简单的html / xml解析器将无法捕获它。

它如何工作

我们需要使用RSelenium。正确安装所有组件后,我们需要设置一个“远程驱动程序”,将其打开并导航到该网站。所有这些步骤只是为了确保我们以浏览器的“正常”用户身份进入网站。这将确保我们可以访问进入网站时实际看到的渲染代码。以下是进入网站并构建链接的详细步骤。


# Make sure you install docker correctly: https://docs.ropensci.org/RSelenium/articles/docker.html
library(RSelenium)

# After installing docker and before running the code, make sure you run
# the rselenium docker image: docker run -d -p 4445:4444 selenium/standalone-firefox:2.53.1

# Now, set up your remote driver
remDr <- remoteDriver(
  remoteServerAddr = "localhost",
  port = 4445L,
  browserName = "firefox"
)

# Initiate the driver
remDr$open(silent = TRUE)

# Navigate to the exito.com website
remDr$navigate(lnk)

prod_links <-
  # Get the html source code
  remDr$getPageSource()[[1]] %>%
  read_html() %>%
  # Find all a tags which have a certain class
  # I searched for this tag manually on the website code and saw that all products
  # had an a tag that shared the same class
  xml_find_all("//a[@class='vtex-product-summary-2-x-clearLink h-100 flex flex-column']") %>%
  # Extract the href attribute
  xml_attr("href") %>%
  paste0("https://www.exito.com", .)

prod_links
#>  [1] "https://www.exito.com/leche-semidescremada-deslactosada-en-bolsa-x-900-ml-145711/p"
#>  [2] "https://www.exito.com/leche-entera-en-bolsa-x-900-ml-145704/p"                     
#>  [3] "https://www.exito.com/leche-entera-sixpack-x-1300-ml-cu-987433/p"                  
#>  [4] "https://www.exito.com/leche-deslactosada-en-caja-x-1-litro-878473/p"               
#>  [5] "https://www.exito.com/leche-polvo-deslactos-semidesc-764522/p"                     
#>  [6] "https://www.exito.com/leche-slight-sixpack-en-caja-x-1050-ml-cu-663528/p"          
#>  [7] "https://www.exito.com/leche-semidescremada-sixpack-en-caja-x-1050-ml-cu-663526/p"  
#>  [8] "https://www.exito.com/leche-descremada-sixpack-x-1300-ml-cu-563046/p"              
#>  [9] "https://www.exito.com/of-leche-deslact-pag-5-lleve-6-439057/p"                     
#> [10] "https://www.exito.com/sixpack-de-leche-descremada-x-1100-ml-cu-414454/p"           
#> [11] "https://www.exito.com/leche-en-polvo-klim-fortificada-360g-239085/p"               
#> [12] "https://www.exito.com/leche-deslactosada-descremada-en-caja-x-1-litro-238291/p"    
#> [13] "https://www.exito.com/leche-deslactosada-en-caja-x-1-litro-157334/p"               
#> [14] "https://www.exito.com/leche-entera-larga-vida-en-caja-x-1-litro-157332/p"          
#> [15] "https://www.exito.com/leche-en-polvo-klim-fortificada-780g-138121/p"               
#> [16] "https://www.exito.com/leche-entera-en-bolsa-x-1-litro-125079/p"                    
#> [17] "https://www.exito.com/leche-entera-en-bolsa-sixpack-x-11-litros-cu-59651/p"        
#> [18] "https://www.exito.com/leche-deslactosada-descremada-sixpack-x-11-litros-cu-22049/p"
#> [19] "https://www.exito.com/leche-entera-en-polvo-instantanea-x-760-gr-835923/p"         
#> [20] "https://www.exito.com/of-alpin-cja-cho-pag9-llev12/p"

希望这能回答您的问题

答案 1 :(得分:1)

点击页面上的 Mostrarmás时,您可以在网络标签中观察到的GraphQL query动态返回数据,包括网址。这就是为什么您的初始查询中没有内容的原因-尚未请求。


XHR以获得产品信息

开发工具的网络标签中的相关XHR:

enter image description here

url查询字符串的实际查询参数:

enter image description here

您可以删除大部分请求信息。您需要做的是extensions参数。更具体地说,您需要提供sha256Hash中与variables键相关的persistedQuery和base64编码的字符串值。


SHA256 Hash

可以从至少一个主要控制设置的js文件中提取适当的哈希。您可以使用的示例文件是:

https://exitocol.vtexassets.com/_v/public/assets/v1/published/bundle/public/react/asset.min.js?v=1&files=vtex.store-resources@0.38.0,OrderFormContext,Mutations,Queries,PWAContext&files=exitocol.store-components@0.0.2,common,11,3,SearchBar&files=vtex.responsive-values@0.2.0,common,useResponsiveValues&files=vtex.slider@0.7.3,common,0,Dots,Slide,Slider,SliderContainer&files=exito.components@4.0.7,common,0,1,3,4&workspace=master

查询哈希可以从xhr请求到此uri的响应文本中进行正则表达式处理。对正则表达式的解释是here,并且第一个匹配就足够了:

enter image description here

要在R中使用stringr进行申请,您将需要一些额外的转义符,例如\s成为\\s


The Base64 encoded product query

您可以使用相应的库生成base64编码的字符串,例如caTools软件包中似乎有一个base64encode R函数。

编码后的字符串看起来像(取决于页面/结果批次):

eyJ3aXRoRmFjZXRzIjpmYWxzZSwiaGlkZVVuYXZhaWxhYmxlSXRlbXMiOmZhbHNlLCJza3VzRmlsdGVyIjoiQUxMX0FWQUlMQUJMRSIsInF1ZXJ5IjoiMTQ4IiwibWFwIjoicHJvZHVjdENsdXN0ZXJJZHMiLCJvcmRlckJ5IjoiT3JkZXJCeVRvcFNhbGVERVNDIiwiZnJvbSI6MjAsInRvIjozOX0=

已解码:

{"withFacets":false,"hideUnavailableItems":false,"skusFilter":"ALL_AVAILABLE","query":"148","map":"productClusterIds","orderBy":"OrderByTopSaleDESC","from":20,"to":39}

fromto参数是二十个批次的产品结果批次的偏移量。因此,您可以编写函数,以返回适当的sha256哈希并发送对产品信息的后续请求,并在其中使用适当的库对上面的字符串进行base64编码,然后更改fromto {{1 }} 按要求。可能还有其他人(有戏!)。


xhr响应:

响应为json,因此您可能需要一个json库(例如jsonlite)来处理结果(更新:似乎您不使用R和params)。按照Python示例,您可以从嵌套在httr中的词典列表中提取链接,其中result['data']['products']是使用result从xhr检索到from的json对象。


示例:

使用R和Python的示例如下所示(注:我不太熟悉R)。上面的代码与语言无关。

请记住,当我提取网址时,返回的json有很多信息,包括产品标题,价格,图像信息等。


示例输出:

enter image description here


待办事项:

  1. 添加错误处理
  2. 使用会话对象可受益于基础tcp连接的重用,尤其是在多次请求获取所有产品的情况下
  3. 添加功能以返回产品总数,并返回循环结构以检索全部(Python示例可能会受益于装饰器)

R(快速入门):

params

Py:

library(purrr)
library(stringr)
library(caTools)
library(httr)

get_links <- function(sha, start, end){
  string = paste0('{"withFacets":false,"hideUnavailableItems":false,"skusFilter":"ALL_AVAILABLE","query":"148","map":"productClusterIds","orderBy":"OrderByTopSaleDESC","from":' , start , ',"to":' , end , '}')
  base64encoded <- caTools::base64encode(string)
  params = list(
    'extensions' = paste0('{"persistedQuery":{"version":1,"sha256Hash":"' , sha , '","sender":"vtex.store-resources@0.x","provider":"vtex.search-graphql@0.x"},"variables":"' , base64encoded , '"}')
  )

  product_info <- content(httr::GET(url = 'https://www.exito.com/_v/segment/graphql/v1', query = params))$data$products
  links <- map(product_info, ~{
     .x %>% .$link
  })
  return(links)
}


start <- '0'
end <- '19'     
sha <- httr::GET('https://exitocol.vtexassets.com/_v/public/assets/v1/published/bundle/public/react/asset.min.js?v=1&files=vtex.store-resources@0.38.0,OrderFormContext,Mutations,Queries,PWAContext&files=exitocol.store-components@0.0.2,common,11,3,SearchBar&files=vtex.responsive-values@0.2.0,common,useResponsiveValues&files=vtex.slider@0.7.3,common,0,Dots,Slide,Slider,SliderContainer&files=exito.components@4.0.7,common,0,1,3,4&workspace=master') %>%
  content(., as = "text")%>% str_match(.,'query\\s+productSearch.*?hash:\\s+"(.*?)"')%>% .[[2]] 
links <- get_links(sha, start, end)
print(links)