Golang 实现网络爬虫,简单易懂

爬虫工作流程

		1.明确目标,url
		2.发送请求获取应答数据
		3.保存,过滤,提取有用信息
		4.使用分析,得到的数据

首先看一个抓取网页生成到本地文件的简单例子

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"project/wdzinx/wdlog"
	"strconv"
	"sync"
)

var wg sync.WaitGroup

//HTTPGet ..
func HTTPGet(url string) (result string, err error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close() //结束关闭body
	//循环读取网页数据
	buf := make([]byte, 4096)
	for {
		n, err := resp.Body.Read(buf)
		if n == 0 {
			fmt.Println("读取网页完成")
			break
		}
		if err != nil && err != io.EOF {
			return "", err
		}
		result += string(buf[:n])
	}
	return
}

//Crawlerone ...
func Crawlerone(i int) {
	url := "https://tieba.baidu.com/f?kw=%E5%88%80%E9%94%8B%E9%93%81%E9%AA%91&ie=utf-8&pn=" + strconv.Itoa((i-1)*50)
	result, err := HTTPGet(url)
	if wdlog.WDShowError(err) {
		return
	}
	//将读到的整网页数据保存成文件
	file, err := os.Create("./file/第" + strconv.Itoa(i) + "页" + ".html")
	if err != nil {
		fmt.Println("os.create fail:", err)
		return
	}
	file.WriteString(result)
	file.Close()
	fmt.Println("第", i, "页爬取完成")
	wg.Done()
}
func worker(start, end int) {
	fmt.Println("开始干活,从第", start, "页到第", end, "页......")
	for i := start; i <= end; i++ {
		wg.Add(1)
		go Crawlerone(i)
	}
	fmt.Println("全部网页爬取完成")
	wg.Wait()
}

func main() {
	//起始,终止页
	var start, end int
	fmt.Print("请输入起始页(>=1)----->")
	fmt.Scan(&start)
	fmt.Print("请输入终止页(>=1)----->")
	fmt.Scan(&end)

	worker(start, end)
}

上面的的例子就是一个最简单的爬虫,去掉了数据处理的模块,其中网址的url,50一次增加,这是网站自身决定的规律(横向爬取,以页为单位),每次爬虫的时候都需要我们分析不同网站的url变换规律,抓取数据如下图
在这里插入图片描述
复杂的例子还需要我们会一点正则

简单正则讲解

实际上,能用自带string解决的问题就不用正则,复杂的才需要正则去解,这里简单说一下正则,实际上强烈不推荐深入学习正则。

字符类
.		匹配任意一个字符
[]		匹配中括号中的任意一个字符
-		在[]中表示匹配任意一个16进制数字
^		在[]开头,表示匹配非括号中字符的任意字符
数量限定符
?	紧跟它前面的单元匹配一次货零次 如[0-9]?\.[0-9] 可以匹配到  1.1   2.0   .5
+	紧跟在它前面的单元匹配一次或多次 如 [a-z0-9A-z_.-]+@[a-z0-9A-z_.-]+\.[a-z0-9A-z_.-]+  匹配email
*	紧跟在它前面的单元匹配零次或多次
{n,m}紧跟在它前面的单元最少匹配n次,最多匹配m次  或者{n,} 或固定{n} n次  注{,m} 已过期  
		如[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}  匹配ip地址
其他特殊字符
\	转义字符
()	将()内中的内容计算为一个单元  如([0-9]{1,3}\.){3}[0-9]{1,3} 匹配ip地址
|	表示或者

更多学习可以看官网: https://code.google.com/p/re2/wiki/Syntax
国内大佬把下来的:http://www.sun190.com/2015/01/re2-正则表达式/

golang中正则用法

go中的正则表达式的标准RE2,使用只需2步

第一步:解析编译正则表达式 ,函数:

func regexp.MustCompile(str string) * Regexp

第二步:执行,函数:

func (reg * Regexp) FindAllString(s string,n int)[][]string

n:匹配的次数,-1表示比配所有

更多正则函数,说明,regexp包说明见连接:https://studygolang.com/pkgdoc

这里主要提供一个通用的
(?s:(.*?)) 
//?s:  表示单行模式,该模式下 . 可以匹配任意符号,包括换行符
//*?   重复>=0次匹配x,越少越好(优先跳出重复)
//aaaa(?s:(.*?)) bbbb  表示以aaaa开头的,以bbbb结束 中间的字符

那下面我们抓一下豆瓣电影排行的数据,

数据处理的例子

package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"os"
	"project/wdzinx/wdlog"
	"regexp"
	"strconv"
	"sync"
)

var wg sync.WaitGroup

//HTTPGet ..
func HTTPGet(url string) (result string, err error) {
//-----------------将http请求伪装成浏览器的
	b := new(bytes.Buffer)
	request, err := http.NewRequest("GET", url, b)
	if err != nil {
		return "", err
	}
	request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36")
	request.Header.Set("content-type", "text/vnd.wap.wml")
//-----------------伪装完成-----------
	client := http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close() //结束关闭body
	//循环读取网页数据
	buf := make([]byte, 4096)
	for {
		n, err := resp.Body.Read(buf)
		if n == 0 {
			fmt.Println("读取网页完成")
			break
		}
		if err != nil && err != io.EOF {
			return "", err
		}
		result += string(buf[:n])
	}
	return
}

