用Node.JS实现爬虫

文章目录


前言

这学期开设了web编程课,第一次实验项目是做一个新闻爬虫及爬取结果的查询网站

以下是这次作业的核心需求
用Node.JS实现爬虫

一、爬虫是什么

爬虫就是能够自动访问互联网并将网站内容下载下来的的程序或脚本,类似一个机器人,能把别人网站的信息弄到自己的电脑上,再做一些过滤,筛选,归纳,整理,排序等等。

二、前期学习

以下主要是对老师代码的分析,以及一些拓展学习
1. 首先引入必要的一些模块库

var fs = require('fs');//保存到本地文件
var myRequest = require('request');//request是服务端发起请求的工具包
var myCheerio = require('cheerio');//cheerio是jquery核心功能的一个快速灵活而又简洁的实现,主要是为了用在服务器端需要对DOM进行操作的地方
var myIconv = require('iconv-lite');//编码转换 GB2312到UTF-8
require('date-utils');

以下是对Request和Cheerio模块库简单用法的介绍
request库的七个主要用法用Node.JS实现爬虫
cheerio文档的api主要可以分为下面几个方面,
加载(loading)
选择器(selectors)
属性操作(attributes)
结构推导(traversing)
结构操作(manipulation)
实用方法(Miscellaneous & Utilities)

2.选择要爬取的网站(这里以中国新闻网为例)

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

3.定义网站的元素内容和网页的正则

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{}).shtml/;

正则表达式的学习非常重要****
以下正则的一些基本语法,一开始觉得正则好难啊,总写不对

代码 说明
. 匹配除换行符以外的任意字符
\w 匹配除换行符以外的任意字符
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束
{n} 重复n次
{n,m} 重复n到m次
\W 匹配不是字母、数字、下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词的开始或结束的位置
//防止网站屏蔽我们的爬虫
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)
};

5.读取种子网站,遍历要爬取的链接,并存在文件中

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); //读取新闻页面
    });
});

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

        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", ""); //摘要   
        var filename = source_name + "_" + (new Date()).toFormat("YYYY-MM-DD") +
            "_" + myURL.substr(myURL.lastIndexOf('/') + 1) + ".json";
        存储json
        fs.writeFileSync(filename, JSON.stringify(fetch));
    });

    
} 

6.爬取成功
用Node.JS实现爬虫
7.用数据库存储信息

Mysql安装配置
1.下载安装包后将其解压到c:\mysql文件夹
然后以管理员身份打开命令行

2.在命令行中cd c:\mysql\bin
安装mysql的服务:mysqld –install
初始化mysql:mysqld --initialize --console,初始化会产生一个随机密码,如下图所示
启动mysql:net start mysql
命令行执行 mysql -u root –p 进入mysql,按提示输入刚才得到的随机密码(冒号之后的所有字符)
用Node.JS实现爬虫

3.进入mysql后可将密码修改为自己的密码
alter user ‘root’@‘localhost’ identified by ‘root’;
Quit退出后再用mysql -u root –p 进入mysql,密码为root
给root用户授权从客户端访问
alter user ‘root’@‘localhost’ identified with mysql_native_password by ‘root’;
flush privileges;
用Node.JS实现爬虫
Mysql使用
进入mysql后可创建一个数据库crawl,然后再创建一个表fetches:
create database crawl;
use crawl; create table ‘fetches’……
可以打开文件fetches.sql右键拷贝到命令行里
show tables;
用Node.JS实现爬虫

三、我的项目的完成

1.首先选取要爬的网站,并引入要用的模块库

var myRequest=require('request');
var myCheerio=require('cheerio');
require('date-utils');
var myIconv = require('iconv-lite');
var mysql=require('./mysql.js');  
var myEncoding="UTF-8";


var source_name="科技迅";
var seedURL='http://www.kejixun.com';//种子网站的网址
var url_reg=/\/article\/(\d{6})\/(\d{6}).shtml/;//获取的链接的正则

用Node.JS实现爬虫

但是,我爬出来的数据都是乱码
用Node.JS实现爬虫
在一番摸索之后发现,这个网站的编码是gbk,经过修改,终于获得正确的数据了
用Node.JS实现爬虫
用Node.JS实现爬虫

2.分析网站中我要爬取的链接

用Node.JS实现爬虫

