爬虫
爬虫简介:
是一个程序,用来获取指定网站数据信息。
- 明确 url 。确定爬取对象
- 发送请求。获取服务器响应数据。
- 解析数据,提取有用数据内容。
- 保存、分析数据结果。
今天我们用go并发来简单写一个小Demo来爬取一下豆瓣评分网站的数据
首先来思路分析一下该怎么做:
- 明确 url。找出url之间的一些小规律,比如豆瓣的url规律如下
https://movie.douban.com/top250?start=0&filter= 1
https://movie.douban.com/top250?start=25&filter= 2
https://movie.douban.com/top250?start=50&filter= 3
https://movie.douban.com/top250?start=75&filter= 4
之间的规律很好找
待提取字符特性:
提示用户指定爬取起始、终止页
封装 doWork 函数, 按起始、终止页面循环爬取网页数据
组织每个网页的 url。 下一页 = +25
封装函数 HttpGetDB(url)result,err { http.Get(url), resp.Body.Read(buf), n==0 break, result+= string(buf[:n}) }
爬取网页的所有数据 通过result 返回给调用者。
解析、编译正则表达式 —— 提取 “电影名称”fileNames 传出的是[][]string , 下标为【1】是不带匹配参考项。
解析、编译正则表达式 —— 提取 “评分”传出的是[][]string , 下标为【1】是不带匹配参考项。
解析、编译正则表达式 —— 提取 “评价人数”传出的是[][]string , 下标为【1】是不带匹配参考项。
封装函数,将上述内容写入文件。save2File( [][]string)
创建并发go程 提取所有网页数据。
创建阻止主go程提取退出的 channel , SpiderPageDB() 末尾处,写channel
doWork 中,添加新 for ,读channel
代码实现:
首先可以封装一个函数来爬取多少页到多少页,并且每一页构造一个go程
func doWork(start, end int) {
page := make(chan int)
// 循环创建多个goroutine,提高爬取效率
for i:=start; i<=end; i++ {
go SpiderPageDB(i, page)
}
// 循环读取 channel, 协调主、子go程调用顺序
for i:=start; i<=end; i++ {
fmt.Printf("第%d页爬取完成\n", <-page)
}
}
获取每一页的数据函数
func HttpGetDB(url string) (result string, err error) {
resp, err1 := http.Get(url)
if err1 != nil {
err = err1
return
}
defer resp.Body.Close()
buf := make([]byte, 4096)
for {
n, err2 := resp.Body.Read(buf)
if n == 0 {
break
}
if err2 != nil && err2 != io.EOF {
err = err2
return
}
result += string(buf[:n])
}
return
}
得到每一个数据然后分析数据解析数据
func SpiderPageDB(i int, page chan<- int) {
url := "https://movie.douban.com/top250?start=" + strconv.Itoa((i-1)*25) + "&filter="
result, err := HttpGetDB(url)
if err != nil {
fmt.Println("HttpGetDB err:", err)
return
}
// 编译、解析正则表达式 —— 电影名
ret1 := regexp.MustCompile(`<img width="100" alt="(?s:(.*?))" src="` )
// 提取有效信息
fileNames := ret1.FindAllStringSubmatch(result, -1)
// 编译、解析正则表达式 —— 分数
pattern := `<span class="rating_num" property="v:average">(.*?)</span>`
ret2 := regexp.MustCompile(pattern )
// 提取有效信息
fileScore := ret2.FindAllStringSubmatch(result, -1)
/* for _, one := range fileScore {
fmt.Println("fileName:", one[1])
}*/
// 编译、解析正则表达式 —— 评分人数
ret3 := regexp.MustCompile(`<span>(\d*?)人评价</span>`)
// 提取有效信息
peopleNum := ret3.FindAllStringSubmatch(result, -1)
// 写入到一个文件中
save2file(i, fileNames, fileScore, peopleNum)
page <- i // 写入channel ,协调主go程与子go程调用顺序。
}
最后保存数据到文件中
func save2file(idx int, fileNames, fileScore, peopleNum [][]string) {
// 组织保存文件路径及名程
path := "D:/ecec/第" + strconv.Itoa(idx) + "页.txt"
f, err := os.Create(path)
if err != nil {
fmt.Println("Create err:", err)
return
}
defer f.Close()
// 获取 一个网页中的条目数 —— 25
n := len(fileNames)
// 写一行标题
f.WriteString("电影名称" + "\t" + "评分" + "\t" + "评价人数" + "\n")
// 依次按序写入电影相关条目。
for i:=0; i<n; i++ {
f.WriteString(fileNames[i][1] + "\t" + fileScore[i][1] + "\t" + peopleNum[i][1] + "\n")
}
}
最后直接在main里面就可以调用
完整代码如下
package main
import (
"fmt"
"strconv"
"net/http"
"io"
"regexp"
"os"
)
func HttpGetDB(url string) (result string, err error) {
resp, err1 := http.Get(url)
if err1 != nil {
err = err1
return
}
defer resp.Body.Close()
buf := make([]byte, 4096)
for {
n, err2 := resp.Body.Read(buf)
if n == 0 {
break
}
if err2 != nil && err2 != io.EOF {
err = err2
return
}
result += string(buf[:n])
}
return
}
func SpiderPageDB(i int, page chan<- int) {
url := "https://movie.douban.com/top250?start=" + strconv.Itoa((i-1)*25) + "&filter="
result, err := HttpGetDB(url)
if err != nil {
fmt.Println("HttpGetDB err:", err)
return
}
// 编译、解析正则表达式 —— 电影名
ret1 := regexp.MustCompile(`<img width="100" alt="(?s:(.*?))" src="` )
// 提取有效信息
fileNames := ret1.FindAllStringSubmatch(result, -1)
// 编译、解析正则表达式 —— 分数
pattern := `<span class="rating_num" property="v:average">(.*?)</span>`
ret2 := regexp.MustCompile(pattern )
// 提取有效信息
fileScore := ret2.FindAllStringSubmatch(result, -1)
/* for _, one := range fileScore {
fmt.Println("fileName:", one[1])
}*/
// 编译、解析正则表达式 —— 评分人数
ret3 := regexp.MustCompile(`<span>(\d*?)人评价</span>`)
// 提取有效信息
peopleNum := ret3.FindAllStringSubmatch(result, -1)
// 写入到一个文件中
save2file(i, fileNames, fileScore, peopleNum)
page <- i // 写入channel ,协调主go程与子go程调用顺序。
}
func save2file(idx int, fileNames, fileScore, peopleNum [][]string) {
// 组织保存文件路径及名程
path := "C:/exec/第" + strconv.Itoa(idx) + "页.txt"
f, err := os.Create(path)
if err != nil {
fmt.Println("Create err:", err)
return
}
defer f.Close()
// 获取 一个网页中的条目数 —— 25
n := len(fileNames)
// 写一行标题
f.WriteString("电影名称" + "\t" + "评分" + "\t" + "评价人数" + "\n")
// 依次按序写入电影相关条目。
for i:=0; i<n; i++ {
f.WriteString(fileNames[i][1] + "\t" + fileScore[i][1] + "\t" + peopleNum[i][1] + "\n")
}
}
func doWork(start, end int) {
page := make(chan int)
// 循环创建多个goroutine,提高爬取效率
for i:=start; i<=end; i++ {
go SpiderPageDB(i, page)
}
// 循环读取 channel, 协调主、子go程调用顺序
for i:=start; i<=end; i++ {
fmt.Printf("第%d页爬取完成\n", <-page)
}
}
func main() {
var start, end int
fmt.Print("请输入爬取起始页面(>=1):")
fmt.Scan(&start)
fmt.Print("请输入爬取终止页面(>=start):")
fmt.Scan(&end)
doWork(start, end)
}