目的:
搜索CSDN博客,将搜索到博客,爬取博客名称、博客作者、博客链接、发布时间、阅读量。
输入搜索开始页码、结束页码,对每页搜索结果进行多协程爬取。
实现步骤:
1. 博客主页搜索“区块链”,点击第2页,跳转到第2页。得到搜索结果的第2页的网址
https://so.csdn.net/so/search/s.do?p=2&q=%E5%8C%BA%E5%9D%97%E9%93%BE&t=blog&domain=&o=&s=&u=&l=&f=&rbg=0
2.找到搜索结果页码规律,p=2,替换2,就可以跳转到相应搜索结果页面。
3. 查看搜索页码的源码,用正则表达式,提取博客url。
4. 根据url,爬取博客详情,将爬取的结果用正则表达式,提取博客名称、博客作者、博客链接、发布时间、阅读量。
5.将爬取的每页的结果保存到一个文件中。
根据以上步骤,可将本程序代码,应用到其他网页。只要找到源码的规律,替换相关的url和正则表达式,即可爬取相关页码内容。
注意:go语言在实现高并发爬取时,要用到channel类型,是go非常特别的一种类型,类似管道,可以实现线程同步、线程间通信。双通道chan类型管道中无数据时会阻塞线程,所以可以监控页面是否爬取完成,页面爬取完成后在chan中放入数据,在主线程中迭代chan,直到全部页码爬取完成,程序结束。
代码如下:代码要从下开始往上看,执行顺序也是这样,执行步骤如上所述,注释也很清楚
package main
import (
"fmt"
"io"
"net/http"
"os"
"regexp"
"strconv"
// "strings"
)
//爬取博主名author,博客名title,博客地址blogSite,发布日期publishTime,浏览量browingTimes
//封装一个函数,爬取一页内容
func SpiderOnePage(url string) (result string, err error) {
resp, err1 := http.Get(url)
if err1 != nil {
err = err1
fmt.Println("http.Get err1 = ", err1)
return
}
defer resp.Body.Close()
//读取网页的内容
buf := make([]byte, 4*1024)
for {
n, err2 := resp.Body.Read(buf)
if n == 0 {
//如果读取接收,直接break
if err2 == io.EOF {
break
//如果是其他错误,就打印出来
} else {
fmt.Println("resp.Body.Read err2 = ", err2)
break
}
}
result += string(buf[:n]) //读取多少,写多少
}
return
}
//获取博客作者Id
func getAuthor(srcResult string) (author string) {
//原文格式 var username = "ijxr1a64ji53l"
re := regexp.MustCompile(`var username = "(?s:(.*?))"`)
if re == nil {
fmt.Println("regexp.MustCompile err")
}
tmpt := re.FindAllStringSubmatch(srcResult, 1)
for _, data := range tmpt {
author = data[1]
break
}
return
}
//获取博客title
func getBlogTitle(blogResult string) (title string) {
// 原文格式 【资讯】福布斯:旅行积分计划是区块链主要目标,对旅行者来说是好消息 - CSDN博客
re := regexp.MustCompile(`(?s:(.*?)) -`)
if re == nil {
fmt.Println("regexp.MustCompile err")
}
tempTitle := re.FindAllStringSubmatch(blogResult, -1)
//
for _, data := range tempTitle {
title = data[1]
break
}
return
}
//获取博客发布时间
func getBlogPTime(blogResult string) (publishTime string) {
//原文格式 2018年01月30日 16:14:25
re := regexp.MustCompile(`(?s:(.*?))2018年01月12日 00:00:00
re1 := regexp.MustCompile(`(?s:(.*?))5485人阅读
re := regexp.MustCompile(`(?s:(.*?))人阅读428
re1 := regexp.MustCompile(`(?s:(.*?)) 28 {
break
}
result += blogdetail + "\n"
//分隔每个博客
result += "----------------------------------------------------------\n\n"
}
}
return
}
func getBlogDetail(blogUrl string) (blogDetail string, err error) {
//获取具体博客页码结果
blogPage, err2 := SpiderOnePage(blogUrl) //得到页面全部内容
if err2 != nil {
err = err2
fmt.Println("blogResult SpiderOnePage(blogUrl) err2 = ", err2)
return
}
//根据得到的页面全部内容,提取博客详情
//获取博客title
title := getBlogTitle(blogPage)
// fmt.Println("getBlogTitle title = ", title)
blogDetail = "title=" + title + "\n"
//获取博客作者Id
author := getAuthor(blogPage)
// fmt.Printf("blogAuthor = #%v#\n", author)
blogDetail += "author =" + author + "\n"
blogDetail += "blogUrl =" + blogUrl + "\n"
//获取博客发布时间
publishTime := getBlogPTime(blogPage)
// fmt.Println("getBlog publishTime = ", publishTime)
blogDetail += "publishTime =" + publishTime + "\n"
//获取博客阅读量
readTimes := getReadTimes(blogPage)
// fmt.Println("getBlog readTimes = ", readTimes)
blogDetail += "readTimes =" + readTimes + "\n"
return
}
//爬取博客内容,得到每一页的结果
func GetBlog(url string) (result string, err error) {
//获取搜索的页面内容
srcResult, err1 := SpiderOnePage(url)
if err1 != nil {
err = err1
return
}
//爬取搜索的页面
result = getBlogResult(srcResult)
fmt.Println("getBlogResult result= \n", result)
return
}
func SpiderPage(i int, page chan int) {
//搜索返回得到的网址为三部分,中间部分为页码
urlHead := "https://so.csdn.net/so/search/s.do?p="
urlEnd := "&q=%E5%8C%BA%E5%9D%97%E9%93%BE&t=blog&domain=&o=&s=&u=&l=&f=&rbg=0"
url := urlHead + strconv.Itoa(i) + urlEnd
fmt.Println("url=", url)
//爬取博客内容,得到每一页的结果
result, err := GetBlog(url)
if err != nil {
fmt.Println("StartSpider err=", err)
return
}
//把爬取的内容写入到文件
fileName := "csdn区块链博客" + strconv.Itoa(i) + ".txt"
f, err := os.Create(fileName)
if err != nil {
fmt.Println("os.Create err ", err)
return
}
f.WriteString(result)
defer f.Close()
page <- i
}
//在csdn博客主页输入区块链,进行搜索
func StartSpider(startPage, endPage int) {
//for循环,遍历每一页的地址
page := make(chan int)
for i := startPage; i <= endPage; i++ {
go SpiderPage(i, page) //并发的爬取每一页内容
}
for i := startPage; i <= endPage; i++ {
fmt.Printf("第%d个网页爬取完成\n", <-page)
}
}
func main() {
var startPage, endPage int
fmt.Println("请输入爬取的第一页")
fmt.Scan(&startPage)
fmt.Println("请输入爬取的最后一页")
fmt.Scan(&endPage)
StartSpider(startPage, endPage)
}
执行结果:
命令行输入开始和结束页码
输出结果
用Notepad++打开的爬取搜索第3页的结果:
生成的文件:
得到的结果为key-value结果,后续可将其储存到数据库,或者用其他方式调用。
后面将推出将爬取结果保存到数据库的博客。