利用Nodejs实现爬虫


前言

本学期期中作业是 新闻爬虫及爬取结果的查询网站,作为只有c语言基础的小菜鸟,刚看到要求时还是一脸懵,通过半个学期的学习,借助老师的代码,撸起袖子加油干,跌跌撞撞地也实现了爬虫。先来看看啥是爬虫,爬虫就是个自动获取网络内容的程序,又称为网页蜘蛛,网络机器人......(来自百度百科...)ok 话不多说 现在开始实现新闻爬虫以及爬取结果的查询网站。


一.工具安装

1.Nodejs

百度搜索nodejs进入官网

 点击下载,找到自己需要的版本

按照提示一路next 安装完成 在cmd中输入node -v可以查看安装版本

2.编码工具VsCode

这里因为以前写C/C++时下载过VScode所以稍微下个插件直接用了,也可以下载WebStorm,Sublime等等

WebStorm下载地址: WebStorm: The Smartest JavaScript IDE, by JetBrains

Sublime下载地址: Sublime Text - Text Editing, Done Right

VScode是一个轻量且强大的跨平台开源代码编辑器(IDE) 打开VScode应用商店下载nodejs插件

3.安装MySQL

下载地址:MySQL :: Download MySQL Community Server

数据库 根据自己需要的版本 下载 后面会详细描述

要下载的工具下载的差不多了,下面正式开始爬虫


二、引入模块

这里先看一下npm这个东西

npm就是Nodejs下的包管理器

  • 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
  • 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
  • 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用

但是,npm的服务器位于国外可能会影响安装 所以淘宝团队做了国内镜像cnpm,与官方同步频率目前为 10分钟 一次以保证尽量与官方服务同步。

安装方法:

  • 安装:命令提示符执行
    npm install cnpm -g --registry=https://registry.npm.taobao.org
  • cnpm -v 来测试是否成功安装

通过改变地址来使用淘宝镜像

  • npm的默认地址是 https://registry.npmjs.org/
  • 可以使用npm config get registry查看npm的仓库地址
  • 可以使用npm config set registry https://registry.npm.taobao.org来改变默认下载地址,达到可以不安装cnpm就能采用淘宝镜像的目的,然后使用上面的get命令查看是否成功。

搞定之后 就可以用cnpm来下载需要的包

之后可以根据需要引入不同的包,在npm网站可以对包的用法进行查询

比如

获取网页内容(http\request\superagent等)

筛选网页信息(cheerio)

输出或存储信息(console\fs\mongodb\mysql等)

下面简单介绍几个

1.request来获取网页内容

var request = require('request');

// 通过 GET 请求来读取 http://cnodejs.org/ 的内容
request('https://nba.hupu.com/', function (error, response, body) {
  if (!error && response.statusCode == 200) {
    // 输出网页内容
    console.log(body);
  }
});

 运行之后得到

利用chrome 右键 查看网页源代码 发现和上面是一样的

2.mysql来连接到数据库

用来实现将数据存储到数据库中

var mysql = require("mysql");
var pool = mysql.createPool({
    host: '127.0.0.1',
    user: 'root',
    password: 'root',//密码
    database: 'crawl'
});

 后面会详细介绍

3.cheerio抓取页面模块

cheerio是nodejs为服务器特别定制的,快速、灵活、实施的jQuery核心实现。适合各种Web爬虫程序。

借用老师发的代码来爬一篇北京大学官网的文章

var myRequest = require('request')
var myCheerio = require('cheerio')
var myURL = 'https://www.pku.edu.cn/about.html'
function request(url, callback) {
    var options = {
        url: url,  encoding: null, headers: null
    }
    myRequest(options, callback)
}
request(myURL, function (err, res, body) {
    var html = body;  
    var $ = myCheerio.load(html, { decodeEntities: false });
    console.log($.html()); 
})  

运行之后得到

 搞定


三.开始爬虫

下面根据老师发的代码示例,进行分析,然后实现爬虫,将新闻存储到自己的电脑中。

首先分析要爬取的网站 打开网页后点击检查,可以查看该网页相关部分的html代码

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');

首先安装所需要的包

当发现提示throw err; ^ Error: Cannot find module 'xxxx'时

说明并没有安装xxxx这个包

在命令行输入cnpm install xxxx 回车就可以下载所需要的包 

var source_name = "中国新闻网";
var myEncoding = "utf-8";
var seedURL = 'http://www.chinanews.com/';

要爬取的网站 以及查看的此网站编码是utf-8 以及正则表达式。。

正则表达式是第一次接触到的概念,其实就是一种被用来检索、替换那些符合某个模式(规则)的文本

这里附上学习链接 正则表达式 – 语法 | 菜鸟教程

var seedURL_format = "$('a')";
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var date_format = "$('#pubtime_baidu').text()";
var author_format = "$('#editor_baidu').text()";
var content_format = "$('.left_zw').text()";
var desc_format = " $('meta[name=\"description\"]').eq(0).attr(\"content\")";
var source_format = "$('#source_baidu').text()";
var url_reg = /\/(\d{4})\/(\d{2})-(\d{2})\/(\d{7}).shtml/;
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'
}

 防止网站屏蔽我们的爬虫 很多网站都反爬虫 这里需要伪装

