这是第一版 将爬取来的数据存到本地 没有过滤筛选数据 其中用到了正则和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 := `<html>
<body>
<h1 id="title">春晓</h1>
<p class="content1">
春眠不觉晓,
处处闻啼鸟。
夜来风雨声,
花落知多少。
</p>
</body>
</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也能使用 例如
- 基于HTML Element 元素的选择器 比如 div span p a
- ID 选择器
- Class 选择器
- 属性选择器 p[class=content1] div[my^=zh] div[my~=zh] div[my$=zh] div[my!=zh] div[my*=zh]
- parent > childre(父元素下匹配所有的子元素)
- parent + brother(相邻的同级标签)
- parent childrens(父元素下的所有子元素)
- parent ~ brother(不相邻的同级标签)
过滤器
- container () p:container('a')筛选出内容包含a的p标签
类似函数的位置操作
- 类似函数的位置操作
- Find(selection) *Selection //根据选择器查找节点集
- Eq(index int) *Selection //根据索引获取某个节点集
- First() *Selection //获取第一个子节点集
- Last() *Selection //获取最后一个子节点集
- Next() *Selection //获取下一个兄弟节点集
- NextAll() *Selection //获取后面所有兄弟节点集
- Prev() *Selection //前一个兄弟节点集
- Get(index int) *html.Node //根据索引获取一个节点
- Index() int //返回选择对象中第一个元素的位置
- Slice(start, end int) *Selection //根据起始位置获取子节点集
- 循环遍历选择的节点
-
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()
}