相关链接汇总:
Node.js网站爬虫(一)项目简介与完成情况
Node.js网站爬虫(二)mysql配置与数据库设计
Node.js网站爬虫(三)网络爬虫
Node.js网站爬虫(四)Express框架构建网站执行搜索功能
Node.js网站爬虫(五)扩展:热点词时间热度动态可视化展示
Node.js网站爬虫(六)易错点合集和感想
1 对目标网站进行分析:
这里以环球网为例子:
1.1 分析网页html结构
通过控制台分析网页html结构
1.1.1 主页:
使用谷歌浏览器进入环球网官网:https://www.huanqiu.com/
右键—>检查,进入控制台。(或通过F12 / CTRL+F12进入)
点击控制台最左上角的箭头后,点击页面的一条新闻,可看到控制台定位到一个<a>标签中,内含该条新闻的跳转url。
1.1.2 新闻详情页:
进入一条新闻详情页并进入控制台:https://tech.huanqiu.com/article/42ujmzmNHGV
标题:
发现源代码中有几个标签里都能找到标题
方式1:< title >的文本中 (后续爬虫时采用了此方式)
方式2:< h3 >的文本中
时间:
作者、来源等标签同理可得,不再赘述。
1.2 其他方式:
也可右键->网页源代码,直接用搜索功能定位标题(或时间等)所在位置,查看所在标签。但这种方式不适用于不知道要查询内容的情况。
2 爬取结构与工具包:
2.1 爬取整体结构:
- 导入模块
- 设置网站、format、正则表达式
- 配置:异步获取url、定时执行
- 读取种子页面
- 读取新闻页面
2.2 工具包
2.2.1 模块下载和导入模块:
运行.js文件出现找不到’xxx’module时,通过npm install xxx 进行下载。
使用require导入模块,例如:var myRequest = require(‘request’);
2.2.2 工具包(重要模块):
request:用于发送GET和POST请求。
cheerio:本实验主要用于提取html文件指定内容。
node-schedule:定时功能。
iconv-lite:转码。
mysql:执行sql语句,将数据存入mysql。
3 爬取流程:
3.1 定义format:
3.1.1 根据html结构使用CSS选择器:
//通过元素
var seedurl_format="$('a')";
var title_format = "$('title').text()";
//通过属性(meta属性)
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var desc_format = " $('meta[name=\"description\"]').eq(0).attr(\"content\")";
//通过class
var date_format = "$('.time').text()";
var author_format = "$('.author').text()";
var source_format = "$('.source data-source').eq(0).attr(\"data-source\")";
//通过id
var content_format = "$('#ContentBody').text()";
在其他网站中,还有通过ID进行选择的情况,例如东方财富网:var content_format = “$(’#ContentBody’).text()”;
jQuery 主要选择器:
选择器 | 实例 | 选取 |
---|---|---|
* | $("*") | 有元素 |
#id | $("#lastname") | id=“lastname” 的元素 |
.class | $(".intro") | 所有 class=“intro” 的元素 |
element | $(“p”) | 所有 元素 |
.class.class | $(".intro.demo") | 所有 class=“intro” 且 class=“demo” 的元素 |
更多jQuery 选择器参考网址:https://www.w3school.com.cn/jquery/jquery_ref_selectors.asp
3.1.2 设置正则表达式
分析多个新闻详情页的url,url格式: ‘/artical/’+11个字符(数字或字母)
以一个为例: https://tech.huanqiu.com/article/42ujmzmNHGV
正则表达式:用于读取种子界面时检查是否符合新闻url的正则表达式
var url_reg = /article\/([0-9a-zA-Z]{11})/;
设置时间的正则表达式:检查获取时间是否符合时间的正则表达式
var regExp = /((\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2})|(\d{4}年\d{1,2}月\d{1,2}日)/
3.2 配置异步执行和定时执行等:
防止网站屏蔽我们的爬虫,request模块异步fetch url:
var headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36'
}
function request(url, callback) {
var options = {
url: url,
encoding: null,
//proxy: 'http://x.x.x.x:8080',
headers: headers,
timeout: 10000 //
}
myRequest(options, callback)
};
定时执行:
//!定时执行
var rule = new schedule.RecurrenceRule();
var times = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]; //每小时
var times2 = 20; //定义在第几分钟执行
rule.hour = times;
rule.minute = times2;
//定时执行httpGet()函数
schedule.scheduleJob(rule, function() {
seedget();
});
3.3 读取种子页面:
读取种子页面,遍历种子页面里所有的a链接,得到具体新闻url。
function seedget() {
request(seedURL, function(err, res, body) { //读取种子页面
// try {
//用iconv转换编码
var html = myIconv.decode(body, myEncoding);
//准备用cheerio解析html
var $ = myCheerio.load(html, { decodeEntities: true });
var seedurl_news;
try {
seedurl_news = eval(seedURL_format);
} catch (e) { console.log('url列表所处的html块识别出错:' + e) };
seedurl_news.each(function(i, e) { //遍历种子页面里所有的a链接
var myURL = "";
try {
//得到具体新闻url
var href = "";
href = $(e).attr("href");
if (href == undefined) return;
if (href.toLowerCase().indexOf('http://') >= 0) myURL = href; //http://开头的
else if (href.startsWith('//')) myURL = 'http:' + href; 开头的
else myURL = seedURL.substr(0, seedURL.lastIndexOf('/') + 1) + href; //其他
} catch (e) { console.log('识别种子页面中的新闻链接出错:' + e) }
if (!url_reg.test(myURL)) return; //检验是否符合新闻url的正则表达式
console.log(myURL);
var fetch_url_Sql = 'select url from fetches where url=?';
var fetch_url_Sql_Params = [myURL];
mysql.query(fetch_url_Sql, fetch_url_Sql_Params, function(qerr, vals, fields) {
if(!vals){
console.log('no URL') //vals: null / undefined / NaN ...
}
else if (vals.length > 0) {
console.log('URL duplicate!')
} else newsGet(myURL); //读取新闻页面
});
});
});
};
3.4 读取新闻页面:
3.4.1 转码并获取内容:
iconv转换编码,cheerio提取html文件内容。
3.4.2 动态执行format字符串,构建json对象:
从提取内容中获得title、publish_date、source、author、content等信息,一定注意判断获取到的是否为空值(null/undefined)。
3.4.3 将数据存入mysql数据库中。
如果成功获取到content,则执行sql语句,将数据存入mysql数据库中。
因为数据库中fetch表里的url属性是unique的,所以不会把重复的url内容写入数据库。
3.4.4 此部分代码:
function newsGet(myURL) { //读取新闻页面
request(myURL, function(err, res, body) { //读取新闻页面
//try {
var html_news = myIconv.decode(Buffer.from(body), myEncoding); //用iconv转换编码
//console.log(html_news);
//准备用cheerio解析html_news
var $ = myCheerio.load(html_news, { decodeEntities: true });
myhtml = html_news;
//} catch (e) { console.log('读新闻页面并转码出错:' + e);};
console.log("转码读取成功:" + myURL);
//动态执行format字符串,构建json对象准备写入文件或数据库
var fetch = {};
fetch.title = "";
fetch.content = "";
fetch.publish_date = (new Date()).toFormat("YYYY-MM-DD");
//fetch.html = myhtml;
fetch.url = myURL;
fetch.source_name = source_name;
fetch.source_encoding = myEncoding; //编码
fetch.crawltime = new Date();
if (keywords_format == "") fetch.keywords = source_name; // eval(keywords_format); //没有关键词就用sourcename
else fetch.keywords = eval(keywords_format);//关键字
if (title_format == "") fetch.title = ""
else fetch.title = eval(title_format); //标题
// info_format得到:时间 来源
if(info_format != "" && info_format!= null){
var info_all = eval(info_format);
//时间
var date_inform = date_reg.exec(info_all);
if(date_inform){
fetch.publish_date = date_inform[0];
fetch.publish_date = new Date(fetch.publish_date).toFormat("YYYY-MM-DD");
}else fetch.publish_date = "no publish date";
//来源
var source_inform = source_reg.exec(info_all);
if(source_inform)fetch.source = source_inform[0];
else fetch.source = fetch.source_name;
};
//作者
if (author_format == "") fetch.author = source_name;
else fetch.author = eval(author_format);
//内容
if (content_format == "") fetch.content = "";
else fetch.content = eval(content_format).replace("\r\n" + fetch.author, ""); //内容,是否要去掉作者信息自行决定
//摘要
if (desc_format == "" && desc_format == undefined){
fetch.desc = fetch.title; //没有摘要,则title就是摘要
}
else {
var descstring = eval(desc_format);
if (descstring != null)fetch.desc = eval(desc_format).replace("\r\n", "");
}
//写进数据库
var fetchAddSql = 'INSERT INTO fetches(url,source_name,source_encoding,title,' +
'keywords,author,publish_date,crawltime,content) VALUES(?,?,?,?,?,?,?,?,?)';
var fetchAddSql_Params = [fetch.url, fetch.source_name, fetch.source_encoding,
fetch.title, fetch.keywords, fetch.author, fetch.publish_date,
fetch.crawltime.toFormat("YYYY-MM-DD HH24:MI:SS"), fetch.content
];
//执行sql,数据库中fetch表里的url属性是unique的,不会把重复的url内容写入数据库
mysql.query(fetchAddSql, fetchAddSql_Params, function(qerr, vals, fields) {
if (qerr) {
console.log(qerr);
return;
}
}); //mysql写入
});
}
4 爬取其他网址需更改处和爬取结果:
根据具体情况修改以下部分即可。
最终爬取了三个网站(环球、澎湃、东方财富)共620条信息。