引入
在具体讲解爬虫知识之前,我们首先简单回顾下前面Http编程。
-
服务器端:利用net/http包编写一个服务器,让浏览器请求我们的服务器。
基本流程:
-
接受请求后分析协议
-
回发相应数据给浏览器。
-
-
客户端:利用net/http包编写一个客户端(用于模拟浏览器行为), 向互联网上现有的服务器发送请求,获取服务器上的数据。
基本的流程:
-
构建、发送请求链接
-
获取服务器返回的响应数据
-
过滤、保存、使用得到的数据
-
关闭请求链接。
-
小提示:我们利用客户端程序是获取的服务器数据和利用浏览器获取的数据是一模一样的,只不过浏览器会把文件按照一定的格式显示出来而已。
爬取百度贴吧
爬取策略分析
-
明确URL。
这里我们以“吃鸡”游戏这个贴吧为例,分析地址规律如下:http://tieba.baidu.com/f?kw=%E7%BB%9D%E5%9C%B0%E6%B1%82%E7%94%9F&ie=utf-8&pn=0 //第一页
http://tieba.baidu.com/f?kw=%E7%BB%9D%E5%9C%B0%E6%B1%82%E7%94%9F&ie=utf-8&pn=50 //第二页
http://tieba.baidu.com/f?kw=%E7%BB%9D%E5%9C%B0%E6%B1%82%E7%94%9F&ie=utf-8&pn=100 //第三页
总结规律:“下一页”地址是“前一页”地址 + 50
-
发送请求,获取响应 (将所有的网站的内容全部爬下来)
-
提取数据,去掉对我们没用处的数据
-
处理数据(按照我们想要的方式存储和使用)
段子爬取
package main
import (
"net/http"
"fmt"
"io/ioutil"
"regexp"
"strconv"
)
func main(){
StartWork(1, 20)
}
func StartWork(start, end int){
gotIt := make(chan int)
for i := start; i <= end; i++{
url := "https://www.pengfu.com/xiaohua_" + strconv.Itoa(i) + ".html"
go SpiderOne(url, i, gotIt)
}
for i := start; i <= end; i++{
index := <-gotIt
fmt.Printf("第%d页爬取ok\n", index)
}
}
func SpiderOne(url string, index int, gotIt chan int){
//获取网络请求的结果
content, err := HttpGet(url)
if err!= nil{
fmt.Println("spider one err:", err)
return
}
//提取段子标题和内容
matchedBytes := ExtractInfo(content, `<h1 class="dp-b"><a href=(?s:.*?)>(?s:(.*?))</a>(?s:.*?)<div class="content-img clearfix pt10 relative">(?s:(.*?))</div>`)
//拼接段子标题和内容,并写入文件
fileContent := make([]byte, 0)
for _, oneMatched := range matchedBytes{
fileContent = append(fileContent, []byte("\r\n标题:")...)
fileContent = append(fileContent, oneMatched[1]...)
fileContent = append(fileContent, []byte("\r\n内容:")...)
fileContent = append(fileContent, oneMatched[2]...)
}
fileName := "D:/spiderResult/" + strconv.Itoa(index) + ".txt"
Save2File(fileContent, fileName)
gotIt <- index
}
/****************************************************
*函数功能:
获取给定的url指向的网络资源,返回结果的字节切片和遇到的错误
*****************************************************/
func HttpGet(url string) (result []byte, err error){
resp, err := http.Get(url)
if err != nil{
fmt.Println("http get err:", err)
return
}
defer resp.Body.Close()
content, err := ioutil.ReadAll(resp.Body)
if err != nil{
fmt.Println("read all err:", err)
return
}
return content, nil
}
/****************************************************
*函数功能:
* 把content内的字节切片存储至path指向的文件中,返回遇到的错误
****************************************************/
func Save2File(content []byte, path string,)(err error){
err = ioutil.WriteFile(path, content, 0666)
if err != nil{
fmt.Println("ioutil WriteFile err:", err)
}
return
}
/*****************************************************
*函数功能:
* 从content的字节切片中提取符合re正则表达式的文本输出
******************************************************/
func ExtractInfo(content []byte, re string)(matchedBytes [][][]byte){
r := regexp.MustCompile(re)
matchedBytes = r.FindAllSubmatch(content, -1)
return
}
爬取豆瓣
//提取电影名称、导演、评分、评论人数、短评
re := `<img width="100" alt="(.*?)"(?s:.*?)<p class="">(?s:(.*?))</p>(?s:.*?)<span class="rating_num" property="v:average">(?s:(.*?))</span>(?s:.*?)<span>(?s:(.*?))人评价</span>(?s:.*?)<span class="inq">(?s:(.*?))</span>`
contents := ExtractInfo(re, rawContent)
//替换网页中的特殊字符,组织最终的有用信息的字符串
fileContent := ""
for _, c := range contents{
for i := 1; i < len(c); i++{
temp := strings.TrimSpace(c[i])
temp = strings.Replace(temp, " ", " ", -1)
temp = strings.Replace(temp, "<br>", "\r\n", -1)
switch i {
case 1:
fileContent += "电影名称:"
case 3:
fileContent += "评分:"
case 4:
fileContent += "短评:"
}
fileContent= fileContent + temp + "\r\n"
}
fileContent += "\r\n"
}
//把最终生成的字符串写入对应文件
Save2File(path ,fileContent)
gotIt <- index
}