function request(url, callback) {
    var options = {
        url: url,
        encoding: null,
        headers: headers,
        timeout: 10000 
    }
    myRequest(options, callback)
}

构造模拟浏览器的request 爬下来的内容以浏览网址形式储存

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);
        //console.log(seedurl_news);
    } 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 (typeof(href) == "undefined") {  // 有些网页地址undefined
                return true;
            }
            if (href.toLowerCase().indexOf('http://') >= 0 || href.toLowerCase().indexOf('https://') >= 0) myURL = href; //http://开头的或者https://开头
            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);
        newsGet(myURL); //读取新闻页面
    });
});

如果要爬取新闻内容的话 就必须读取种子页面 思路是解析种子页面的所有 a href 链接并遍历——规整化链接,符合新闻url的正则表达式的就爬取

function newsGet(myURL) { //读取新闻页面
    request(myURL, function(err, res, body) { //读取新闻页面
        //try {
        var html_news = myIconv.decode(body, myEncoding); //用iconv转换编码
        //console.log(html_news);
        var $ = myCheerio.load(html_news, { decodeEntities: true });
        myhtml = html_news;
        //} catch (e) {    console.log('读新闻页面并转码出错:' + e);};

        console.log("转码读取成功:" + myURL);
        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();

构建json对象准备写入文件并且建立一个空fetch以存储数据 这样可以将爬到的数据存储到构建的json文件中

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); //标题

        if (date_format != "") fetch.publish_date = eval(date_format); //刊登日期   
        console.log('date: ' + fetch.publish_date);
        console.log(myURL);
        fetch.publish_date = regExp.exec(fetch.publish_date)[0];
        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 (author_format == "") fetch.author = source_name; //eval(author_format);  //作者
        else fetch.author = eval(author_format);

        if (content_format == "") fetch.content = "";
        else fetch.content = eval(content_format).replace("\r\n" + fetch.author, ""); //内容,是否要去掉作者信息自行决定

        if (source_format == "") fetch.source = fetch.source_name;
        else fetch.source = eval(source_format).replace("\r\n", ""); //来源

        if (desc_format == "") fetch.desc = fetch.title;
        else fetch.desc = eval(desc_format).replace("\r\n", ""); //摘要    

需要爬取的内容标题 日期 作者 内容 来源 以及摘要

ar filename = source_name + "_" + (new Date()).toFormat("YYYY-MM-DD") +
            "_" + myURL.substr(myURL.lastIndexOf('/') + 1) + ".json";
        存储json
        fs.writeFileSync(filename, JSON.stringify(fetch));

最后 将爬取的内容储存到fetch中并建立文件

到这里基本完成一个可以爬取新闻网页内容,并且存储的爬虫,下面来运行看结果

文件夹中出现很多的json文件 用记事本打开看一下内容 

得到了想到得到的内容 还不错 


三.使用数据库

第一次使用mysql,这也是我走最多弯路的地方,在官网安装完成后

完成一系列配置工作 可以参照MySQL 安装 | 菜鸟教程

可以在我的电脑——管理——服务中找到MySQL

在此过程中我也遇到很多问题:

比如 进行配置时一定要以管理员身份运行。

第一次登陆后会要求强制修改密码,好好记住自己的密码...... 

还有这种低级的...mysql写成mysqld...... 

记住实在不行就重启 net stop mysql 和net start mysql

ok 当你克服了种种困难终于进入了你的数据库 

因为要将数据存入数据库中,所以对刚才的代码进行修改

首先将存入json文件部分注释掉


var mysql = require('./mysql.js');

加入所需要的模块

var mysql = require("mysql");
var pool = mysql.createPool({
    host: '127.0.0.1',
    user: 'root',
    password: 'root',//密码
    database: 'crawl'
});
var query = function(sql, sqlparam, callback) {
    pool.getConnection(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) {
    pool.getConnection(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;

 mysql.js文件 其中数据库的用户名是root 密码为root 使用database名为crawl 当然这些东西可以自己设置

比如进入数据库后在命令提示符中使用create database xxx创建名为xxx的database 使用use xxx使用名为xxx的database等等操作......

当然这里我还是根据老师的来操作了

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);
            }

实现将内容写入到数据库中 

代码修改完成后进去数据库,借用老师的fetches代码,创建表格

创建完成后 运行程序

 进入数据库中 输入select url , title from fetches;(注意结尾要有分号)可以看到

爬取的内存存储到了数据库的表格中(这里可以下载navicat等数据库管理软件,嫌麻烦我并没有下....想要更加直观对数据库进行可视化可以尝试)

到这里就实现了将新闻网页内容进行爬取,并且存入数据库中。


四.查询网站

 1.前端

创建一个html文件,以实现输入要查询title而输出包含查询内容的新闻

<!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> 

2.后端

js代码如下:

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")

})

code runner运行之后:

 在浏览器中打开网址后

在标题中输入要查询的内容,例如“赵立坚”

就可以看到含有“赵立坚”的新闻内容 


 五.拓展

在完成了老师的代码例子之后,开始自己寻找可以爬取的新闻网站进行爬虫操作,以及存入数据库

但是要注意对网站源代码进行分析 进行代码修改

爬取结束之后 打开数据库看一下

从另一个网站爬取了很多新闻 一共有了150条 数据库中多了很多内容 ok搞定