爬取第一页的全部链接里的下载地址包括下一页
继上一个例子,我们来改写一下
fetcher.go内容
package fetcher
import (
"errors"
"io/ioutil"
"net/http"
)
func Fetch(url string) ([]byte, error) {
client := &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)")
res, err := client.Do(req)
if err != nil {
return nil, err
}
if res.StatusCode != 200 {
return nil, errors.New("Wrong StatusCode!")
}
defer res.Body.Close()
return ioutil.ReadAll(res.Body)
}
//我把URL请求的逻辑都放在了fetch函数中,里面做了一些异常处理。值得说的有2点:
//在Header中设置了User-Agent,让访问看起来更像搜索引擎Bot。如果一个网站希望自己的内容被Google收录那么他就不会拒绝这样的UA的访问。
//需要通过ioutil.ReadAll 读取res的body内容
types.go跟parser.go一个目录
package parser
type Request struct { //保存要爬取的url和对应的函数
Url string
ParserFunc func([]byte) ParserResult
}
type ParserResult struct { //保存爬取的结果
Requests []Request //可以爬取到很多地址加到队列里
Items []interface{} //爬取到的项目名称
}
parser.go内容
package parser
import (
"regexp"
)
const urlRe = `<h5><a target="_blank" href="([^"]+)">([^<]+)</a></h5>`
const mp4ListRe = `thurl="(http:\/\/[^"]+)"`
const nextPageRe = `<a target="_self" href="([^"]+)" class="pagelink_a">下一页</a>`
func Parse(contents []byte) ParserResult {
rep := regexp.MustCompile(urlRe) //匹配正则
match := rep.FindAllStringSubmatch(string(contents), -1) //返回字符串切片,-1表示全部,1表示匹配1个,2就匹配2个
result := ParserResult{} //新建一个队列
for _, m := range match { //遍历字符串切片
result.Items = append(result.Items, m[2]) //获取到匹配的名称加到Items里,m[2]代表正则里的第二个括号里的内容
result.Requests = append(result.Requests, //获取到匹配的url加到队列里,m[1]代表正则里的第一个括号里的内容
Request{
Url: "https://www.193291.com/" + m[1],
ParserFunc: Parsemp4list, //这个队列里的函数是Parsemp4list
})
}
result.Items = append(result.Items, "下一页") //获取到下一页的url加到队列里里
result.Requests = append(result.Requests, ParseNextPage(contents))
return result
}
func Parsemp4list(contents []byte) ParserResult {
rep := regexp.MustCompile(mp4ListRe) //匹配正则
match := rep.FindAllStringSubmatch(string(contents), -1) //返回字符串切片,-1表示全部,1表示匹配1个,2就匹配2个
result := ParserResult{} //新建一个队列
for _, m := range match {
result.Items = append(result.Items, m[1]) //获取到匹配的url加到队列里,m[1]代表正则里的第一个括号里的内容
result.Requests = nil //这里获取到了下载地址就不用再添加队列
}
return result
}
func Nilparser([]byte) ParserResult {
return ParserResult{}
}
func ParseNextPage(contents []byte) Request {
rep := regexp.MustCompile(nextPageRe) //匹配下一页的正则
match := rep.FindStringSubmatch(string(contents)) //返回字符串切片
return Request{
Url: "https://www.193291.com/" + match[1],
ParserFunc: Parse, //这个队列里的函数是Parse
}
}
engine.go内容
package engine
import (
"fmt"
"log"
"crawler0.4/fetcher"
"crawler0.4/parser"
)
func Run(seed parser.Request) { //参数是一个种子
var requests []parser.Request //新建一个队列接收种子
requests = append(requests, seed) //接收参数传来的第一个种子
for len(requests) > 0 { //如果种子不位0
r := requests[0] //取第一个种子
requests = requests[1:] //截掉第一个种子
contents, err := fetcher.Fetch(r.Url) //开始爬取对应种子的页面
if err != nil {
log.Printf("Fetching %s error!", r.Url)
continue //如果获取失败就继续下一个种子
}
parserResult := r.ParserFunc(contents) //队列结构的函数提取结果
requests = append(requests, parserResult.Requests...) //这里可以获取到很多url加到队列中
for _, item := range parserResult.Items {
fmt.Printf("Got item: %s \n", item) //打印队列里的项目
}
fmt.Printf("获取到%d条信息\n", len(parserResult.Items))
}
}
main.go内容
package main
import (
"crawler0.4/engine"
"crawler0.4/parser"
)
func main() {
engine.Run(parser.Request{
Url: "https://www.193291.com/index.php?m=vod-list-id-4-order--by-time-class-0-year-0-letter--area--lang-.html",
ParserFunc: parser.Parse,
})
}
这样就一直获取到url包括下一页,加到队列里爬取下载地址,但不是并发,一个一个获取的,下次改成并发