golang知乎爬虫小试

问题以及准备

问题

break的使用:A “break” statement terminates execution of the innermost “for”, “switch”, or “select” statement within the same function.也就是说break不仅仅用于for
所以导致爬虫代码未能正确的结束,修改后的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
rowlooper:
for {
//防止知乎防御攻击禁止访问,在公司的网络比较卡,1s一次没有被禁止,回家后1s的间隔就会forbidden.具体几秒有待测试
time.Sleep(time.Second * 2)
select {
case <-stopFalg:
log.Println("end")
break rowlooper
default:
if urls.Len() != 0 {
s := <-spiderPool
url := urls.Get()
go spiderProcess(s, url)
} else {
if len(spiderPool) != 50 {
log.Println("暂时没有待爬取的url,请等待运行中的worker结束")
} else {
log.Println("无待爬取的节点,运行结束")
break rowlooper
}
}
}
}

这里明确跳出位置来实现代码。还有就是上面提到的知乎禁止的问题,我在写爬虫的时候并没有模拟登陆也没有实现代理功能(当然爬虫本身应该提供代理相关的实现来避免触发网站的防御,暂时不会写,所以…),因为也没这个需求,我之所以写这个是看到it推送文章,里面有用python,java写知乎爬虫,然后说是看看知乎妹纸占多少,然后没有看到go的,所以我就简单写了一个 = =。其实很简单,就是http.Get()方法的使用以及简单的调度,写的很匆忙,所以可能会比较混乱。

sorry,the pic is missing
仅仅200多条数据就这样了…此时只有登陆后才能正确访问(因为账户安全方面,我没有模拟登陆)。

准备

大部分是使用的golang基础包。但是为了分析html以及解析复杂json,所以需要get以下package:

  • github.com/bitly/go-simplejson
  • github.com/PuerkitoBio/goquery

爬虫调度

1
2
3
4
5
6
7
8
9
10
11
12
13
//spider 方法定义
type Spider interface {
//设置要爬取的url
SetUrl(url string)
//获取html内容
GetHtml()
//获取下一层的节点
GetUrls() []string
//分析数据
Analy()
//保存数据
SaveData()
}

这里显示定义了爬虫需要的一些基本方法。其中保存数据的时候我只保存了用户本身的数据,存在mysql数据库中,而与之关联的教育背景,工作经历我只有简单的log,并没有存入数据库(毕竟我只是来看妹子的占比或者说互联网行业的占比)。其中sql部分我也有放入代码里。而调度部分就如在break部分贴出的代码中写的那样,首先查看url池子里有没有未爬取的url,有的话就尝试获取spider实例进行爬取,没有则阻塞等待。反之,如果url池子已经空了,则观察爬虫池子是不是已经充满,即所有的爬虫已经完成工作,没有则等待其完成,当然这时候基本会有新的待爬取url进入url池子,除非http请求失败了。如果url池子是空的,所有爬虫实例也已经归还的话则break,爬虫结束。
所以说正常情况下爬虫肯可能跑很久很久,所以这里实现了人为退出爬虫的方法:

1
2
3
//等待接受终止信号
stopFalg = make(chan os.Signal, 1)
signal.Notify(stopFalg, os.Interrupt, os.Kill)

1
2
3
4
5
6
7
8
9
10
for{
select{
...
case <-stopFalg:
log.Println("end")
break rowlooper
...
}
}
Stop()

这里等待监视用户输入信号比如用户ctrl+c。这时候系统会发现该信号并将其写入名为stopFlag的channel中,此时将会跳出for循环,执行Stop方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func Stop() {
for {
time.Sleep(time.Second * 1)
done := len(spiderPool)
if done != 50 {
log.Printf("等待剩余goroutine运行结束,共%d,运行中:%d", 50, 50-done)
continue
} else {
break
}
}
log.Println("运行结束~")
os.Exit(0)
}

这时候系统会去检查爬虫池子(此时就算url池子中有未爬取的url也不会进行分配了),直到所有的爬虫实例归还为止,然后退出系统。这样既可以实现人为停止爬虫也保证每个爬虫实例执行过程的完整,不会出现一些破损流程。

还有一点就是关于错误以及panic的处理,在具体的爬虫代码中不会很仔细的检查这些错误,panic就panic了。会在调度里检查这些情况,因为这些错误是无关紧要的,甚至某些情况下遇到error我会主动panic(这是不可避免的,比如我用simpleJson解析json在遍历节点的时候会出现nil的情况,这里我直接忽略错误或者主动panic,当然这里还有多种选择,我的选择是直接放弃这条url的解析,抛弃数据哪怕已经解析了大部分的数据,当然也可以只是跳过这一步,将剩余的数据保存,这些只需要在recover逻辑里写清即可),因为在爬虫实例执行过程中我并不会特别关心某个协程的执行情况,出错了我就将其归还即可,等待下一轮的调度,所以在调度里会有这样的代码,其中去重工作我是交给数据库自己实现的,即报错就跳过该数据。

1
2
3
4
5
6
7
8
defer func() {
if err := recover(); err != nil {
log.Println(err)
spiderPool <- s
return
}
}()

好了,这大概就是爬虫的执行过程了,详细的代码不过是Get请求,然后使用goquery/simpleJson获取我们想要的数据。具体的使用方法他们都有提供相应的doc。此处就不多加赘述。

运行截图

sorry,the pic is missing
这里就是一位陈小伊的用户 描述写的是非典型理科女 行业是科研 女性(0是男性,1是女性,2是不知道),清华大学神经科学,至于工作履历没写,现居美国。可以通过知乎连接people/chen-xiao-yi-76-92进入其页面
sorry,the pic is missing

sorry,the pic is missing
sorry,the pic is missing
上面的同理

还有结果,大概一个多小时获取了219条数据,公司网慢的很。
然后妹子是78位,感觉比例还是可以的啊~
sorry,the pic is missing

打印end处是因为按下ctrl+c,此时显示等待运行中的爬虫结束
sorry,the pic is missing

代码在github

客官扫码领红包哟~