新闻网站爬虫及结果查询

新闻网站爬虫及结果查询

(一)nodejs+express搭建服务器

1.安装node.js

Node.js 安装包及源码下载地址为:https://nodejs.org/en/download/

2.全局安装express-generator

npm install express --save -g
npm install express-generator --save -g

3.初始化项目

express project1 //构建这个应用的目录结构
cd project1
npm install //安装依赖
npm start //启动项目

项目目录结构:
在这里插入图片描述
bin: 启动目录 里面包含了一个启动文件 www ,默认监听端口是3000

node_modules: 所有安装的依赖模块都在这个文件夹里面

public: 所有的前端静态资源 css image js

routes: 放的是路由文件

路由主要定义 url 和资源的映射关系 ( 一一对应关系 )

主要用来接收前端发送的请求响应数据给前端

views: 主要放置 ejs 后端模板文件

app.js: 入口文件(主文件) 总路由 (其他的路由要由它来分配)

package.json: 描述文件,最重要的是依赖的模板列表 dependencies

依赖列表里面的所有模板可以通过 npm i 一次性全部安装

4.添加依赖

在package.json文件里添加需要的依赖,如:request,Cheerio,pg等,再次安装依赖。

(二)连接数据库

1.连接池创建

在代码中引入pg模块,并编写数据库配置:

var pg = require('pg');
var config = {  
    user:"postgres",
    database:"spider",
    password:"postgres",
    port:5432,
    // 扩展属性
    max:20, // 连接池最大连接数
    idleTimeoutMillis:3000, // 连接最大空闲时间 3s
}
var pool = new pg.Pool(config);

2.编写操作数据库的函数

有参数和无参数,该函数实现连接数据库并执行SQL语句

var query = function(sql, sqlparam, callback) {
    pool.connect(function(err, conn, done) {
        if (err) {
            console.log(err)
            callback(err, null, null);
        } else {
            conn.query(sql, sqlparam, function(err, result) {
                //conn.release(); //释放连接 
                done();
                callback(err, result); //事件驱动回调 
            });
        }
    });
};
var query_noparam = function(sql, callback) {
    pool.connect(function(err, conn, done) {
        if (err) {
            callback(err, null, null);
        } else {
            conn.query(sql, function(err, result) {
                conn.release(); //释放连接 
                callback(err, result); //事件驱动回调 
            });
        }
    });
};

3.新闻表和关键词表的设计

新闻表:
id_news是每个新闻的id,设为UNIQUE属性唯一标识新闻数据
url是新闻链接
source_name是新闻的来源网站名
source_encoding是新闻的编码方式
title是新闻标题
publish_date是新闻的发表日期
content是新闻内容
关键词表:
id_word是关键词id,唯一标识该关键词
id_news是该关键词所属的新闻id,用于两表的连接
word是关键词

CREATE TABLE news (
   id_news serial UNIQUE, //避免重复
   url text DEFAULT NULL UNIQUE,
   source_name varchar(50) DEFAULT NULL,
   source_encoding varchar(45) DEFAULT NULL,
   title varchar(100) DEFAULT NULL,
   publish_date date DEFAULT CURRENT_TIMESTAMP,
   content text,
  PRIMARY KEY (id_news)
);

CREATE TABLE splitwords (
   id_word serial UNIQUE, 
   id_news int, //两表的连接
   word varchar(50) DEFAULT NULL
);

(三)爬虫

爬取了三个网站:雪球网、中国财经网、网易新闻网。
每一个爬虫的大致步骤都是:
1、读取种子页面
2、分析出种子页面里的所有新闻链接,并爬取内容,解析成结构化数据
3、将结构化数据存储到数据库中
下面以中国财经网为例

1.读取种子页面

引入需要的包

var pgsql = require('../pg.js');
var myIconv = require('iconv-lite');
var myRequest = require('request');
var myCheerio = require('cheerio');

构造模仿浏览器的request是每个爬虫都包含的部分,定义来header来防止网站屏蔽,并且定义了request函数,调用该函数能够访问指定的url并且能够设置回调函数来处理得到的html页面。

//防止网站屏蔽我们的爬虫
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) {//request module fetching url
    var options = {
        url: url,
        encoding: null,
        headers: headers,
        timeout: 10000
    }
    myRequest(options, callback)
}

