爬虫工作流程
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()
}
操作截图:
爬取得到的图片
持续更新中…