爬虫
爬虫大体上分为两种类型,一种是爬取网页源码(一般是 HTML),另一种就是模拟请求 API 获取数据。这里我们先介绍爬取网页源码。以百度贴吧为例。
——仅供学习交流使用,如有侵权联系删除。
基本框架
写爬虫其实就是写客户端,只不过请求的对象由浏览器变为自己的编译器了。因此我们要了解爬取网站的URL等信息。
package main
import (
"fmt"
"net/http"
"log"
)
func main (){
res,err := http.Get("path")
if err != nil {
log.Fatalln(err.Error())
return
}
defer res.Body.Close()
body,err := ioutil.ReadAll(res.Body)
fmt.Println(body)
}
基本就是以上的框架,因此我们爬取其他网页源码只需要对其进行扩展即可。
踩点网站
- 第一页
https://tieba.baidu.com/f?kw=%E6%B0%B8%E5%8A%AB%E6%97%A0%E9%97%B4&ie=utf-8&pn=0
- 第二页
https://tieba.baidu.com/f?kw=%E6%B0%B8%E5%8A%AB%E6%97%A0%E9%97%B4&ie=utf-8&pn=50
由此可以推断出其页面之间的规律是pn+=50
初版代码
package main
import (
"fmt"
"log"
"net/http"
"os"
"strconv"
)
func Working(start, end int){
for i := start; i <= end; i++{
url :="https://tieba.baidu.com/f?kw=%E6%B0%B8%E5%8A%AB%E6%97%A0%E9%97%B4&ie=utf-8&pn="+strconv.Itoa((i - 1) * 50)
result,err := HttpGet(url)
if err != nil {
log.Fatalln(err.Error())
return
}
path := "./tieba/"+"第"+strconv.Itoa(i)+"页"+".html"
f,err := os.Create(path)
if err != nil {
log.Fatalln(err.Error())
return
}
f.WriteString(result) //写入
f.Close() //直接关闭
}
}
func HttpGet(url string)(result string, err error){
res, err1 := http.Get(url)
if err1 != nil{
err = err1
return
}
defer res.Body.Close()
buf := make([]byte,4096)
for {
n, err2 := res.Body.Read(buf)
if n == 0 {
fmt.Println("读取完毕")
break
}
if err != nil {
err = err2
return
}
result += string(buf[:n])
}
return
}
func main (){
//https://tieba.baidu.com/f?kw=%E6%B0%B8%E5%8A%AB%E6%97%A0%E9%97%B4&ie=utf-8&pn=50 //第二页
//https://tieba.baidu.com/f?kw=%E6%B0%B8%E5%8A%AB%E6%97%A0%E9%97%B4&ie=utf-8&pn=100 //
start, end := 0, 0
fmt.Print("请输入爬取的起始页:(>=1)")
fmt.Scan(&start)
fmt.Print("请输入爬取的终止页:(>=start)")
fmt.Scan(&end)
Working(start,end)
}
上面的代码只是对网站源码简单的爬取并保存成.html文件,我们并没有对其进行数据的筛选与获取。以下是上面代码的一部分要点:
- 在HttpGet中对URL进行访问,并获取源码
result返回到Working函数中,并且在遇到错误时候把所有的错误返回到Working中。 - 在
working中,创建文件之后写入文件,应该立即把文件关闭,避免其他页面的源码写入。
优化
只有一个线程在爬,肯定是比较慢的,因此我们可以搞并发式爬取,这只需要对原有代码一点点改动即可。
package main
import (
"fmt"
"log"
"net/http"
"os"
"strconv"
)
func pa(i int,ch chan int){
url :="https://tieba.baidu.com/f?kw=%E6%B0%B8%E5%8A%AB%E6%97%A0%E9%97%B4&ie=utf-8&pn="+strconv.Itoa((i - 1) * 50)
result,err := HttpGet(url)
if err != nil {
log.Fatalln(err.Error())
return
}
path := "./tieba/"+"第"+strconv.Itoa(i)+"页"+".html"
f,err := os.Create(path)
if err != nil {
log.Fatalln(err.Error())
return
}
f.WriteString(result) //写入
f.Close() //直接关闭
ch <- i
}
func Working(start, end int,ch chan int){
for i := start; i <= end; i++{
go pa(i,ch)
}
for i := start; i <= end; i++{
fmt.Printf("第%d页读取完毕\n",<-ch)
}
}
func HttpGet(url string)(result string, err error){
res, err1 := http.Get(url)
if err1 != nil{
err = err1
return
}
defer res.Body.Close()
buf := make([]byte,4096)
for {
n, err2 := res.Body.Read(buf)
if n == 0 {
break
}
if err != nil {
err = err2
return
}
result += string(buf[:n])
}
return
}
func main (){
//https://tieba.baidu.com/f?kw=%E6%B0%B8%E5%8A%AB%E6%97%A0%E9%97%B4&ie=utf-8&pn=50 //第二页
//https://tieba.baidu.com/f?kw=%E6%B0%B8%E5%8A%AB%E6%97%A0%E9%97%B4&ie=utf-8&pn=100 //
ch := make(chan int)
start, end := 0, 0
fmt.Print("请输入爬取的起始页:(>=1)")
fmt.Scan(&start)
fmt.Print("请输入爬取的终止页:(>=start)")
fmt.Scan(&end)
Working(start,end,ch )
}
这样的话我们爬取网页源码的速度就快了很多。因此我们未来爬取网页源码的时候就可以使用并发式爬取。
而为了进一步筛选数据,我们可以使用一些第三方库,也可以使用正则表达式。