爬取种子页面,并且获取其中每一个新闻对应的链接,其对应的代码如下:
其中seedurl_news对应的页面上所有新闻,使用seedurl_news.each()对每一块新闻获取其具体url,并且检查了url是否在数据库中已经存储过了,避免重复插入数据

var seedURL = 'http://www.chinanews.com/finance/';
request(seedURL, function(err, res, body) { //读取种子页面
    //用iconv转换编码
    var html = myIconv.decode(body, myEncoding);
    //console.log(html);
    //准备用cheerio解析html
    var $ = myCheerio.load(html, { decodeEntities: true });
    var seedurl_news;
    try {
        seedurl_news = $('a');
        //console.log(seedurl_news);
    } catch (e) { console.log('url列表所处的html块识别出错:' + e) }
    seedurl_news.each(function(){
        try {
            var href = "";
            href = $(this).attr('href');
        }catch(e) {
            console.log('get the seed url err' + e);
        }
        //if (href.startsWith('//')) href = 'http:' + href;
        if (!url_reg.test(href)) {
            console.log(href + " not match the url_reg");
            return;
        }
        href = String(href);
        if (href.indexOf("//www.chinanews.com/cj") != 0) {
            // console.log(href + " not match the right url");
            return ;
        }
        href = 'http:' + href;
        console.log("crawler " + href);
        // not try to crawl the repeat page
        var fetch_url_Sql = 'select url from news where url= $1';
        var fetch_url_Sql_Params = [href];
        pgsql.query(fetch_url_Sql, fetch_url_Sql_Params, function(err, result) {
            if (err) {
                console.log(err)
            } else { // a new page
                if(result) {
                    console.log("URL " + href + " repeat");
                } else {
                    newsGet(href);
                }
            }
        });
    })
});

2.爬取新闻链接内容

在获取了新闻详情页面后,调用newsGet函数,爬取所需要的内容。
爬取的信息包括:新闻标题、内容、来源、发表时间,并对内容、发表时间等内容的格式进行了处理

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.publish_date = (new Date()).toFormat("YYYY-MM-DD");
        //fetch.html = myhtml;
        fetch.url = myURL;
        fetch.source_name = source_name;
        fetch.source_encoding = myEncoding; //编码
        if (title_format == "") fetch.title = ""
        else fetch.title = eval(title_format); //标题
        if (date_format != "") fetch.publish_date = eval(date_format); //$('.article-meta').children().eq(1).text(); //刊登日期   
        console.log(fetch.publish_date);
        fetch.publish_date = fetch.publish_date.split(" ")[0];
        console.log('date: ' + fetch.publish_date);
        fetch.publish_date = fetch.publish_date.replace('年', '-')
        fetch.publish_date = fetch.publish_date.replace('月', '-')
        fetch.publish_date = fetch.publish_date.replace('日', '')
        fetch.publish_date = new Date(fetch.publish_date).toFormat("YYYY-MM-DD");
        if (content_format == "") fetch.content = "";
        else fetch.content = eval(content_format).replace('\n', "").replace(" ", ""); //内容,是否要去掉作者信息自行决定
        //console.log(fetch.content);
        }
        });

3.存储结构化数据

执行SQL语句把新闻数据插入news表

// save the result in postgresql
        var fetchAddSql = 'INSERT INTO news (url, source_name, source_encoding, title, publish_date, content)' 
        + 'VALUES ($1, $2,$3,$4,$5, $6)';
        var fetchAddSql_Params = [fetch.url, fetch.source, fetch.source_encoding,
            fetch.title, fetch.publish_date, fetch.content
        ];

        //执行sql,数据库中fetch表里的url属性是unique的,不会把重复的url内容写入数据库
        pgsql.query(fetchAddSql, fetchAddSql_Params, function(err, result) {
            if(err) {
                console.log(err);
            }
        });

4.关键词表数据的插入

利用nodejieba.extract()方法,提取新闻内容中的关键词,并剔除停用词。