//FindInfo ..
func FindInfo(i int, sdata string) {
	//解析数据---使用正则表达式
	regexpn := regexp.MustCompile(`<img width="100" alt="(?s:(.*?))"`)
	moviename := regexpn.FindAllStringSubmatch(sdata, -1)
	regexpd := regexp.MustCompile(`导演:\s?(?s:(.*?))\s`)
	moviedaoyan := regexpd.FindAllStringSubmatch(sdata, -1)
	regexpp := regexp.MustCompile(`average">(?s:(.*?))</span>`)
	moviepingfen := regexpp.FindAllStringSubmatch(sdata, -1)
	regexpr := regexp.MustCompile(`<span>([0-9]{0,20})人评价</span>`)
	movieprpj := regexpr.FindAllStringSubmatch(sdata, -1)

	//将读到的整网页数据保存成文件
	file, err := os.OpenFile("../../file/第"+strconv.Itoa(i)+"页"+".txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("os.create fail:", err)
		return
	}
	//fmt.Println("moviename", len(moviename))
	for n, info := range moviename {
		_, err = file.WriteString("电影名称:" + info[1] + " -->导演:" + moviedaoyan[n][1] + " --->评分:" + moviepingfen[n][1] + " --->评价人数:" + movieprpj[n][1] + "\n")
		if err != nil {
			fmt.Println("写入错误")
			break
		}
	}
	file.Close()
}

//Crawlerone ...
func Crawlerone(i int) {
	url := "https://movie.douban.com/top250?start=" + strconv.Itoa((i-1)*25) + "&filter="
	result, err := HTTPGet(url)
	if wdlog.WDShowError(err) {
		return
	}
	FindInfo(i, result)
	fmt.Println("第", i, "页爬取完成")
	wg.Done()
}
func worker(start, end int) {
	fmt.Println("开始干活,从第", start, "页到第", end, "页......")
	for i := start; i <= end; i++ {
		wg.Add(1)
		go Crawlerone(i)
	}
	fmt.Println("全部网页爬取完成")
	wg.Wait()
}

func main() {
	//起始,终止页
	var start, end int
	fmt.Print("请输入起始页(>=1)----->")
	fmt.Scan(&start)
	fmt.Print("请输入终止页(>=1)----->")
	fmt.Scan(&end)

	worker(start, end)
}

其实上面的例子也很简单,就是在上一个的基础上,加入了数据采集功能。使用正则完成。
豆瓣比贴吧安全性强一点,但也强不了多少,把http的请求行伪装一下就分辩不出来了。
采集到的数据截图如下:
在这里插入图片描述

图片抓取的栗子

那基于上面的方法,我们是不是可以将网页中的一些url信息提取出来,再打开这些url,进行纵向抓取,下面的例子是百度图片的抓取方法,采用的是传统分页式,

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"project/wdzinx/wdlog"
	"regexp"
	"strconv"
	"sync"
	"sync/atomic"
)

var key string
var num int
var wg sync.WaitGroup
var name int32 = 0

//正则表达式获取图片url
func getinfo(data string) [][]string {
	rege := regexp.MustCompile(`{"thumbURL":"(?s:(.*?))"`)
	info := rege.FindAllStringSubmatch(data, -1)
	return info
}

func worker(url string) {
	//打开第一层url
	resp, err := http.Get(url)
	if wdlog.WDShowError(err) {
		fmt.Println("url 拉取错误")
		return
	}
	var data string
	buf := make([]byte, 4096)
	for {
		n, err := resp.Body.Read(buf)
		if n == 0 {
			break
		}
		if wdlog.WDShowError(err) || err == io.EOF {
			break
		}
		data += string(buf[:n])
	}
	resp.Body.Close()
	imglist := getinfo(data)
	//将正则摘取出来的图片url再次打开下载
	for _, urlname := range imglist {
		image, err := http.Get(urlname[1])
		if wdlog.WDShowError(err) {
			fmt.Println("url 拉取错误")
			return
		}
		//名字从1开始取值。原子互斥,防止文件重名,也可以直接用url做名字
		imgpath := "../../file/" + strconv.Itoa(int(atomic.AddInt32(&name, 1))) + `.jpg`
		imgfile, err := os.OpenFile(imgpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
		if wdlog.WDShowError(err) {
			continue
		}
		imgbuf := make([]byte, 4096)
		for {
			n, err := image.Body.Read(imgbuf)
			if n == 0 || err != nil {
				break
			}
			imgfile.Write(imgbuf[:n])
		}
		image.Body.Close()
		imgfile.Close()
	}
	wg.Done()
}

func main() {

	fmt.Printf("请输入爬取关键字--->")
	fmt.Scan(&key)
	fmt.Printf("请输入要爬取页数--->")
	fmt.Scan(&num)
	//循环爬取每页
	for i := 0; i <= num; i++ {
		url := `https://image.baidu.com/search/flip?tn=baiduimage&ie=utf-8&word=` + key + `&pn=` + strconv.Itoa(i*20) + `&gsm=&ct=&ic=0&lm=-1&width=0&height=0`
		wg.Add(1)
		go worker(url)
	}
	wg.Wait()
}

操作截图:
在这里插入图片描述
爬取得到的图片
在这里插入图片描述
持续更新中…

发布了53 篇原创文章 · 获赞 5 · 访问量 2333

猜你喜欢

转载自blog.csdn.net/qq_25490573/article/details/103211490