新闻爬虫与爬取结果的查询网站的实现
项目要求
新闻爬虫及爬取结果的查询网站
◦核心需求:
◦1、选取3-5个代表性的新闻网站(比如新浪新闻、网易新闻等,或者某个垂直领域权威性的网站比如经济领域的雪球财经、东方财富等,或者体育领域的腾讯体育、虎扑体育等等)建立爬虫,针对不同网站的新闻页面进行分析,爬取出编码、标题、作者、时间、关键词、摘要、内容、来源等结构化信息,存储在数据库中。
◦2、建立网站提供对爬取内容的分项全文搜索,给出所查关键词的时间热度分析。
◦技术要求:
◦1、必须采用Node.JS实现网络爬虫
◦2、必须采用Node.JS实现查询网站后端,HTML+JS实现前端(尽量不要使用任何前后端框架)
0.所需工具
vscode
Visual Studio Code(简称“VS Code” )是Microsoft在2015年4月30日Build开发者大会上正式宣布一个运行于 Mac OS X、Windows和 Linux 之上的,针对于编写现代Web和云应用的跨平台源代码编辑器,可在桌面上运行,并且可用于Windows,macOS和Linux。它具有对JavaScript,TypeScript和Node.js的内置支持,并具有丰富的其他语言(例如C++,C#,Java,Python,PHP,Go)和运行时(例如.NET和Unity)扩展的生态系统。
其用于对爬虫代码进行调试,找出错误并进行修改。
node.js
Node.js发布于2009年5月,由Ryan Dahl开发,是一个基于Chrome V8引擎的JavaScript运行环境,使用了一个事件驱动、非阻塞式I/O模型,让JavaScript 运行在服务端的开发平台,它让JavaScript成为与PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言。
其用于连接网站后端。
1.代码分析
1.所用工具包
fs为filesystem,用于保存爬取新闻内容所需要的函数
request用于获取网页HTML模块库
cheerio为nodejs抓取页面模块,为服务器特别定制的快速灵活实施的Query核心实现
iconv-lite在爬虫项目中负责将任何变异编译标准的代码转化为标准的utf-8
date-utils为一个日期工具类,帮助我们爬取新闻发布时间、爬取时间等操作
2.网站域名
我们先根据我们所要爬取的网站来定义我们的网站名称,例如,我的爬虫项目中爬取的网站为网易新闻,我就定义我的source_name为网易新闻,之后我们再将我们的编码标准以及网站域名进行定义。
3.网页各元素
在写这一部分代码时,我们需要对我们爬取的网站进行分析:
在对网站内的一条新闻进行检查时,我们发现在源代码中,某则新闻的链接对应的标签元素为<a></a>,因此,在我们的爬虫代码中,seedURL_format则选择出a标签的元素,此为图中第一行代码的来历
之后,以图中网站为例,我们对其页面源代码进行查看,找到其中一则新闻的标题所在处,我们发现标题在源代码中的定义为<title>,因此在我们的爬虫代码中,title_format则定义为$(‘title’).text(),text()用于提取内容,将新闻标题提取出来
再来说关于新闻正文内容的分析,在对新闻内容进行检查时,如上图所示,可以发现正文的定义为<div class="post_body">,因此在我们的爬虫代码中,content_format则定义为$('.post_body').text()
而从图中可以看到对网页源代码中对keywords及author的定义为<mata name="名称"content="内容">,所以我们就定义$('meta[name=\"keywords\"]').eq[0].attr(\"content\"),其用于读取keywords的内容,而对于author也是如此
最后来说说正则表达式,我们需要对比分析多条新闻链接
通过对比,我们可以看出新闻的链接名的基础为“www.163.com/news/article/(16位字母与数字混合).html
由此可得正则表达式/\/article\/(\w{16}).html/
正则表达式用于判断某条链接是否为我们需要爬取的链接
3.为爬虫代码创建浏览器请求头
这段代码的作用是防止我们爬取的网站将我们的爬虫屏蔽,但具体的原理或许还有待之后的更深入的学习才能理解
4.分析函数
我们接下来对代码中的request函数进行分析,位于下方的图中的代码为位于上方的图中代码的具体描述,在上面图中的request中,参数为url以及回调函数callback,其中对option进行了定义,并且引用了myRequest,其中的option参数在myRequest上方已经定义完成。
在下面一张图中,在request函数中,先将存入body中的HTML读取并利用iconv转换编码存入定义的变量html,之后用cheerio对其进行解析,之后将之前定义的爬取网站的新闻链接表达式seedURL_format放入eval函数,然后读取其中含有<a>的语句存入seedurl_news,用each函数遍历,之后用if判断语句将链接中以http://或https://开头的链接取出,之后判断我们取出的链接是否符合我们的正则表达式,再进行下一步的爬取,newsGet函数用于打开可以爬取的链接并爬取其中我们所需的内容
5.内容爬取
newsGet函数,先打开链接,转换编码,解析,如果以上过程无一出错,则会输出“转码读取成功”的字样,再进行下一步工作
在爬取新闻内容前,我们需要一些变量来存储我们爬取下来的内容,因此,我们在代码中定义一个名为fetch的类,用fetch.title、fetch.content、fetch.url、fetch.author分别存储我们爬取下来的新闻的标题,正文内容,网址,作者
之后,通过一系列的if判断语句来判断新闻的各个元素格式是否符合我们先前所定义的变量格式,如果符合,则直接存入fetch.keywords等变量,如果不符合,则通过eval函数存入变量
为了整理我们爬取的新闻,我们通过fs函数将已经爬取下来的新闻以news_"时间“_"网址”命名的json文件形式存储在E盘JavaScript文件夹中result1文件夹中
爬取效果如下
文件整理效果如下
共爬取160条新闻
var fetchAddSql = 'INSERT INTO fetches(url,title,' +
'publish_date,content,author,keywords) VALUES(?,?,?,?,?,?)';
var fetchAddSql_Params = [fetch.url, fetch.title, fetch.publish_date, fetch.content,fetch.author,fetch.keywords
];
mysql.query(fetchAddSql, fetchAddSql_Params, function(qerr, vals, fields) {
if (qerr) {
console.log(qerr);
}
}); //mysql写入(有疑问,待解决)
});
之后我们通过上述代码来实现将我们爬取下来的新闻存入数据库的表中,具体分析有待之后进一步学习后再来添加,拭目以待
效果如下
额外内容:
在上面的代码中我们爬取的是网易新闻的新闻,我通过对此代码的分析与修改,还爬取了另一个网站的新闻,分析过程大致相同,直接呈上代码
var fs = require('fs');
var myRequest = require('request');
var myCheerio = require('cheerio');
var myIconv = require('iconv-lite');
require('date-utils');
var mysql = require('./mysql.js');
var schedule = require('node-schedule');
var source_name = "中国政府网";
var myEncoding = "utf-8";//编码标准
var seedURL = 'http://www.gov.cn/';
var seedURL_format = "$('a')";
var title_format = "$('title').text()";
var content_format = "$('.pages_content').text()";
var url_reg = /\/(\d{4})-(\d{2})\/(\d{2})\/content_(\d{7}).htm/;
var author_format = "$('meta[name=\"author\"]').eq(0).attr(\"content\")";
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
//var regExp = /((\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2})|(\d{4}年\d{1,2}月\d{1,2}日)/
//防止网站屏蔽我们的爬虫
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'
}
//request模块异步fetch url
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, 12]; //每天2次自动执行
// var times2 = 5; //定义在第几分钟执行
// rule.hour = times;
// rule.minute = times2;
// //定时执行httpGet()函数
// schedule.scheduleJob(rule, function() {
// seedget();
// });
request(seedURL, function(err, res, body) { //读取种子页面
// try {
//用iconv转换编码
var html = myIconv.decode(body, myEncoding);
//console.log(html);
//准备用cheerio解析html
var $ = myCheerio.load(html, { decodeEntities: true });
// } catch (e) { console.log('读种子页面并转码出错:' + e) };
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 || href.toLowerCase().indexOf('https://')>=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.length > 0) {
console.log('URL duplicate!')
} else newsGet(myURL); //读取新闻页面
});
});
});
function newsGet(myURL) { //读取新闻页面
request(myURL, function(err, res, body) { //读取新闻页面
//try {
var html_news = myIconv.decode(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.html = myhtml;
fetch.url = myURL;
fetch.author = author_format;
if (keywords_format == "") fetch.keywords = source_name;
else fetch.keywords = eval(keywords_format);
if (title_format == "") fetch.title = ""
else fetch.title = eval(title_format);
if (content_format == "") fetch.content = "";
else fetch.content = eval(content_format).replace("\r\n" + fetch.author, "");
if (author_format == "") fetch.author = source_name;
else fetch.author = eval(author_format);
var filename ="E:\\JavaScript\\result1\\news" + "_" + (new Date()).toFormat("YYYY-MM-DD") +
"_" + myURL.substr(myURL.lastIndexOf('/') + 1) + ".json";
fs.writeFileSync(filename, JSON.stringify(fetch));
// var filename = source_name + "_" + (new Date()).toFormat("YYYY-MM-DD") +
// "_" + myURL.substr(myURL.lastIndexOf('/') + 1) + ".json";
// 存储json
// fs.writeFileSync(filename, JSON.stringify(fetch));
var fetchAddSql = 'INSERT INTO fetches2(url,title,' +
'content,author,keywords) VALUES(?,?,?,?,?)';
var fetchAddSql_Params = [fetch.url, fetch.title, fetch.content,fetch.author,fetch.keywords
];
mysql.query(fetchAddSql, fetchAddSql_Params, function(qerr, vals, fields) {
if (qerr) {
console.log(qerr);
}
});
});
}
var fetch_url_Sql = 'select url from fetches where url=?';
var fetch_url_Params = [myURL];
mysql.query(fetch_url_Sql, fetch_url_Params, function(qerr, vals, fields){
if(vals.length > 0){
console.log('URL duplicate!')
} else newsGet(myURL);//读取新闻页面
});
2.express(以网易新闻为例)
1.用express构建网站访问mysql
var express = require('express');
var mysql = require('./mysql.js')
var app = express();
//app.use(express.static('public'));
app.get('/7.03.html', function(req, res) {
res.sendFile(__dirname + "/" + "7.03.html");
})
app.get('/7.04.html', function(req, res) {
res.sendFile(__dirname + "/" + "7.04.html");
})
app.get('/process_get', function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' }); //设置res编码为utf-8
//sql字符串和参数
var fetchSql = "select url,source_name,title,author,publish_date from fetches where title like '%" +
req.query.title + "%'";
mysql.query(fetchSql, function(err, result, fields) {
console.log(result);
res.end(JSON.stringify(result));
});
})
var server = app.listen(8080, function() {
console.log("访问地址为 http://127.0.0.1:8080/7.03.html")
})
创建上述代码7.03.js作为网页后端
<!DOCTYPE html>
<html>
<body>
<form action="http://127.0.0.1:8080/process_get" method="GET">
<br> 标题:<input type="text" name="title">
<input type="submit" value="Submit">
</form>
<script>
</script>
</body>
</html>
而此代码7.03.html作为网页前端
之后先运行js文件,再前往html文件中相应网址进行查询,网页如下
在搜索栏中输入:娱乐 进行搜索,结果如下图
2.用express脚手架创建网站框架
脚手架内容我不太了解,经过对老师的询问老师也表示脚手架的知识对于现在的我们来说还不需要掌握,具体分析日后补充
现在我的实力只能按照ppt来依葫芦画瓢
我们在代码位置创建文件夹search_site并安装express-generator文件
最终文件夹如图
接着在命令提示符打开search_site文件夹通过运行命令npm install mysql -save来安装全部所需modules
var express = require('express');
var router = express.Router();
var mysql = require('../mysql.js');
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
router.get('/process_get', function(request, response) {
//sql字符串和参数
var fetchSql = "select url,source_name,title,author,publish_date " +
"from fetches where title like '%" + request.query.title + "%'";
mysql.query(fetchSql, function(err, result, fields) {
response.writeHead(200, {
"Content-Type": "application/json"
});
response.write(JSON.stringify(result));
response.end();
});
});
module.exports = router;
此代码实现了网页中的关键字搜索
之后在search_site/public创建html文件来创建网页界面,命名为crawl.html
<!DOCTYPE html>
<html>
<header>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
</header>
<body>
<form>
<br> 标题:<input type="text" name="title_text">
<input class="form-submit" type="button" value="查询">
</form>
<div class="cardLayout" style="margin: 10px 0px">
<table width="100%" id="record2"></table>
</div>
<script>
$(document).ready(function() {
$("input:button").click(function() {
$.get('/process_get?title=' + $("input:text").val(), function(data) {
$("#record2").empty();
$("#record2").append('<tr class="cardLayout"><td>url</td><td>source_name</td>' +
'<td>title</td></tr>');
for (let list of data) {
let table = '<tr class="cardLayout"><td>';
Object.values(list).forEach(element => {
table += (element + '</td><td>');
});
$("#record2").append(table + '</td></tr>');
}
});
});
});
</script>
</body>
</html>
准备工作就完成了,接下来看看效果图
达到这个效果之后,本次作业就算勉强完成了
由于目前知识面的缺失,有些地方尚待补充完善,请谅解
yysy,这门课感觉好难
目录