零基础nodejs爬虫心得体会分享
本篇博客将记录仅有一学期C语言编程学习经验的学生,从零开始学习Nodejs爬虫的一些心得与总结。
大多数代码都来自老师,我将对部分遇到的困难,和比较难以理解的部分进行分析。
思路总述
在正式接触这个项目之前,我对爬虫的了解也仅限于“听说”的程度,也没有接触过JS语言,所以一上来感到无从下手。
幸好,感谢老师给了我们两倍的时间来完成项目,同时每一步都给我们细致地演示代码,辅佐以JS语法的学习,让我磕磕绊绊地完成了这个项目。
从普通人都能够听懂的角度来解释,爬虫就是实现在各种网站中搜索关键信息这一功能。
听上去非常简单,但是实际上要经历先获取目标页面的URL、再在源代码中搜索到我们所需的元素、最终将所获得的元素建立数据库进行保存,等等这些步骤。
感谢我的同学给我的帮助,在参考了各位先行写完的博客,经历了多个极低效率工作到凌晨三点的夜晚后,我能够实现基本的爬虫功能了,并且将在以下分享我的心路历程。
由于我对JS的了解相当肤浅,相关的知识甚至是边写边查阅资料学习的,还望各位看官及时指出纰漏之处。
慢慢更新。
获取URL
URL,是网页存放在Web服务器之后便于用户访问的定位符,也就是大家常说的网址。
其格式一般为“协议名 + 主机名/IP地址及其端口 + 文件路径”,在此不多解释。
既然要爬虫,那么就先要确定爬取的对象。
以我首先爬取的新浪国内新闻网站为例,定义seedURL用于存放目标页面的url,由此便确定了爬取的页面的地址,以便接下来的元素获取步骤。
//定义要访问的网站
var source_name = "新浪新闻网";
var domain = 'https://news.sina.com.cn/china/';
var myEncoding = "utf-8";
var seedURL = 'https://news.sina.com.cn/china/';
此处的domain语句似乎在后续进程中并未用到,具体用处存疑。
这一步没有什么需要具体阐述的,找到目标页面,复制网址作为目标界面的URL即可。
myEncoding是转码用的,目前所知的转码类型应有utf-8和GBK两种,如果发现爬取下来的汉字全部都是乱码,则应当改变转码方式进行尝试。
我爬取的新浪新闻是utf-8,网易则是GBK,貌似还有其他的编码类型。
需要注意的内容:
种子页面的链接千万不能错!否则会报错,且没有任何明确指出是URL错误的报错信息。
这里的报错信息其实并不是因为url错误才产生的,因为在爬虫完成爬取功能后依然出现,似乎是读取内容过多才会产生的。百度查询了很久也没找到相似的问题与解决方案。
老师认为可能是不支持https协议的问题,应当改成http协议。或许确实存在所述情况,但与我的情况不同,所以姑且在此记录一笔。
定义获取元素的方式
这一步相当重要,一方面关联到了正则表达式的概念,另一方面,在完成爬虫主体功能后,基本上只需修改这里的获取方式,即可将爬虫活用于其它网站,非常方便。
//定义新闻页面内具体元素的读取方式
var seedURL_format = "$('a')";
var keywords_format = "$('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var date_format = "$('meta[property=\"article:published_time\"]').eq(0).attr(\"content\")"; //从指定标签的第0个位置,即首位,开始取出content内容,上同
var author_format = "$('.show_author').text()"; //class用'.'号标识符取出
var content_format = "$('.article').text()";
var description_format = "$('meta[name=\"description\"]').eq(0).attr(\"content\")";
var source_format = "$('.source').text()";
var url_reg = /\/((\d{4})-(\d{2})-(\d{2}))\/(doc-(\w{15})).shtml/; //正则表达式筛选不同的部分
var regExp = /((\d{
4})-(\d{
2})-(\d{
2}))/ //筛去无用信息
首先搞清楚一件事情,这里的定义全部都是字符串,从变量类型上类似于C语言中的string,在这里是没有实际运行功能的,但写成符合语法的语句是为了下文使用eval函数进行操作。
在此解释一下eval()函数,其可计算某个字符串,并执行其中的的 JavaScript 代码,故其实并不能与string混为一谈。
此处的$符号理解为查找功能即可,在页面源代码中查找符合后续搜索条件的内容,查阅百度后发现似乎等于document.getElementById()函数。
既然要获取页面元素,那么就要从目标页面的源代码入手。依然以我首先选取的新浪国内网站为例,为何选定国内新闻这一分栏将在下文解释。
首先看整个“国内”分区下的内容,新浪新闻网整洁的源代码能够让我们很容易找到每则新闻的URL.
看图找规律,不难发现所有的新闻都是以“a href=”开头的,是href属性的a标签,故每个新闻页面的url可以通过查找a标签来获取。
选择任意一条新闻进入,再次查看网页源代码。
根据实验要求,找到我们需要爬取的各个元素下来,对于不同的语句,定义$的搜索方式也应该不同。
attr函数是用于获取指定标签下的指定内容,在此处基本用来取content内容;eq(0)是指从第0位开始取值,即是从content内容下的首位开始获取。
出现class属性的内容时,需要在前面加上"."来搜索。
爬取其他网站遇到的一些问题
兴趣使然,恰逢最近LOL的LPL春季赛,想去爬取一些相关新闻,于是兴致勃勃打开腾讯电竞新闻,却发现该页面的源代码中竟然没有出现子页面的url,对于这样的网站应该如何爬取,依然存在疑问。
借助正则表达式获取子页面的URL
观察之前的新闻url,我们不难发现这些url的大致格式相同,中间可能出现不同的只有两部分,一部分是"2020-4-26"字样的日期可能有变,另一部分则是"doc-"后的数字+字母的组合不同。这是这里用到正则表达式的一大原因,它能够使得我们只要通过这些有差异的部分就能完整地爬取众多子页面的URL。
翻阅犀牛书以及查阅众多资料以后,我还是只能记下几个简单的会在正则表达式中用到的符号,并且模仿着写了几个简单的正则表达式。
var url_reg = /\/((\d{4})-(\d{2})-(\d{2}))\/(doc-(\w{15})).shtml/; //正则表达式筛选不同的部分
var regExp = /((\d{
4})-(\d{
2})-(\d{
2}))/ //筛去无用信息
这里只用到了\d(任何ASCII数字,即0-9)与\w(任何ASCII单词,即a-zA-z0-9)两个字符类的值,{}中的数字即为满足前列字符类条件的字数。
以第一句为例,日期字样统一以"YYYY-MM-DD"的格式呈现,故(\d{4})-(\d{2})-(\d{2})是对应日期处的不同的。doc-后的字符共有15个,这里出于方便考虑就直接用了\w{15}进行筛选,不多做区分了。前后两部分不同之间由"/“符号连接,对应了原url中的”/"符号。
选择新浪新闻的另一个原因便是筛选子页面url的正则表达式书写起来相当方便,也便于理解。
下一条正则表达式是用来筛选日期信息的,因为在源代码中获取的日期信息很多情况下并不只有年月日,还会附带具体的时间乃至时区等信息,这些信息在此略显多余,因而使用正则表达式挑选出我们所需的部分,筛去多余的无用信息。
不同网站的元素爬取部分比较重要,如果掌握了原理,那么就可以很方便地在不改变主体功能函数的情况下,只需改变获取元素的方式、包括其中的正则表达式,就可以省时地用爬虫爬取多个不同网站的信息。
引入各种包
接下来是函数的主体部分,需要引入很多额外的包,并且借助函数更方便的实现功能。
var fs = require('fs');
var myRequest = require('request')
var myCheerio = require('cheerio')
var myIconv = require('iconv-lite')
require('date-utils');
Request的使用
//构造一个模仿浏览器的request
var headers = {
'User-Agent': 'Chrome/81.0.4044.122'
} //将爬虫伪装成浏览器,防止被屏蔽
function request(url, callback){
var options ={
url: url,
encoding: null,
headers: headers,
timeout: 10000
}
myRequest(options, callback)
}
这一段便是之前调用了request包后使用的代码,是向服务端发起http请求,其中option中的各项参数包括但不限于罗列的路径、编码、请求头以及超时限制,还可以定义请求方式(POST、GET等)、代理等其他参数。
options即是这里定义的必要参数,callback是成功后的回调函数。
这里构造完成后,就可以在下文使用request函数,并且通过遍历的方式找到主页面内的全部a标签,转化为Http://开头的绝对路径,再判断是否符合先前书写的正则表达式来获取所有满足条件的URL.