爬取豆瓣电影
爬取豆瓣电影
爬取网页简单工作流程:
明确URL (请求的url地址,明确爬什么)
发送请求,获取响应数据 (将网站的所有内容全部爬取)
保存响应数据,提取有用信息
处理数据(存储、使用)
双向爬取
首先我们来熟悉两个基础技术名词:横向爬取 和 纵向爬取。
横向爬取:
所谓横向爬取,是指在爬取的网站页面中,以“页”为单位,找寻该网站分页器规律。一页一页的爬取网站数据信息。大多数网站,采用分页管理模式。针对这类网站,首先要确立横向爬取方法。
纵向爬取:
纵向爬取,是指在一个页面内,按不同的“条目”为单位。找寻各条目之间的规律。一条一条的爬取一个网页中的数据信息。也就是同时爬取一个页面内不同类别数据。
爬取电影评分
类似的,我们再来试试豆瓣电影爬取。首先打“豆瓣”网站首页,然后单击菜单中“排行榜”。在页面右部偏下的位置有一个“豆瓣电影TOP250”,这里记录了全球影迷评出的世界级经典影片。当你剧荒的时候,参考下这个排行榜,选取任意一部来观赏下,都不会有浪费生命的感觉。
点击“全部”进入前250部电影排行榜页面。URL为:movie.douban.com/top250 同样,测试下翻页特性:
第二页:movie.douban.com/top250?start=25&a...=
第三页:movie.douban.com/top250?start=50&a...=
第四页:movie.douban.com/top250?start=75&a...=
得出横向爬取条件大致为:“下一页”= “前一页”+ 25。
寻找好的电影,主要参考依据为“分数”和“评分人数”两项。因此,这里我们将电影名、评分分数和该电影的参与评分的评分人数作为我们的爬取目标。
选择任意一个页面,探索纵向爬取条件。总结发现:
电影名:被包裹在 <img width="100" alt="电影名称" 之中
分数:被包裹在 <span class="rating_num" property="v:average">分数</span> 之中
评分人数:被包裹在 <span>评分人数 人评价</span> 之中
同样,可以使用正则表达式 (?s:(.*?))来依次获取 电影名、分数、评分人数三部分数据信息。而后封装文件,将这三部分信息按格式存入即可。
借助前面的经验,我们直接实现一个并发版的爬虫,爬取豆瓣电影评分信息。
示例代码:
package main
import (
"fmt"
"net/http"
"os"
"regexp"
"strconv"
"time"
)
//发起请求,获取网页内容
func HttpGet(url string) (result string, err error) {
resp, err1 := http.Get(url) //发送get请求
if err1 != nil {
err = err1
return
}
defer resp.Body.Close()
//读取网页内容
buf := make([]byte, 4*1024)
for {
n, _ := resp.Body.Read(buf)
if n == 0 {
break
}
result += string(buf[:n]) //累加读取的内容
}
return
}
func SpiderPage(i int, page chan int) {
// 明确爬取的url
fmt.Println("正在抓取第" + strconv.Itoa(1) + "页......")
url := "https://movie.douban.com/top250?start=" + strconv.Itoa((i-1)*25) + "&filter="
time.Sleep(1 * time.Second) // 为防止访问IP 被网站屏闭,降低爬取频率
// 开始爬取页面内容,将结果保存至 result。
result, err := HttpGet(url)
if err != nil {
fmt.Println("HttpGet err = ", err)
return
}
// 解析、编译正则表达式。—— 评价人数
ret1 := regexp.MustCompile(`<span>(?s:(\d*?))人评价</span>`)
// 取关键信息 评价人数
pepoleCount := ret1.FindAllStringSubmatch(result, -1)
// 解析、编译正则表达式。—— 评分
patternScore := `<span class="rating_num" property="v:average">(?s:(.*?))</span>`
ret2 := regexp.MustCompile(patternScore)
filmScore := ret2.FindAllStringSubmatch(result, -1)
// 解析、编译正则表达式。—— 电影名称
patternName := `<img width="100" alt="(?s:(.*?))"`
ret3 := regexp.MustCompile(patternName)
filmName := ret3.FindAllStringSubmatch(result, -1)
// 把内容写入到文件
Save2File(i, pepoleCount, filmScore, filmName)
page <- i //写完一个文件,写i 到channel
}
//把内容写入到文件
func Save2File(i int, pepoleCount, filmScore, filmName [][]string) {
// 新建文件,每一页保存成一个文件
f, err := os.Create("第 " + strconv.Itoa(i) + " 页.txt")
if err != nil {
fmt.Println("os.Create err = ", err)
return
}
defer f.Close()
// 写标题
f.WriteString("电影名称" + "\t\t\t" + "评分" + "\t\t" + "评价人数" + "\t" + "\n")
// 写内容
n := len(pepoleCount)
for i := 0; i < n; i++ {
f.WriteString(filmName[i][1] + "\t\t\t" + filmScore[i][1] + "\t\t" + pepoleCount[i][1] + "\t" + "\n")
}
}
func DoWork(start, end int) {
fmt.Printf("准备爬取第 %d 页到 %d 页的网址\n", start, end)
page := make(chan int)
for i := start; i <= end; i++ {
//定义一个函数,爬主页面
go SpiderPage(i, page)
}
for i := start; i <= end; i++ {
fmt.Printf("第 %d 个页面爬取完成\n", <-page)
}
}
func main() {
var start, end int
fmt.Printf("请输入起始页( >= 1 ):")
fmt.Scan(&start)
fmt.Printf("请输入终止页( >= 起始页 ):")
fmt.Scan(&end)
DoWork(start, end)
}