这是第一版 将爬取来的数据存到本地 没有过滤筛选数据 其中用到了正则和goquery两种方法
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"strings"
// "github.com/antchfx/htmlquery" // xpath方法 没有练习
"github.com/PuerkitoBio/goquery"
)
func main() {
// 指定爬取起始,终止页
var start, end int
fmt.Println("请输入爬取的起始页")
fmt.Scan(&start)
fmt.Println("请输入爬取的终止始页")
fmt.Scan(&end)
// working(start, end) // 这个方法只是将爬来的代码 原封不动的再存到本地 没有分析数据
spider(start, end) //这个使用了 过滤筛选 数据 用到了正则和goquery
}
func working(start, end int) {
for i := start; i < end; i++ {
url := "https://tieba.baidu.com/f?kw=%E9%BB%91tfboys&ie=utf-8&pn=" + strconv.Itoa(i*50)
result, err := httpGet(url)
if err != nil {
fmt.Println("错误")
continue
}
f, err := os.Create("第" + strconv.Itoa(i) + "页.html")
if err != nil {
fmt.Println("create html error ")
return
}
f.WriteString(result)
f.Close() // 这里不能用defer 因为写了defer就要for循环完才能执行defer 而现在是每个循环要创建一个文件 然后再关闭一个资源
}
}
func spider(start, end int) {
f, err := os.OpenFile("sao2015.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
fmt.Println("create file fail")
return
}
for m := 1; m < 13; m++ {
for i := start; i < end; i++ {
fmt.Println(i)
url := "https://www.054gan.com/video/2015-" + strconv.Itoa(m) + "/" + strconv.Itoa(i) + ".html"
result, err := httpGet(url)
if err != nil {
fmt.Println("错误")
continue
}
dom, err := goquery.NewDocumentFromReader(strings.NewReader(result))
if err != nil {
log.Fatalln(err)
}
dom.Find(".cat_pos a font").Each(func(i int, selection *goquery.Selection) {
fmt.Println("i", i, "select text", selection.Text())
f.WriteString(selection.Text() + "----" + url + "\n")
})
}
}
f.Close()
}
func httpGet(url string) (result string, err error) {
resp, err := http.Get(url)
if err != nil {
fmt.Println("错误")
return
}
// 循环读取
defer resp.Body.Close()
buf := make([]byte, 4096)
for {
n, err2 := resp.Body.Read(buf)
if n == 0 || err2 == io.EOF { // 这两个条件满足任一个即可
fmt.Println("读取网页完成")
break // 结束本次循环
}
if err2 != nil && err2 != io.EOF {
err = err2
return // 结束循环 同事结束此方法
}
result += string(buf[:n])
}
return
}
第二版 并发版
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
)
func main() {
// 指定爬取起始,终止页
var start, end int
fmt.Println("请输入爬取的起始页")
fmt.Scan(&start)
fmt.Println("请输入爬取的终止始页")
fmt.Scan(&end)
working(start, end)
}
func working(start, end int) {
page := make(chan int)
for i := start; i <= end; i++ {
go spider(i, page)
}
// 一直会page阻塞在这里 每当回在爬取完成后往page中写入一个值 然后这里就可以循环一次 和select的用法还不一样这里是循环 没有涉及通道的判断
for i := start; i <= end; i++ {
fmt.Printf("第%d页爬取完成 \n", <-page)
}
fmt.Println("爬取结束")
close(page)
}
func spider(i int, page chan int) {
url := "https://tieba.baidu.com/f?kw=%E9%BB%91tfboys&ie=utf-8&pn=" + strconv.Itoa(i*50)
result, err := httpGet(url)
if err != nil {
fmt.Println("错误")
return
}
// fmt.Println("result=", result) // 输出的是字符串
f, err := os.Create("第" + strconv.Itoa(i) + "页.html")
if err != nil {
fmt.Println("create html error ")
return
}
f.WriteString(result)
f.Close() // 这里不能用defer 因为写了defer就要for循环完才能执行defer 而现在是每个循环要创建一个文件 然后再关闭一个资源
page <- i
}
func httpGet(url string) (result string, err error) {
resp, err := http.Get(url)
if err != nil {
fmt.Println("错误")
return
}
// 循环读取
defer resp.Body.Close()
buf := make([]byte, 4096)
for {
n, err2 := resp.Body.Read(buf)
if n == 0 || err2 == io.EOF { // 这两个条件满足任一个即可
fmt.Println("读取网页完成 \n")
break // 结束本次循环
}
if err2 != nil && err2 != io.EOF {
err = err2
return // 结束循环 同事结束此方法
}
result += string(buf[:n])
}
return
}
总结:并发就是加了一个通道 在最后监听 如果每个通道任务执行完毕 那么就像这个通道传入一个数字;这个样监听通达的循环才能执行下去,直至结束。
goquery 总结:先举个小例子 来入门
func main() {
html := `
春晓
春眠不觉晓,
处处闻啼鸟。
夜来风雨声,
花落知多少。
`
dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
if err!=nil{
log.Fatalln(err)
}
dom.Find("p").Each(func(i int, selection *goquery.Selection) {
fmt.Println(selection.Text())
})
}
NewDocumentFromReader() 返回了一个*Document和error。Document代表一个将要被操作的HTML文档。
Find() 是获取当前匹配元素集中每个元素的子代,参数是x选择器 ,它返回一个包含这些匹配元素的新选择对象。在例子中我们使用的是元素选择器P,它会帮我们匹配出所有的p标签 。
Each() 是迭代器,它会循环遍历选择的节点,它的参数是一个匿名函数,匿名函数拥有2个参数,一个是元素的索引位置,还有一个就是选择的结果集匹配到的内容都在它的里面。
Text() 则是获取匹配元素集中的文本内容
选择器: jqery的选择器大多 goquery也能使用 例如
过滤器
类似函数的位置操作
Each(f func(int, *Selection)) *Selection //遍历
EachWithBreak(f func(int, *Selection) bool) *Selection //可中断遍历
Map(f func(int, *Selection) string) (result []string) //返回字符串数组
Attr(), RemoveAttr(), SetAttr() //获取,移除,设置属性的值
AddClass(), HasClass(), RemoveClass(), ToggleClass()
Html() //获取该节点的html
Length() //返回该Selection的元素个数
Text() //获取该节点的文本值
Children() //返回selection中各个节点下的孩子节点
Contents() //获取当前节点下的所有节点
Find() //查找获取当前匹配的元素
Next() //下一个元素
Prev() //上一个元素
高阶用法
doc.Find("#content-left .article ").Each(func(i int, s *goquery.Selection) {
url, _ := s.Find("a[class]").Attr("href")
hash["all_content"] = sub_doc.Find(".content").Text()
}