一直认为爬虫是很多web开发者绕不开的一个点。我们也应该或多或少接触到这方面的东西,因为我们可以从爬虫身上学到一些web开发应该掌握的基础知识。而且,这很有趣。我是知乎的轻度和重度用户。我写了一个爬虫来帮助我爬取和分析它的数据。我觉得这个过程挺有意思的,因为这是一个不断给自己制造问题,然后解决问题的过程。遇到了一些点,今天总结一下,分享给大家。它爬了什么?先简单介绍一下我的爬虫。它可以定时捕捉某个问题的关注度、浏览量和答案,这样我就可以把这些数据绘制成图表来展示它的热点趋势。为了不让我错过一些热门事件,它会定期在我关心的话题下获取热门问答,并推送到我的邮箱。作为一名前端开发者,我必须为这个爬虫系统创建一个接口,它允许我登录我的知乎账号,添加我关心的主题和主题,并看到可视化数据。所以这个爬虫也有登录知乎和搜索话题的功能。然后看界面。下面就认真的说说它的发展历程。技术选择Python以其简单快速的语法和丰富的爬虫库一直是爬虫开发者的首选。不幸的是我不熟悉它。当然,最重要的是,作为前端开发者,如果node能够满足爬虫的需求,自然是首选。而且随着node的发展,也出现了很多好用的爬虫库,甚至还有puppeteer这种可以直接模拟Chrome访问网页的工具。Node在爬虫方面应该可以很好的满足我所有的爬虫需求。所以我选择从零开始搭建一个基于koa2的服务器。为什么不直接选择egg、express、thinkjs等更全面的框架呢?因为我爱折腾。这也是一个学习过程。如果你之前不了解node,并且有兴趣搭建一个node服务器,可以阅读我之前的文章——从零开始搭建Koa2Server。对于爬虫,我选择了request+cheerio。知乎虽然很多地方用到了react,但得益于它的大部分页面还是由服务端渲染,只要我能请求网页和接口(request),解析页面(cherrio)就可以满足我的爬虫需求。其他的我就不一一列举了。我会列出一个技术栈服务器koajs作为节点服务器框架;request+cheerio作为爬虫服务;mongodb作为数据存储;node-schedule作为任务调度;nodemailer作为电子邮件推送。客户端vuejs前端框架;museui材料设计UI库;chart.js图表库。技术选好后,我们还要关心业务。第一个任务是实际抓取页面。如何爬取网站的数据?知乎没有开放接口供用户获取数据,所以想要获取数据就得自己去爬取网页信息。我们知道,即使是网页,本质上也是一个GET请求接口。我们只需要在服务器端请求对应网页的地址即可(客户端请求会跨域),然后解析html结构得到想要的数据。.那我为什么要登录呢?由于您不是登录账号获取信息,知乎只会展示有限的数据,您将无法获知您的知乎账号关心的话题、问题等。而如果你想让自己的系统被其他小伙伴使用,你还必须建立一个账号系统。模拟登录,大家会使用Chrome等现代浏览器查看请求信息。我们在知乎的登录页面登录,然后查看抓取的接口信息可知,登录无非就是将账号、密码等信息发送给一个登录API。如果成功。服务器会设置一个cookie给客户端,也就是登录凭证。所以我们的思路也是一样的,通过爬虫服务器请求接口,带上我们的账号密码信息,成功之后把返回的cookie保存到我们的系统数据库中,后面爬取其他页面的时候带上这个cookie就可以了。当然,真正去尝试的时候,我们会遭遇更多的挫折,因为我们会遇到token、验证码等问题。不过既然我们有了客户端,就可以把验证码的识别交给真人而不是服务器去解析图片字符,这样就降低了我们登录的难度。曲折的是,即使提交了正确的验证码,还是会提示验证码错误。如果我们自己做过验证码提交系统,可以很快定位到原因。如果没有,我们可以再查看一下登录涉及的request和response,我们也可以猜测:当客户端获取到验证码时,知乎服务器也会给客户端设置一个新的cookie。在提交登录请求时,您必须连同此cookie一起提交验证码,以验证本次提交的验证码确实是当时给用户的验证码。语言描述有点绕,我用图的形式表达了一次登录请求的完整过程。注:我写爬虫的时候,知乎也部分采用了图片字符验证码,现在已经全部改为“点击倒排文字”的形式了。这样会增加提交正确验证码的难度,但也不是不可以。获取到图片后,手动识别并点击倒排文字,将点击的坐标提交到登录界面。当然有兴趣有能力的同学也可以自己写算法来识别验证码。在上一步的爬取数据中,我们已经获取到了登录后的凭证cookie。用户登录成功后,我们将登录的账户信息及其凭证cookie保存在mongo中。以后该用户发起的爬取需求,包括跟踪问题的数据爬取,都将基于该cookie进行爬取。当然cookie是有时间限制的,所以我们保存cookie的时候也要记录过期时间。当我们后面拿到cookie的时候,我们需要添加一个过期检查。如果到期,我们将返回到期提醒。爬虫的基础扎实后,才能真正拿到自己想要的数据。我的需求是知道一个知乎问题的热点趋势。先用浏览器看一个问题页下面有什么数据,可以让我爬取分析。比如比如这个问题:有哪些令人惊奇的推理段落。打开链接后浏览页面最直接显示的关注者,1xxxx个回答,默认显示几个高赞回答和点赞评论数。右键查看网站源码,确认数据是服务器渲染的,我们可以通过request请求网页,然后通过cherrio使用css选择器定位数据节点,获取并存储。代码示例如下:asyncgetData(cookie,qid){constoptions={url:`${zhihuRoot}/question/${qid}`,method:'GET',headers:{'Cookie':cookie,'Accept-Encoding':'deflate,sdch,br'//不允许gzip,开启gzip会开启知乎客户端渲染,导致爬取失败}}constrs=awaitthis.request(options)if(rs.error){returnthis.failRequest(rs)}const$=cheerio.load(rs)constNumberBoard=$('.NumberBoard-item.NumberBoard-value')const$title=$('.QuestionHeader-title')$title.find('button').remove()返回{成功:true,标题:$title.text(),数据:{qid:qid,关注者:Number($(NumberBoard[0]).text()),读者:Number($(NumberBoard[1]).text()),answers:Number($('h4.List-headerTextspan').text().replace('answers',''))}这样我们就爬取了一个问题的数据,只要在一定时间间隔继续执行这个方法获取数据,就可以最终绘制出一个问题的数据曲线,分析热点趋势.那么问题来了,这个定时任务要怎么做呢?对于定时任务,我使用node-schedule进行任务调度。如果你以前做过计划任务,你可能会熟悉它的类似cron的语法。不熟悉也没关系。它提供了不像cron的、更直观的设置来配置任务。你可以通过阅读文档得到一个大概的了解。当然,这个定时任务并不是简单的不断执行上面的爬取方法getData。因为这个爬虫系统不只有一个用户,一个用户不只跟踪一个问题。所以我们这里的完整任务应该是遍历系统中每一个cookie还没有过期的用户,然后遍历每一个用户的跟踪问题,然后获取这些问题的数据。系统还有另外两个定时任务,一个是定时抓取用户关心的话题的热门答案,一个是将这个话题的热门答案推送给对应的用户。这两个任务和上面的任务大致相同,就不赘述了。但是我们在做定时任务的时候,会有一个详细的问题,就是爬取的时候如何控制并发问题。具体的例子:如果爬虫请求并发太多,知乎可能会限制访问这个IP,所以我们需要让爬虫一个一个请求,或者几个请求。简单考虑一下,我们将采取循环等待。我想都没想就写了下面的代码://爬虫方法asyncfunctiongetQuestionData(){//做爬虫动作}//questions就是获取的问答questions.forEach(awaitgetQuestionData)但是执行之后,我们会发现,这个其实还是并发执行的,为什么呢?其实仔细想想就明白了。forEach只是for循环的语法糖,如果没有这个方法,让你实现,你会怎么写?你也可以这样写:Array.prototype.forEach=function(callback){for(leti=0;i
