Web编程期中作业,新闻网站爬虫
一、安装Mysql数据库
由于我以前安装过mysql的数据库,而现在密码忘记了,所以我就重新安装了mysql
使用Node.js连接Mysql数据库
npm install --save mysql
var pg=require('pg');
var config={//连接设置
user:"root",
database:"crawler",
password:"****",
port:3306,
}
var pool=new pg.Pool(config);
var query = function(sql, sqlparam, callback) {//带参数的sql查询
pool.connect(function(err, conn) {
if (err) {
callback(err, null, null);
} else {
conn.query(sql, sqlparam, function(qerr, vals, fields) {
conn.release(); //释放连接
callback(qerr, vals, fields); //事件驱动回调
});
}
});
};
var query_noparam = function(sql, callback) {//不带参数的sql查询
pool.connect(function(err, conn) {
if (err) {
callback(err, null, null);
} else {
conn.query(sql, function(qerr, vals, fields) {
conn.release(); //释放连接
callback(qerr, vals, fields); //事件驱动回调
});
}
});
};
exports.query = query;
exports.query_noparam = query_noparam;
其中关于node.js中函数的诡异写法在官网中这种书写方法叫做基于事件驱动的回调,如上面代码中的
conn.query(sql, function(qerr, vals, fields) {
conn.release(); //释放连接
callback(qerr, vals, fields); //事件驱动回调
});
我们给某个方法传递了一个函数,这个方法在有相应事件发生时调用这个函数来进行回调 。
二、爬取新闻网页
1、爬取网易新闻网页
通过观察网易新闻的导航页和新闻主体页面,我们发现网易新闻的导航页中的具体新闻连接在<a></a>
标签下,而具体文章的连接如
https://www.163.com/dy/article/G8HC1KFV0515DHOR.html
其中article和最后的长度为16的字符串是不变的,dy为特定的某一类新闻。
故我们可以写出匹配具体url的正则表达式
var url_reg = /\/(dy)\/(article)\/(\S{16}).html/;
之后再观察具体网页内容的html代码,我们修改爬虫代码的选择器。
var source_name = "网易新闻";
var domain = 'https://www.163.com/sports/article/G8LLSDAL00058781.html';
var myEncoding = "utf-8";
var seedURL = 'https://www.163.com/sports/article/G8LLSDAL00058781.html';
var seedURL_format = "$('a')";
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var date_format = "$('#ne_wrap').eq(0).attr(\"data-publishtime\")";
var author_format = "$('.post_author').text()";
var content_format = "$('.post_body').text()";
var desc_format = " $('meta[name=\"description\"]').eq(0).attr(\"content\")";
var source_format = "$('.post_info').text()";
var url_reg = /\/(sports)\/(article)\/(\S{16}).html/;
爬取得到网易新闻的内容。
我们的数据库设计只有一个fetches表,包含11个字段。
id_fetches | url | source_name | source_encoding | title | keywords | author | publish_date | crawltime | content | createtime |
---|---|---|---|---|---|---|---|---|---|---|
Integer(Primary Key) | TEXT | TEXT | TEXT | TEXT | TEXT | TEXT | date | date | TEXT | date |
2、爬取环球网新闻
环球网
同爬取网易新闻时相同,观察环球网新闻的html代码,修改爬虫选择器。
var source_name = "环球网新闻";
var domain = 'https://www.huanqiu.com/';
var myEncoding = "utf-8";
var seedURL = 'https://www.huanqiu.com/';
var seedURL_format = "$('a')";
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var date_format = " $('.time').text()";
var author_format = "$('.author').text()";
var content_format = "$('.l-con clear').text()";
var desc_format = " $('meta[name=\"description\"]').eq(0).attr(\"content\")";
var source_format = "$('.source').text()";
var url_reg = /\/(article)\/(\S{11})/;
3、爬取新浪新闻
和爬取前两个网页的方法相同,这里只给出修改的正则表达式信息,和jquery选择器。
var source_name = "新浪新闻";
var domain = 'https://news.sina.com.cn/';
var myEncoding = "utf-8";
var seedURL = 'https://news.sina.com.cn/';
var seedURL_format = "$('a')";
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var date_format = " $('meta[name=\"weibo: article:create_at\"]').eq(0).attr(\"content\")";
var author_format = "$('.show_author').text()";
var content_format = "$('.article').text()";
var desc_format = " $('meta[name=\"description\"]').eq(0).attr(\"content\")";
var source_format = " $('meta[property=\"article:author\"]').eq(0).attr(\"content\")";
var url_reg = /\/(d)\/(\d{4}-\d{2}-\d{2})\/(\S{19}).shtml/;
三、搜索功能
搜索功能后端使用nodejs的express框架,在前端使用表单和ajax获得后端数据,将json数据转为表单形式显示在界面。
搜索功能支持按标题、关键词或者内容搜索。
前端:
<!DOCTYPE html>
<html>
<header>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.bootcss.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css">
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/1.4.0/jquery.js"></script>
<script type="text/javascript" src="http://echarts.baidu.com/gallery/vendors/echarts/echarts.min.js"></script>
<script src="./javascripts/echarts.min.js"></script>
<style>
.input-check {
position: absolute;
opacity: 0;
}
.input-check-label {
z-index: 10;
cursor: pointer;
}
.input-check-label .check,
.input-check-label .radio {
display: inline-block;
position: relative;
height: 1.2em;
width: 1.2em;
margin-right: 4px;
border: 1px solid #FFFF66;
border-radius: 30%;
color: rgba(255, 255, 255, 0);
background-color: rgba(255, 255, 255, 0);
vertical-align: text-bottom;
}
.input-check-label .radio {
border-radius: 50%;
}
.input-check:checked+.input-check-label .check:after {
position: absolute;
left: 0;
right: 0;
margin: auto;
content: '';
width: 0.7em;
height: 1em;
border: 3px solid #FFFF66;
border-top: none;
border-left: none;
background: transparent;
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.input-check:checked+.input-check-label .radio:after {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
content: '';
width: 0.5em;
height: 0.5em;
background-color: #FFFF66;
border-radius: 50%;
}
input {
border: 1px solid #ccc;
padding: 7px 0px;
border-radius: 3px;
padding-left: 5px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
-webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s
}
input:focus {
border-color: #FFFF66;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px #FFFF66;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px #FFFF66
}
</style>
</header>
<body>
<div style="background-image: linear-gradient(45deg, #39b54a, #8dc63f);">
<!-- <input class="input-check" type="checkbox" id="check1" />
<label class="input-check-label" for="check1"><b class="check"></b>复选框</label> -->
<div style="height: 200px;"></div>
<div style="font-size: 30px;margin-left: 35%;color: #FFFF66;width: 30px;
transform: rotate(40deg);">新</div>
<div style="font-size: 35px;margin-left: 40%;color: #FFFF66;width: 35px;
transform: rotate(-30deg);">闻</div>
<div style="background-color: #FFFF66;width: 6px;height: 16px;
margin-left: 49%;border-radius: 42%;"></div>
<div style="background-color: #FFFF66;width: 30px;height: 30px;
margin-left: 49%;border-radius: 50%;"></div>
<div style="background-color: #FFFF66;width: 6px;height: 16px;
margin-left: 50.7%;border-radius: 42%;margin-top: -46px;"></div>
<div style="font-size: 35px;margin-left: 55%;color: #FFFF66;width: 35px;
transform: rotate(40deg);">爬</div>
<div style="font-size: 30px;margin-left: 60%;color: #FFFF66;width: 30px;
transform: rotate(-30deg);">虫</div>
<form class="form-inline" style="margin-left: 30%;">
<br> <input type="text" name="title_text" class="search">
<div style="width: 15px;"></div>
<input class="form1 input-check" type="checkbox" id="check1">
<label class="input-check-label" for="check1"><b class="check"></b>
<div style="color: #FFFF66;">title</div>
</label>
<div style="width: 15px;"></div>
<input class="form2 input-check" type="checkbox" id="check2">
<label class="input-check-label" for="check2"><b class="check"></b>
<div style="color: #FFFF66;">
keywords
</div>
</label>
<div style="width: 15px;"></div>
<input class="form3 input-check" type="checkbox" id="check3">
<label class="input-check-label" for="check3"><b class="check"></b>
<div style="color: #FFFF66;">
content
</div>
</label>
<div style="width: 15px;"></div>
<input class="form-submit" type="button" value="查询" id="search_button" style="padding: 7px 7px;width: 60px;
text-align: center; color: #99CC00; border:none">
</form>
<!-- <div class="search d1">
<form class="form-inline">
<br> <input type="text" name="title_text" class="search">
<input class="form1" type="checkbox" >title
<input class="form2" type="checkbox">keywords
<input class="form3" type="checkbox">content
<input class="form-submit" type="button" value="查询" id="search_button">
</form>
</div>
-->
<form>
<br><input type="text" name="title_hot" style="margin-left: 30%;margin-right: 15px;">
<input type="button" class="form-submit" id="hot_search" value="热度分析" style="padding: 7px 7px;width: 120px;
text-align: center; color: #99CC00; border:none">
</form>
<div class="panel-body" style="height: 600px; overflow-y:scroll">
<div style="border: 1px #000000; width: 90%; margin: 0 auto;">
<div class="cardLayout" style="margin: 10px 0px">
<table width="100%" id="record2" class="table table-bordered"></table>
</div>
<div class="hot_search_Layout" style="margin: 20px 0px">
<table width="80%" id="hot_record" class="table table-bordered"></table>
</div>
</div>
</div>
<div id="Chartmain" style="width: 100px;height:50px;"></div>
</div>
<script>
$(document).ready(function () {
$("#search_button").click(function () {
var title = $('input.form1').is(":checked");
var keywords = $('input.form2').is(":checked");
var content = $('input.form3').is(":checked");
if (title.valueOf() == false && keywords.valueOf() == false && content.valueOf() == false) {
alert("至少选择一项");
return;
}
var words = $('input.search').val();
console.log(words);
$.get('/process_get?title=' + title + '&keywords=' + keywords + '&content=' + content + '&words=' + words, function (data) {
$("#record2").empty();
$("#record2").append('<tr class="cardLayout"><th>url</th><th>source_name</th>' +
'<th>title</th><th>author</th><th>publish_date</th></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>');
}
});
});
});
$(document).ready(function () {
$("#hot_search").click(function () {
$.get('/process_hot?title=' + $("input:text").val(), function (data) {
$("#hot_record").empty();
$("#record2").empty();
$("#hot_record").append('<tr class="hot_search_Layout"><th>时间</th>' +
'<th>包含指定词的新闻数量</th></tr>');
for (let list of data) {
let table = '<tr class="hot_search_Layout"><td>';
Object.values(list).forEach(element => {
table += (element + '</td><td>');
});
$("#hot_record").append(table + '</td></tr>');
}
});
});
});
</script>
</body>
</html>
后端:
后端实现在nodejs的index.js文件中,使用路由Router接收process_get路由的请求,
router.get('/process_get', function(request, response){
//var fetchSql = "select url,source_name,title,author,publish_date " +
// "from fetches where title like '%" +request.query.title + "%'"+" order by publish_date";
var fetchSql="select url,source_name,title,author,publish_date from fetches where ";
var flag=false;//标志符判断是否选择多个选项。
if(request.query.title=="true"){
if(!flag){
flag=true;
}
else{
fetchSql += "or";
}
fetchSql+="title like '%"+request.query.words+"%'";
}
if(request.query.keywords=="true"){
if(!flag){
flag=true;
}
else{
fetchSql += "or";
}
fetchSql+="keywords like '%"+request.query.words+"%'";
}
if(request.query.content=="true"){
if(!flag){
flag=true;
}
else{
fetchSql += "or";
}
fetchSql+="content like '%"+request.query.words+"%'";
}
fetchSql=fetchSql+"order by publish_date";
mysql.query(fetchSql, function(err, result, fields){
response.writeHead(200, {
"Content-Type": "application/json"
});
try{
response.write(JSON.stringify(result));
response.end();
}catch{
console.log("结果为空");
}
});
});
即通过将前端表单传入的字段,拼接为sql语句从数据库中查出返回即可。
前端搜索结果如图
四、热度分析功能
热度分析即将我们搜索得到的新闻结果按照时间进行汇总,得到具体某一天相关文章的数量。我们可以通过数据库的groupby语句实现。
前端: 新添加一个表单元件负责对相关标题新闻的热度分析搜索。
<form >
<br>标题:<input type="text" name="title_hot">
<input type="button" class="form-submit" id="hot_search" value="热度分析">
</form>
</div>
<div class="panel-body" style="height: 600px; overflow-y:scroll">
<div style="border: 1px #000000; width: 90%; margin: 0 auto;">
<div class="cardLayout" style="margin: 10px 0px">
<table width="100%" id="record2" class="table table-bordered"></table>
</div>
<div class="hot_search_Layout" style="margin: 20px 0px">
<table width="80%" id="hot_record" class="table table-bordered"></table>
</div>
</div>
</div>
<script>
$(document).ready(function(){
$("#hot_search").click(function(){
$.get('/process_hot?title='+$("input:text").val(),function(data){
$("#hot_record").empty();
$("#record2").empty();
$("#hot_record").append('<tr class="hot_search_Layout"><th>时间</th>' +
'<th>包含指定词的新闻数量</th></tr>');
for (let list of data) {
let table = '<tr class="hot_search_Layout"><td>';
Object.values(list).forEach(element => {
table += (element + '</td><td>');
});
$("#hot_record").append(table + '</td></tr>');
}
});
});
});
</script>
后端: 同搜索功能一样实现在index.js下,通过Router的process_hot路由处理请求,将表单传入数据拼接为sql语句,只不过时sql语句有所改变。
router.get('/process_hot',function(req,res){
var fetchSql="SELECT publish_date,COUNT(*) FROM fetches WHERE title LIKE '%"+req.query.title+"%'"+" GROUP BY publish_date"
mysql.query(fetchSql,function(err,result,fields){
res.writeHead(200,{
"Content-Type":"application/json"
});
res.write(JSON.stringify(result));
res.end();
});
});
实现效果如下图:
本次实验代码地址:https://gitlab.com/lianghui123/web_coding.git
五、总结
本次作业,学习了node.js的使用,了解了express的框架。实现的过程中也踩了很多坑,比如nodejs访问数据库查询返回的结果是一个Object对象,需要需要.eq(0).val()才能拿到值转为json数据。express框架的使用类似于vue-cli的使用,在实验过程中有时会出现http连接报错304的情况,经检查发现是mysql数据库的默认配置是最大的连接数为100,如果我们点击次数太多,且没有及时释放连接,会出现Too many connections的报错,导致出现连接304的错误。
只需要修改mysql文件配置,将最大连接数增大即可。
各个网站的编码可能不同,如网易新闻的dy栏目下采用的是GBK编码,如果我们使用utf-8编码解析会出现乱码的情况,故我们要先使用浏览器插件查看网页的编码格式再确定爬虫解析网页的编码格式。
不足与预期: 原生html前端界面不是很美观,可以考虑使用vue+nodejs开发网站,美化界面。