实现一个go语言的简单爬虫来爬取CSDN博文(一)

来源:互联网 发布:淘宝购物运费险怎么用 编辑:程序博客网 时间:2024/06/05 18:39

前言

如何实现一个爬虫系统或则简单的小脚本?一般是定义一个入口页面,然后一个页面会有其他页面的URL,于是从当前页面获取到这些URL加入到爬虫的抓取队列中,然后进入到新页面后再递归的进行上述的操作,其实说来就跟深度遍历或广度遍历一样。
golang由于其编译速度很快,而且对并发(goroutine)的天然支持,配合chan的协程处理,可以很好地实现一个稳定高效的爬虫系统.

用到的包

完全不借助第三方的框架,通过go sdk的标准库来实现一个爬虫应用,主要用到的包

  • net/http 标准库里内建了对http协议的支持,实现了一个http client,可以直接通过其进行get,post等请求
  • strings 不像java的String是一个引用类型,go语言中的字符串类型是一个内建的基础类型, 而且go语言默认只支持UTF-8编码,strings包实现了一些简单的针对utf-8字符串操作的函数
  • regexp go sdk中的正则表达式包
  • io/ioutil io处理的工具包
  • encoding/xml 解析xml的包

channel机制

Golang在并发设计方面参考了C.A.R Hoare的CSP,即Communicating Sequential Processes并发模型理论。 CSP模型的消息传递在收发消息进程间包含了一个交会点,即发送方只能在接收方准备好接收消息时才能发送消息。
golang在其并发实现中,主要是用channel来实现通信的。其中channel包括两种,缓冲的channel和非缓冲的channel.

  • 缓冲的channel:保证往缓冲中存数据先于对应的取数据,简单说就是在取的时候里面肯定有数据,否则就因取不到而阻塞.
  • 非缓冲的channel:保证取数据先于存数据,就是保证存的时候肯定有其他的goroutine在取,否则就因放不进去而阻塞。

Go Channel基本操作语法

Go Channel的基本操作语法如下:

c := make(chan bool) //创建一个无缓冲的bool型Channel
c <- x        //向一个Channel发送一个值<- c          //从一个Channel中接收一个值x = <- c      //从Channel c接收一个值并将其存储到x中x, ok = <- c  //从Channel接收一个值,如果channel关闭了或没有数据,那么ok将被置为false

不带缓冲的Channel兼具通信和同步两种特性,适合协调多个routines。

for/select的基本操作

我们在使用select时很少只是对其进行一次evaluation,我们常常将其与for {}结合在一起使用,并选择适当时机从for{}中退出。

