Golang爬虫如何爬取js加载的动态页面数据

2,516 阅读4分钟
写在前面

静态网页也就是网页中没有程序代码,只有HTML,静态网页一旦写成,格式就不会变化。而动态网页是除了HTML,还有特定功能的程序代码。当我们通过浏览器与服务器进行交互的时候,服务器根据我们的请求动态的返回数据并加载网页,该过程也就是所谓的页面渲染。

因为服务器不会一次性的返回所有页面内容,所以很多数据并不在网页的源代码中,因此也就不能通过简单的http.Get()来爬取页面数据。

1664457486(1).png 以我项目需要爬取的网站为例www.fifa.com/tournaments…

1664457509(1).png

我们可以看到,在返回请求的Body里面是空值,并没有我们在网页源代码中所见的数据。因此我们需要动态爬取存放在服务器上的数据。抓取动态数据的方法一般有两种:(1)通过JavaScript逆向工程获取动态数据接口(真实的访问路径)——通过API爬取数据。(2)利用第三方库模拟真实浏览器,获取JavaScript渲染后的内容。本文主要讨论第二种方法:

方法一:Golang+chromedp+goquery

chromedp 本质上就是一个浏览器调度包,它与Selenium非常类似,但是使用起来更加方便,更易拓展,而且对golang更加友好

go get -u github.com/chromedp/chromedp //安装chromedp

使用chromedp(首先要有chrome浏览器),我们可以将需要浏览的网站的url传入,然后等待网页把全部的动态js执行完毕,生成最后的html文件,这时再获取其中的元素,便可以读取所有的元素.

func GetHttpHTML(url string, selector string) (tmp [][]string) {
   options := []chromedp.ExecAllocatorOption{
      chromedp.Flag("headless", true), //设置成无浏览器弹出模式
      chromedp.Flag("blink-settings", "imageEnable=false"),
      chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"),
   }
   c, _ := chromedp.NewExecAllocator(context.Background(), options...)
   chromeCtx, cancel := chromedp.NewContext(c, chromedp.WithLogf(log.Printf))
   _ = chromedp.Run(chromeCtx, make([]chromedp.Action, 0, 1)...)
   
   timeOutCtx, cancel := context.WithTimeout(chromeCtx, 60*time.Second)
   defer cancel()
   
   var htmlContent string
   err := chromedp.Run(timeOutCtx,
      chromedp.Navigate(url),
      //需要爬取的网页的url
      //chromedp.WaitVisible(`#content > div > section.fp-tournament-award-badge-carousel_awardBadgeCarouselSection__w_Ys5 > div > div > div.col-12.fp-tournament-award-badge-carousel_awardCarouselColumn__fQJLf.g-0 > div > div > div > div > div > div > div.slick-slide.slick-active.slick-current > div > div > div`),
      chromedp.WaitVisible(selector),
      //等待某个特定的元素出现
      chromedp.OuterHTML(`document.querySelector("body")`, &htmlContent, chromedp.ByJSPath),
      //生成最终的html文件并保存在htmlContent文件中
   )
   if err != nil {
      log.Fatal(err)
   }
   log.Println(htmlContent)
   doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
   
   awardTmp := [][]string{}
   doc.Find(`div[class="fp-tournament-award-badge_awardContent__dUtoO"]`).Each(func(i int, selection *goquery.Selection) {
      award := selection.Find(`p[class="fp-tournament-award-badge_awardName__JpsZZ"]`).Text()   
      //goquery通过Find()查找到我们选择的位置,Each()的功能与遍历相似返回所有的结果,Text()返回文本内容
      name := selection.Find(`h4[class=" fp-tournament-award-badge_awardWinner__P_z2d"]`).Text()
      country := selection.Find(`p[class="fp-tournament-award-badge_awardWinnerCountry__EmjVU"]`).Text()
      
      awardTmp = append(awardTmp, []string{award, name, country})
   })
   return awardTmp
}

函数需要传入第一个值就是我们需要爬取的网页,而第二个值selector即为我们爬取的数据对应的选 html择器 , 通过谷歌浏览器进入网站,按 F12 -> 点击左上角的鼠标 -> 再点击我们需要爬取的数据 -> 就可以看到实际的html源码(目前看到的是通过 javascript 动态生成数据后的)

image.png 我需要爬取的是世界杯奖项对应的球员以及国家,选择此处位置作为我们的selector。 此时,htmlContext中就已经保存了经过数据渲染之后完整的页面信息。接下来就可以通过goquery选择出我们需要的数据,我将其保存在二维数组中方便下一步处理

1664459607(1).png 大功告成~~

方法二:通过selenium来模拟浏览器发送请求

go get github.com/tebeka/selenium //安装selenium库

另外,我们还需要安装webdriver,通过chrome://version可以查询到我们浏览器的版本

1664460399(1).png

下载与之对应版本的chromedriver并存放到chrome的安装位置

1664460636(1).png

opts := []selenium.ServiceOption{}
caps := selenium.Capabilities{
   "browserName": "chrome",
}

imageCaps := map[string]interface{}{
   "profile.managed_default_content_settings.images": 2,
}
chromeCaps := chrome.Capabilities{
   Prefs: imageCaps,
   Path:  "",
   Args: []string{
      "--headless", //Chrome无头模式
      "--no-sandbox",
      "--User-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
      "--ignore-certificate-errors",
      "--ignore -ssl-errors",
   },
}
caps.AddChrome(chromeCaps)
//启动Chromedriver
service, err := selenium.NewChromeDriverService(
   "path/to/chromedriver", 8080, opts...,
)
defer service.Stop()

if err != nil {
   fmt.Println(err)
   return "", err
}
//调起Chrome浏览器
webDriver, err := selenium.NewRemote(caps, fmt.Sprintf("http://localhost:%d/wd/hub", 8080))

if err != nil {
   fmt.Println(err)
   return "", err
}
defer webDriver.Quit()
//导航到目标网站
err = webDriver.Get(url)

if err != nil {
   fmt.Println(err)
   return "", err
}
//selenium提供了多种选择器(ByTagName, ByClassName, ByCSSSelector)帮助我们选择
outputDiv, err := webDriver.FindElement(selenium.ByTagName, "span")
结语

通过以上两种方法我们就可以爬取到需要动态加载的数据