赞
踩
可能很多人都觉得爬虫是 Python 的专属技能,但其实使用 Go 语言可能会实现更加好的效果
在开始实现爬虫之前我们必须明白一件事,那就是爬虫是什么。网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。
写爬虫程序有以下几点值得注意
明确目标URL
发送请求,获取应答数据包
保存过滤数据
使用分析数据
以重庆邮电大学吧为例
根据 URL 可以发现规律,每次在 pn 后面多了50。
https://tieba.baidu.com/f?kw=重庆邮电大学&ie=utf-8&pn=0
https://tieba.baidu.com/f?kw=重庆邮电大学&ie=utf-8&pn=50
https://tieba.baidu.com/f?kw=重庆邮电大学&ie=utf-8&pn=100
在找到规律之后我们就可以具体实现了,我们需要使用到 net/http 包来获取页面的数据并通过 IO 操作将其保存在文件中。
package main import ( "fmt" "io" "net/http" "os" "strconv" ) func httpGet(url string) (res string, err error) { resp, err1 := http.Get(url) if err != nil { err = err1 //内部错误传出 return } defer resp.Body.Close() //循环读取数据 传出给调用者 buf := make([]byte, 4096) for { n, err2 := resp.Body.Read(buf) if n == 0 { fmt.Println("读取完成") break } if err2 != nil && err2 != io.EOF { err = err2 return } //累加数据 res += string(buf[:n]) } return } func query(start int, end int) { fmt.Printf("正在爬取%d页到%d页...\n", start, end) //循环爬取数据 for i := start; i <= end; i++ { url := "https://tieba.baidu.com/f?kw=%E9%87%8D%E5%BA%86%E9%82%AE%E7%94%B5%E5%A4%A7%E5%AD%A6&ie=utf-8&pn=" + strconv.Itoa((i-1)*50) res, err := httpGet(url) if err != nil { fmt.Println("err = ", err) continue } //保存为文件 f, err := os.Create("第" + strconv.Itoa(i) + "页" + ".html") if err != nil { fmt.Println("err = ", err) continue } f.WriteString(res) f.Close() //保存好一个文件就关闭一个 } } func main() { //指定起始终止页 var start, end int fmt.Print("请输入爬取的起始页(>=1):") fmt.Scan(&start) fmt.Print("请输入爬取的终止页(>=start):") fmt.Scan(&end) query(start, end) }
Go 语言的一大语言特色就是天然支持高并发,而爬虫和并发正可以完美结合,高并发进行爬取可以极大提高爬虫的效率。而高并发的实现并不困难,我们只需要去开启一个协程并使其与主协程同步即可,其他的操作都与非并发版的类似。
package main import ( "fmt" "io" "net/http" "os" "strconv" ) func httpGet(url string) (res string, err error) { resp, err1 := http.Get(url) if err != 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 } //累加数据 res += string(buf[:n]) } return } //爬取单个页面的函数 func spiderPage(i int, page chan int) { url := "https://tieba.baidu.com/f?kw=%E9%87%8D%E5%BA%86%E9%82%AE%E7%94%B5%E5%A4%A7%E5%AD%A6&ie=utf-8&pn=" + strconv.Itoa((i-1)*50) res, err := httpGet(url) if err != nil { fmt.Println("err = ", err) return } //保存为文件 f, err := os.Create("第" + strconv.Itoa(i) + "页" + ".html") if err != nil { fmt.Println("err = ", err) return } f.WriteString(res) f.Close() //保存好一个文件就关闭一个 page <- i //与主协程完成同步 } func query(start int, 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.Print("请输入爬取的起始页(>=1):") fmt.Scan(&start) fmt.Print("请输入爬取的终止页(>=start):") fmt.Scan(&end) query(start, end) }
正则表达式,又称规则表达式 , (Regular Expression,在代码中常简写为regex、regexp或RE),是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符"),是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,通常被用来检索、替换那些符合某个模式(规则)的文本。
许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开来的,后来在广泛运用于Scala 、PHP、C# 、Java、C++ 、Objective-c、Perl 、Swift、VBScript 、Javascript、Ruby 以及Python等等。正则表达式通常缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen。
package main import ( "fmt" "regexp" ) func main() { str := "abc a7c mfc cat 8ca azc cba" //解析 ret := regexp.MustCompile(`a.c`) //提取 res := ret.FindAllStringSubmatch(str, -1) //打印 fmt.Println(res) }
//输出
[[abc] [a7c] [azc]]
进程 已完成,退出代码为 0
package main import ( "fmt" "regexp" ) func main() { str := "3.14 123.123 .68 haha 1.0 abc 7. ab.3 66.6 123." //解析 ret := regexp.MustCompile(`[0-9]+.[0-9]+`) //提取 res := ret.FindAllStringSubmatch(str, -1) //打印 fmt.Println(res) }
//输出
3.14] [123.123] [1.0] [66.6]]
进程 已完成,退出代码为 0
package main import ( "fmt" "regexp" ) func main() { str := `<div class="wrapper"> <ul style='margin-left: 3px;' id='main-menu' > <li><a href="index.php">首 页</a></li> <li ><a href="user.php" >个人服务</a></li> <li><a href="jwglFiles/index.php" target='_blank' >教学管理文件</a></li> <li><a href="#">培养方案</a> <ul> <li><a href="pyfa2020/index.php" target='_blank'>2020版培养方案</a></li> <li><a href="pyfa/index.php" target='_blank'>2016版培养方案</a></li> <li><a href="infoNavi.php?dId=000303" target='_blank'>其他版培养方案</a></li> <li><a href="lxs/pyfa/index.php" target='_blank'>留学生</a></li> </ul> </li> <li><a href="bszn/index.php" target='_blank' >办事指南</a></li> <li><a href="kebiao/index.php" target='_bank'>课表查询</a></li> <li><a href="jxjc/index.php" target='_bank' >进程与调停课</a></li> <li><a href="ksap/index.php" target='_bank' >考试安排</a></li> <li><a href="infoNavi.php?dId=000308" target='_bank' >表格下载</a></li> <li><a href="infoNavi.php?dId=000310" target='_bank' >校历</a></li> <!-- <li ><a href="history/index.php" target="_blank">历史数据</a></li> <li><a href="websiteNavi.php" class="topMenu" >功能网站</a></li> <li><a href="historyData.php" class="topMenu" >数据中心</a></li> <li ><a href="jwzxlxs/index.php" target="_blank">留学生</a></li> --> <li><a href="infoNavi.php?dId=0007" target='_bank' >党建工作</a></li> <li><a href="contact.php" class="popInfo" >联系我们</a></li> </ul> <div style="float: right;color: rgb(221, 221, 221);padding: 9px 10px;">` //解析 ret := regexp.MustCompile(`<li><a href="(?s:(.*?))"`) //提取 res := ret.FindAllStringSubmatch(str, -1) //打印 for _, one := range res { //fmt.Println("one[0]=", one[0]) fmt.Println(one[1])//返回的是一个数组 } }
//输出内容 index.php jwglFiles/index.php # pyfa2020/index.php pyfa/index.php infoNavi.php?dId=000303 lxs/pyfa/index.php bszn/index.php kebiao/index.php jxjc/index.php ksap/index.php infoNavi.php?dId=000308 infoNavi.php?dId=000310 websiteNavi.php historyData.php infoNavi.php?dId=0007 contact.php
查标题正则表达式
class="j_th_tit ">(?s:(.*?))</a>
package main import ( "fmt" "io" "net/http" "os" "regexp" "strconv" ) func httpGet(url string) (res string, err error) { resp, err1 := http.Get(url) if err != 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 } //累加数据 res += string(buf[:n]) } return } func saveFile(i int, title [][]string) { f, err := os.Create("第" + strconv.Itoa(i) + "页.txt") if err != nil { fmt.Println("err = ", err) return } defer f.Close() n := len(title) for i := 0; i < n; i++ { f.WriteString(title[i][1] + "\n") } } //爬取单个页面的函数 func spiderPage(i int, page chan int) { url := "https://tieba.baidu.com/f?kw=%E9%87%8D%E5%BA%86%E9%82%AE%E7%94%B5%E5%A4%A7%E5%AD%A6&ie=utf-8&pn=" + strconv.Itoa((i-1)*50) res, err := httpGet(url) if err != nil { fmt.Println("err = ", err) return } ret := regexp.MustCompile(`class="j_th_tit ">(?s:(.*?))</a>`) titles := ret.FindAllStringSubmatch(res, -1) saveFile(i, titles) page <- i //与主协程完成同步 } func query(start int, 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.Print("请输入爬取的起始页(>=1):") fmt.Scan(&start) fmt.Print("请输入爬取的终止页(>=start):") fmt.Scan(&end) query(start, end) }
如果有没弄清楚的地方欢迎大家向我提问,我都会尽力解答
这是我的 GitHub 主页 github.com/L2ncE
欢迎大家 Follow/Star/Fork 三连
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。