爬虫学习记录

吴诗涛 2022-07-29 [R]

暑假过半,一个月来深入学习R语言,最近横向拓宽一下R语言技能,包括利用Rmd文件构建常用的幻灯片和网页文档、网络爬虫等。

此文用于记录爬虫使用心得,仅为个人理解,不足之处请指正。

简单理解,爬虫就是利用计算机替代我们的手工复制和粘贴,因此和手工复制一样,爬虫的关键是:

  1. 找到所有网页链接;
  2. 找到同一网页链接上所有所需信息的位置并提取;
  3. 访问所有网页,获取所有信息。

以通过翻页展示网站内容的数英网为例,爬取该网站上与数据有关的文章标题和发文时间:

在网页搜索框中输入关键词,这里为“数据”。

Figure 1: 在网页搜索框中输入关键词,这里为“数据”。

构建网页地址

在搜索结果下点击“查看更多文章”后,可以看到页面需要进行翻页:

通过点击翻页,并复制网址地址,可以发现网址中的规律

Figure 2: 通过点击翻页,并复制网址地址,可以发现网址中的规律

https://www.digitaling.com/search/articles/1?kw=%E6%95%B0%E6%8D%AE  # 第一页
https://www.digitaling.com/search/articles/2?kw=%E6%95%B0%E6%8D%AE  # 第二页
https://www.digitaling.com/search/articles/3?kw=%E6%95%B0%E6%8D%AE  # 第三页

可以发现,网址中除了1,2,3会随着翻页而改变,其余部分完全相同。由此,可以通过paste0()函数构建所有需要访问的网页链接:

urls <- paste0("https://www.digitaling.com/search/articles/",
               1:35, # 共有35页
               "?kw=%E6%95%B0%E6%8D%AE")
urls
#> [1] "https://www.digitaling.com/search/articles/1?kw=%E6%95%B0%E6%8D%AE"
#> [2] "https://www.digitaling.com/search/articles/2?kw=%E6%95%B0%E6%8D%AE"
#> [3] "https://www.digitaling.com/search/articles/3?kw=%E6%95%B0%E6%8D%AE"
#> [4] "https://www.digitaling.com/search/articles/4?kw=%E6%95%B0%E6%8D%AE"
#> [5] "https://www.digitaling.com/search/articles/5?kw=%E6%95%B0%E6%8D%AE"
#>  [ reached getOption("max.print") -- omitted 30 entries ]

获取所需信息的位置

这一环节需要用到rvest包读取网页信息,而后确认所需信息在HTML中的位置,最终构建读取函数,供第三步循环使用。如有扎实的HTML和CSS知识作支撑会更顺利,由于鄙人条件不成熟,因此以大量的试错法进行弥补。😇

library(rvest)
library(tidyverse) # 偷懒起见,还是直接加载了tidyverse大包

读取一个测试网址

test_url <- urls[1]
web <- read_html(test_url)
web
#> {html_document}
#> <html xmlns="https://www.w3.org/1999/xhtml">
#> [1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8 ...
#> [2] <body>\r\n\t<!-- Google tag (gtag.js) -->\r\n<script async src="https://w ...

这一步把整个网页文件都读取到了R中,接下来只要想办法在里边提取出想要的信息即可。

确认选择器

利用选择器进行提取,不同类型的信息对应的选择器不同,目前有两种方法确认不同信息的选择器:

  1. SelectorGadget,可以从这里安装谷歌浏览器插件。1
  2. 在所需信息的位置右击,检查。

提取“标题”

通过SelectorGadget,确认标题在该网页中被定义为“h3”(三级标题),因此只要提取该网页文件中所有的被定义为“h3”的文本,就可以获取这一页上所有的标题。

Figure 3: 通过SelectorGadget,确认标题在该网页中被定义为“h3”(三级标题),因此只要提取该网页文件中所有的被定义为“h3”的文本,就可以获取这一页上所有的标题。

title <- web |> 
  html_nodes("h3") |> 
  html_text()
title
#> [1] "东方甄选又爆了,入淘宝直播首秀一场卖出1亿!"                  
#> [2] "狂卖170亿,今年电影为啥大爆发?"                              
#> [3] "2023年夏日经济之现制咖啡&茶饮市场洞察报告,线上总用户达1.51亿"
#> [4] "详拆「茶百道」招股书:新茶饮TOP3的喜与忧"                     
#> [5] "暂停4年,维密在中国“咸鱼翻身”了"                              
#>  [ reached getOption("max.print") -- omitted 25 entries ]

这一页上一共提取了30个标题。

提取“日期”

通过检查元素,最终确认标题在该网页中被定义为“label”,同上,提取该网页文件中所有的被定义为“label”的文本,就可以获取这一页上所有的日期。

Figure 4: 通过检查元素,最终确认标题在该网页中被定义为“label”,同上,提取该网页文件中所有的被定义为“label”的文本,就可以获取这一页上所有的日期。

date <- web |> 
  html_nodes("label") |> 
  html_text()  # 这里提取了33个,最后3个为无效信息,需剔除
date <- date[1:(length(date)-3)]
date
#> [1] "2023-08-31" "2023-08-20" "2023-08-17" "2023-08-17" "2023-08-16"
#>  [ reached getOption("max.print") -- omitted 25 entries ]

输出测试网址上的信息

将每页的标题和时间合并到数据框中,便于后续处理。

data <- tibble(title, date)
DT::datatable(data)

构建爬虫函数

以上测试的最终目的是构建一个自动抓取的函数,该函数以网址为参数,如上所示的数据框为输出。具体如下:

scraper <- function(url) {
  # 读取参数网址对应的网页文件
  web <- read_html(url)
  
  # 获取网页文件中的标题
  title <- web |> 
  html_nodes("h3") |> 
  html_text()
  
  # 获取网页文件中的时间
  date <- web |> 
  html_nodes("label") |> 
  html_text()  
  date <- date[1:(length(date)-3)] # 剔除最后3个无效信息

  # 返回数据框
  return(tibble(title, date))
  
  # 休眠2秒,防止访问过快,被服务器禁止访问
  Sys.sleep(2)
}

获取所有信息

每爬取一个页面,可以获得一个数据框,其中包含30条记录(最后一页可能不足30条)。利用循环遍历35个页面,获得35个数据框,最后按行合并所有数据框,即可得到页面上所有的信息。

这一步看似复杂,但是可以由map_dfr()函数一步到位:

scraper_data <- map_dfr(urls, scraper)
nrow(scraper_data)
#> [1] 1050

最终获取了1050行数据,结果如下(展示前十条):

DT::datatable(scraper_data[1:10, ]) 

总结

最近遇到很多奇奇怪怪的网站,但思路始终是一致的,灵活运用,🉑解决90%的爬虫问题。

有的网站加载页的方式不一(比如下拉滚动加载),这会导致在网址不变的情况下爬虫无法抓取该页面所有信息,针对这样的网页可以采用还不错的谷歌插件Web Scraper等方法去抓取。利用R语言需补充一些其他的技术,目前只略知一二,未能熟练运用。


  1. 手头没有其他浏览器,不是谷歌浏览器的小伙伴自己寻找一下有无该插件吧!🏃 ↩︎