for {        select {        case x := <- somechan:            // … 使用x进行一些操作        case y, ok := <- someOtherchan:            // … 使用y进行一些操作,            // 检查ok值判断someOtherchan是否已经关闭        case outputChan <- z:            // … z值被成功发送到Channel上时        default:            // … 上面case均无法通信时,执行此分支        }} 

range操作

Golang中的for range除了可以迭代一些集合类型还可以来循环从channel中取数据,当channel中无数据时便阻塞当前循环。

for url := range urlChannel {        fmt.Println("routines num = ", runtime.NumGoroutine(), "chan len = ", len(urlChannel))        go Spy(url)}

goroutine

Go语言通过goroutine提供了对于并发编程的非常清晰直接的支持,但goroutine是Go语言运行库的功能,不是操作系统提供的功能,goroutine不是用线程实现的.goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。所以它非常廉价,我们可以很轻松的创建上万个goroutine,但它们并不是被操作系统所调度执行

除了被系统调用阻塞的线程外,Go运行库最多会启动$GOMAXPROCS个线程来运行goroutine

实现CSDN博文爬虫

由于实现的爬虫功能简单,所以所有代码均再main包下完成.
首先我们需要在main包下声明一个全局的urlchannel用来同步开启的多个routines在某个页面获取的<a> 标签的href属性

var urlChannel = make(chan string, 200) //chan中存入string类型的href属性,缓冲200

声明在html文档中获取<a> 的正则表达式

var atagRegExp = regexp.MustCompile(`<a[^>]+[(href)|(HREF)]\s*\t*\n*=\s*\t*\n*[(".+")|('.+')][^>]*>[^<]*</a>`) //以Must前缀的方法或函数都是必须保证一定能执行成功的,否则将引发一次panic

入口函数main

当进入main函数时,将启动一个goroutine来从入口url=”http:/blog.csdn.net”开始爬取(Spy函数)页面内容分析<a> 标签
接下来通过for range urlChannel来循环取出爬取到的<a> 标签中的href属性,并再次开启一个新的goroutine来爬取这个href属性对应的html文档内容

func main() {    go Spy("http:/blog.csdn.net")    //go Spy("http://www.iteye.com/")    for url := range urlChannel {        fmt.Println("routines num = ", runtime.NumGoroutine(), "chan len = ", len(urlChannel)) //通过runtime可以获取当前运行时的一些相关参数等        go Spy(url)    }    fmt.Println("a")}

Spy函数

由于每个爬取goroutine都是调用Spy函数来分析一个url对应的html文档,所以需要在函数开始就defer 一个匿名函数来处理(recover)可能出现的异常(panic),防止异常导致程序终止,defer执行的函数会在当前函数执行完成后结果返回前执行,无论该函数是panic的还是正常执行

    defer func() {        if r := recover(); r != nil {            log.Println("[E]", r)        }    }()

由于go内建了对http协议的支持,可以直接通过http包下的http.Get或则http.Post函数来请求url.但由于大部分网站对请求都有防范DDOS等的限制,需要自定义请求的header,设置代理服务器(CSDN好像对同一IP的请求平率限制并不严格,iteye亲测很严格,每分钟上万会被封住IP)等操作,可以使用http包下的http.NewRequest(method, urlStr string, body io.Reader) (*Request, error)函数,然后通过Request的Header对象设置User-Agent,Host等,最后调用http包下内置的DefaultClient对象的Do方法完成请求.
当拿到服务器响应后(*Response)通过ioutil包下的工具函数转换为string,找出文档中的<a>标签 分析出href属性,存入urlChannel中.

func Spy(url string) {    defer func() {        if r := recover(); r != nil {            log.Println("[E]", r)        }    }()    req, _ := http.NewRequest("GET", url, nil)    req.Header.Set("User-Agent", GetRandomUserAgent())    client := http.DefaultClient    res, e := client.Do(req)    if e != nil {        fmt.Errorf("Get请求%s返回错误:%s", url, e)        return    }    if res.StatusCode == 200 {        body := res.Body        defer body.Close()        bodyByte, _ := ioutil.ReadAll(body)        resStr := string(bodyByte)        atag := atagRegExp.FindAllString(resStr, -1)        for _, a := range atag {            href,_ := GetHref(a)            if strings.Contains(href, "article/details/") {                fmt.Println("☆", href)            }else {                fmt.Println("□", href)            }            urlChannel <- href        }    }}

随机伪造User-Agent

var userAgent = [...]string{"Mozilla/5.0 (compatible, MSIE 10.0, Windows NT, DigExt)",    "Mozilla/4.0 (compatible, MSIE 7.0, Windows NT 5.1, 360SE)",    "Mozilla/4.0 (compatible, MSIE 8.0, Windows NT 6.0, Trident/4.0)",    "Mozilla/5.0 (compatible, MSIE 9.0, Windows NT 6.1, Trident/5.0,",    "Opera/9.80 (Windows NT 6.1, U, en) Presto/2.8.131 Version/11.11",    "Mozilla/4.0 (compatible, MSIE 7.0, Windows NT 5.1, TencentTraveler 4.0)",    "Mozilla/5.0 (Windows, U, Windows NT 6.1, en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",    "Mozilla/5.0 (Macintosh, Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",    "Mozilla/5.0 (Macintosh, U, Intel Mac OS X 10_6_8, en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",    "Mozilla/5.0 (Linux, U, Android 3.0, en-us, Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13",    "Mozilla/5.0 (iPad, U, CPU OS 4_3_3 like Mac OS X, en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",    "Mozilla/4.0 (compatible, MSIE 7.0, Windows NT 5.1, Trident/4.0, SE 2.X MetaSr 1.0, SE 2.X MetaSr 1.0, .NET CLR 2.0.50727, SE 2.X MetaSr 1.0)",    "Mozilla/5.0 (iPhone, U, CPU iPhone OS 4_3_3 like Mac OS X, en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",    "MQQBrowser/26 Mozilla/5.0 (Linux, U, Android 2.3.7, zh-cn, MB200 Build/GRJ22, CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"}var r = rand.New(rand.NewSource(time.Now().UnixNano()))func GetRandomUserAgent() string {    return userAgent[r.Intn(len(userAgent))]}

解析<a> 元素

<a> 可以当做一份xml文档(只有一个a为根节点的简单xml)来解析出href/HREF属性,通过go标准库中xml.NewDecoder来完成

func GetHref(atag string) (href,content string) {    inputReader := strings.NewReader(atag)    decoder := xml.NewDecoder(inputReader)    for t, err := decoder.Token(); err == nil; t, err = decoder.Token() {        switch token := t.(type) {        // 处理元素开始(标签)        case xml.StartElement:            for _, attr := range token.Attr {                attrName := attr.Name.Local                attrValue := attr.Value                if(strings.EqualFold(attrName,"href") || strings.EqualFold(attrName,"HREF")){                    href = attrValue                }            }        // 处理元素结束(标签)        case xml.EndElement:        // 处理字符数据(这里就是元素的文本)        case xml.CharData:            content = string([]byte(token))        default:            href = ""            content = ""        }    }    return href, content}

总结

通过以上代码,一个简单的网络爬虫就实现了.而且对goroutine和range的配合使用基本就了解了.但如你所见,goroutine的运行机制和chan的设计原理绝非以上寥寥数句代码就可窥见其真面目.
go语言实现的简单爬虫来爬取CSDN博文

2 1
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 冰箱冷冻室门生锈变形关不严怎么办 诲信电冰箱电脑板坏了怎么办 冰箱里放了热水后就不制冷了怎么办 双温冰柜冷藏矿泉水不冰怎么办 美的电饭煲e一传感器也没坏怎么办 美的电饭煲不工作显示C3怎么办 美的电饭煲啪一声响不工作了怎么办 误给宝宝吃了坏的饭怎么办 鼠标没反应键盘指示灯不亮怎么办 新买变频冰箱风机声音大怎么办 三星手机玩王者荣耀一直闪退怎么办 刚申请的阿里大宝卡不想要了怎么办 国美刚买不到一个月电视坏了怎么办 交保险后保险公司不给开收据怎么办 收据白联作废红联丢失怎么办 收据作废客户联给客户了怎么办 宜家买的床和床垫搬家了怎么办 科龙空调开机后自己关机怎么办 以旧换新旧的没给商家 报案怎么办 想换新手机但是旧的没坏怎么办 从苏宁易购买的电视坏了怎么办 苏宁易购服务站买的电视坏了怎么办 用微信登陆京东账号退不出来怎么办 微信账号密码手机号都忘记了怎么办 我的手机号京东被别人绑定了怎么办 京东之前绑定的手机号不用了怎么办 京东退货钱未到银行卡账号里怎么办 京东身份证绑的手机号不用了怎么办 海尔冰箱要退货箱子扔了怎么办 海尔冰箱门变形关不严没吸力怎么办 长时间不用的手机忘记密码怎么办啊 微信忘记密码手机号又不用了怎么办 华为账号换了手机密码忘记了怎么办 用u盘制作音响喊话内容怎么办 新三板公司退市了小股东怎么办? 新三板公司退市股东股票怎么办 利群收购乐天玛特超市卡怎么办 买房过户夫妻一方是外省户口怎么办 欠了国美金融贷款没钱还会怎么办? 国企员工涨工资不在编的员工怎么办 装车牌照的螺丝孔小了怎么办