Go Colly抓取豆瓣电影Top250 | 🏆 技术专题第二期征文

2,300 阅读4分钟

百度一下爬虫抓取豆瓣top250,结果竟有70多页。

一、起因

为何豆瓣Top250如此受欢迎?因为它实在是太适合做爬虫入门练习了。

几乎没有任何反爬限制,要抓取的电影相关内容也全部都在源码中(没有异步加载,JS动态修改DOM等情况)。

本来计划抓取掘金热门文章来着,但是发现数据基本都是Ajax请求接口获取,所以还是以豆瓣为例吧。

二、爬虫

因为第一份工作就是Python爬虫,所以对其他语言的爬虫框架也是比较感兴趣的。

爬虫说简单也简单,会发出Http请求、了解一些Html基本知识、能够将数据保存下来,就算是爬虫入门了。但爬虫说难也难,如何高效的编写爬虫、如何保证数据的准确和实效、如何应对各种反爬机制、以及如何在合规合法的情况下去获取数据。

在GitHub上搜了一圈Go语言相关的框架,发现Colly一枝独秀,竟有11.6k✨

三、Colly

重要的事情只说一遍: 一定要去看官方文档,这个好像不是官方的go-colly.org,但是也要浏览一遍才可以的。

挂一下官方example里面的basic示例吧。注释很详细,所以官方示例一定要看!!!

func main() {
	// Instantiate default collector
	c := colly.NewCollector(
		// Visit only domains: hackerspaces.org, wiki.hackerspaces.org
		colly.AllowedDomains("hackerspaces.org", "wiki.hackerspaces.org"),
	)

	// On every a element which has href attribute call callback
	c.OnHTML("a[href]", func(e *colly.HTMLElement) {
		link := e.Attr("href")
		// Print link
		fmt.Printf("Link found: %q -> %s\n", e.Text, link)
		// Visit link found on page
		// Only those links are visited which are in AllowedDomains
		c.Visit(e.Request.AbsoluteURL(link))
	})

	// Before making a request print "Visiting ..."
	c.OnRequest(func(r *colly.Request) {
		fmt.Println("Visiting", r.URL.String())
	})

	// Start scraping on https://hackerspaces.org
	c.Visit("https://hackerspaces.org/")
}

四、豆瓣页面分析及代码摘要

如图,我们要做的就是:

  • ①打开首页,获取列表页地址
  • ②进入列表页
  • ③遍历列表获取详情页URL,获取下一页(列表页)地址
  • ④重复②、③,直到没有下一页为止

4.1 下一页URL的获取

如图可以看到,当我们处于第1页(非最后一页)时,span.next元素下面是有a元素的,里面的地址即为下一页。

当我们翻到最后一页时,a元素不见了。因此我们可以根据是否有a元素来判断是不是已经抓取完全部数据了。

Colly中使用goquerySelector来选择元素,也可以使用XPath来做选择,有兴趣的可以了解一下。这里我们使用goquerySelector

下一页URL获取代码如下:

collector.OnHTML("div.paginator > span.next", func(element *colly.HTMLElement) {
    href, found := element.DOM.Find("a").Attr("href")
    // 如果有下一页,则继续访问
    if found {
        element.Request.Visit(element.Request.AbsoluteURL(href))
    }
})

4.2 详情页列表URL的获取

如图,我们只需要查找到div.article > ol.grid_view就找到了li列表的直接父元素。然后再依次遍历li节点即可。我们所需的a元素,在li节点下面div.hd > ahref属性。

具体代码如下:

collector.OnHTML("ol.grid_view", func(element *colly.HTMLElement) {
    // 依次遍历所有的li节点
    element.DOM.Find("li").Each(func(i int, selection *goquery.Selection) {
        href, found := selection.Find("div.hd > a").Attr("href")
        // 如果找到了详情页,则继续下一步的处理
        if found {
            parseDetail(collector, href, writer)
            log.Println(href)
        }
    })
})

4.3 详情页内容获取

我们要获取的内容:排名Idx,标题title,年份year,基本信息info,评分rating,地址url。

分析完页面Dom结构之后,整个抓取代码的编写就变得简单了起来。

具体信息获取代码如下:

collector.OnHTML("body", func(element *colly.HTMLElement) {
    selection := element.DOM.Find("div#content")
    idx := selection.Find("div.top250 > span.top250-no").Text()
    title := selection.Find("h1 > span").First().Text()
    year := selection.Find("h1 > span.year").Text()
    info := selection.Find("div#info").Text()
    info = strings.ReplaceAll(info, " ", "")
    info = strings.ReplaceAll(info, "\n", "; ")
    rating := selection.Find("strong.rating_num").Text()
	...

4.4 后续处理

可对抓取到的数据进行清洗、整理,然后入库等操作。本例仅将数据存储至csv文件。

五、代码及结果展示

源码已上传至GitHub, Gitee

抓取数据结果如下:

六、后记

其实编写爬虫时,最耗时的是页面Dom结构分析的过程。代码编写只是整个抓取过程的实现部分,并不会耗费很多的时间。

如果耗费的很多的时间(假装在说别人😄),那么你需要去看文档和官方示例了。

当你的才华和能力,不足以支撑你的梦想的时候,请静下心来学习。

欢迎留言交流~

🏆 技术专题第二期 | 我与 Go 的那些事......