var fetchAddSql = 'select id_news, content from news';
pgsql.query_noparam(fetchAddSql, function(err, result) {
    for(var i = 0; i < result.rows.length; i++) {
        var words = nodejieba.extract(result.rows[i].content,30);
        var id_news = result.rows[i].id_news;
        var insert_word_Sql;
        var insert_word_Params;
        for(var j = 0; j < words.length; j++) {
            if(!stop_words.has(words[j].word)) {
                insert_word_Sql = 'insert into splitwords(id_news, word)' + ' VALUES ($1, $2)';
                insert_word_Params = [id_news, words[j].word];  
                // console.log(words[j]);
                pgsql.query(insert_word_Sql, insert_word_Params, function(err, result) {
                    if(err) {
                        console.log(err);
                    }
                });
            }
        }
    }
    
});

数据库显示:
news表:

关键词表:
在这里插入图片描述

(四)结果查询网站的搭建

1.用pgsql查询已爬取的数据(按时间顺序)

构建sql查询语句并且调用pgsql.query_noparam进行查询即可,且对标题、关键词、源网站的查询都是按照时间降序排列,最新的新闻排在旧的新闻前面呈现。

router.get('/get_title', function (req, res) {
    res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' }); 
    // console.log(req.query.title);
        var fetchSql = "select url,source_name,title,publish_date " +
        "from news where title like '%" + req.query.title + "%' Order By publish_date desc" ;
    pgsql.query_noparam(fetchSql, function(err, result, fields) {
        // console.log(result.rows);
        res.end(JSON.stringify(result.rows));
        // console.log(res);
    });
});

2、用网页发送请求到后端查询

前端采用文本框样式,获取输入的查询词,并提交给后端
每个文本框及按钮绑定不同的id,便于访问不同的路由

<title>新闻信息检索</title>
<h1>输入新闻标题并搜索:</h1>
 <input type="text" name="title" id="title_text">  
<input type="button" value="搜索" id="title_button">
	<h1>输入源网站名并搜索:</h1>
 <input type="text" name="source_name" id="source_name_text">  
<input type="button" value="搜索" id="source_name_button">
	<h1>输入新闻关键词并搜索:</h1>
 <input type="text" name="keyword" id="keyword_text">  
<input type="button" value="搜索" id="keyword_button">

网页显示:
在这里插入图片描述

3.用表格显示查询结果

采用现有的css框架bootstrapTable呈现查询结果

$('#title_table').bootstrapTable({
                        url:params,
                        method:'GET',
                        pagination:true,
                        sidePagination:'client',
                        pageSize:5,
                        striped : true,
                        sortable : true,
                        sortOrder:"asc",
                        showRefresh:true,
                        search:true,
                        showToggle: true,
                        toolbar: '#toolbar',
                        showColumns : true,
                        columns:[{
                            field :'url',
                            title : 'url',
                            sortable : true
                        }, {
                            field:'title',
                            title:'title'
                        }, {
                            field:'source_name',
                            title:'source_name',
                            sortable : true
                        },{
                            field:'publish_date',
                            title:'publish_date',
                            sortable : true

                        }]
                    })

表格显示:
在这里插入图片描述

4.关键词的时间热度分析

从数据库的新闻信息中统计每天包括该关键词的新闻数量,按照时间顺序降序排列

router.get('/order_keyword', function (req, res) {
    res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' }); 
    // console.log(req.query.keyword);
    var fetchSql = "SELECT publish_date,count(*) as Num FROM news WHERE content like '%" + req.query.keyword + "%' group by publish_date order by publish_date desc;";
    pgsql.query_noparam(fetchSql, function(err, result, fields) {
        // console.log(result.rows);
        res.end(JSON.stringify(result));
        // console.log(res);
    });
});

结果展示:
在这里插入图片描述
图表形式更加直观:
引入echarts.js库,绘制动态柱状图

var myChart = echarts.init(document.getElementById('chart'));
    // 指定图表的配置项和数据
    var option = {
        title: {
            text: '关键词时间热度图'
        },
        tooltip: {},
        legend: {
            data:['新闻数量']
        },
        xAxis: {
            data: dates
        },
        yAxis: {},
        series: [{
            name: '新闻数量',
            type: 'bar',
            data: nums
        }]
    };

    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option);
                });

在这里插入图片描述

5.网站展示

为了美化前端界面,加入了背景图片以及鼠标跟随特效
在这里插入图片描述

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值