$('.firstAdd-con ul li').each(function() { //遍历链接  
            var myURL = "";
            try {
                var href = "";
                href = $(this).find('a').attr('href');
                if (href == undefined) return;
                myURL = href;
            } catch (e) { console.log('识别新闻链接出错:' + e) }

3.分析和获取我要爬取的具体元素内容
用Node.JS实现爬虫
在这一步,我遇到了几个难题
首先第一个困难是
标签<class="main f1">,我发现这个class对应多个属性值,我在获取对象时,如果写成 $('.main f1'),则无法找到同属main和f1的;由于发现网页后面的代码中f1标签还有其他用处,于是我这里用 main标签获取$('.main')

第二个难题是我要获取的作者,时间和资料来源在一个标签下,我要想办法把它们分开
发现它们三者中间有一个小原点,可以将这个换成一个字符串语句,然后把它们分开

var tempArry = $first.find('.writer').text().split('·');
        fetch.author = tempArry[0];
        // console.log(fetch.author);
        fetch.publish_date = tempArry[1];
        fetch.content =$first.find('.introduction').text();
        fetch.keywords=$('meta[name="keywords"]').eq(0).attr('content');

4.将我要的信息存入数据库
在这里介绍一下我数据库的结构
除了一些作者,标题,关键字等,由于最近大家的知识产权意识越来越强,我就加上版权信息(copyright)吧

CREATE TABLE `fetches` ( 
  `id_fetches` int(11)  NOT NULL AUTO_INCREMENT,
  `url` varchar(100) DEFAULT NULL,
  `source_name` varchar(100) DEFAULT NULL,
  `source_encoding` varchar(45) DEFAULT NULL,
  `title` varchar(50) DEFAULT NULL,
  `keywords` varchar(200) DEFAULT NULL,
  `writer` varchar(200) DEFAULT NULL,
  `copy_right`varchar(100)DEFAULT NULL,
  `publish_date` date DEFAULT NULL,
  `crawltime` datetime DEFAULT NULL,
  `content` longtext,
  `createtime` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id_fetches`),
  UNIQUE KEY `id_fetches_UNIQUE` (`id_fetches`),
  UNIQUE KEY `url_UNIQUE` (`url`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

存入数据库

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

5.网页展示
网页展示首先可以自己自己搭建前后端来实现
也可以借助express脚手架来搭建网站

以下是我用express脚手架来创建的一个网站框架

express –e search_site
-e的参数表明我们用ejs作为缺省view引擎,而非采用jade。
然后生成出一个search_site的文件夹
由于我们需要使用mysql,因此将mysql.js拷贝进这个文件夹
用Node.JS实现爬虫
mysql.js拷贝后还需要在search_site文件夹内cmd运行:
npm install mysql –save
–save表示将mysql包安装到该项目中,并且将依赖项保存进package.json里
在search_site文件夹内cmd运行npm install
将package.json中列举的依赖项全部安装,完成网站搭建。

这里着重介绍一下我的查询页面在原有基础上做的一些改动
1.由于觉得只搜索文章标题太单一,又添加了时间和网站来源的搜索项
2.关于关键词的热度分析,我想到了可以用图片的形式展示,但由于我目前能力有限,不会用python写词云 ,只能先借助其他生成工具生成图片,然后将图片插入页面中
3.将背景颜色设置成薄荷绿~~,白色有点单调~~

<body bgcolor="F5FFFA">
    <form>
        <img src="images/词云.jpg" style="float:left;" width="180" height="180" />
        <h2>搜索近日爬取的科技新闻</h2> 
        <br> <strong>标题:</strong><input type="text" name="title_text">
        <input class="form-submit" type="button" value="查询">
        <br> <strong>时间:</strong><input type="text" name="title_text">
        <input class="form-submit" type="button" value="查询">
        <br> <strong>网站:</strong><input type="text" name="title_text">
        <input class="form-submit" type="button" value="查询">
        <br/>
        <a href="images/词云.jpg"><input type = "button" value="热度词词云"></a>
        <br />
    </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"><th>url</th><th>source_name</th>' +
                        '<th>title</th><th>author</th><th>publish_date</th><th>copy_right</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>');
                    }
                });
            });

        });
    </script>
</body>

最终效果图如下
用Node.JS实现爬虫
6.拓展
定时爬虫功能

// !定时执行
var rule = new schedule.RecurrenceRule();
var t1 = [8, 20]; //每天2次自动执行
var t2 = 20; //定义在第几分钟执行
rule.hour = t1;
rule.minute = t2;

//定时执行httpGet()函数
schedule.scheduleJob(rule, function() {
    seedget();
});

总结

终于,我的第一个作业完成了。对于一个爬虫小白来说,完成这次作业着实不易。一开始,我捧着js的那本犀牛书看啊看啊,觉得把js学懂了就能看懂老师代码了,后来发现并不是如此。于是,我从老师的代码入手,理解老师代码的大致思路,这其中有很多看不懂的细节,我就自己查询资料和问助教老师。在大致理解老师的代码后,开始着手完成自己的项目,这其中遇到了许多难题,一个个解决,我发现自己的自学能力和分析问题,解决问题的能力提升了,看到自己的爬虫终于能运行了,内心确实有一些成就感。最后,非常感谢助教老师的耐心指导和同学的帮助。

上一篇:fetch与axios


下一篇:Fetch 讲解