如何用js完成爬虫项目

  • 前言
  • 一、node.js的安装
  • 二、mysql的安装
  • 三、确定爬取网页
  • 四、查看分析网页源码
  • 五、开始写爬虫
  • npm···
  • Node调用mysql
  • 定义要访问的网站
  • 定义新闻页面里具体的元素的读取方式
  • 定义哪些url可以作为新闻页面
  • 构造一个模仿浏览器的request
  • 读取种子页面
  • 解析出种子页面里所有的链接
  • 遍历种子页面里所有的链接
  • 规整化所有链接,如果符合新闻URL的正则表达式就爬取
  • 读取具体的新闻页面,构造一个空的fetch对象用于存储数据
  • 读取新闻页面中的元素并保存到fetch对象里
  • 到这里爬虫已经差不多写好了
  • 六、构建网站访问数据库中爬取到的内容
  • 首先创建一个7.03.html作为网页端(前端)
  • 再创建一个7.03.js作为后端
  • Node运行7.03.js后访问http://127.0.0.1:8080/7.03.html
  • 用express脚手架来创建一个网站框架
  • 用vscode打开文件search_site/routes/index.js
  • 在search_site/public/下创建一个search.html
  • 在search_site文件夹下cmd运行(node bin/www),然后用浏览器打开网址http://127.0.0.1:8080/search.html


前言

这是我第一次尝试写爬虫,爬虫代码肯定有很多地方写得不太好,不过思路大致是这个样子,之后我还会做些优化。
在这里我用的vscode写的一个新闻网站的爬虫。
可以自行安装vscode。
开始之前还要做些准备工作。



一、node.js的安装

Windows下https://nodejs.org/ 官网下载安装最新的LTS版本。
MAC OS下载相应的pkg包安装。
Linux下载tar.gz包解压运行。
特殊linux版本或者嵌入式系统(比如树莓派系统)可下载源码编译。
具体安装例子见:
https://www.runoob.com/nodejs/nodejs-install-setup.html

二、mysql的安装

https://dev.mysql.com/downloads/mysql/
在这里下载安装包后将其解压到c:\mysql文件夹。

然后以管理员身份打开命令行。

爬虫 运行javascript js做爬虫_javascript


然后自己动手安装配置好后就可以开始使用了

进入mysql后可创建一个数据库crawl,然后再创建一个表fetches。

新建文件fetches.sql。

爬虫 运行javascript js做爬虫_javascript_02


然后就可以拷贝到命令行里。

爬虫 运行javascript js做爬虫_mysql_03

三、确定爬取网页

我选取的是新华网http://www.xinhuanet.com/

四、查看分析网页源码

在该新闻网页右键查看源代码。

如:

爬虫 运行javascript js做爬虫_nodejs_04


分析我们需要爬取的内容。

五、开始写爬虫

npm···

爬虫 运行javascript js做爬虫_js_05


我们需要先在终端输入

npm i request npm i cheerio npm i iconv-lite npm i data-utils;

来安装这些我们需要的包。

下面就是爬虫里我们要引用的包
代码如下(示例):

var fs = require('fs');
var myRequest = require('request')//获取网页内容
var myCheerio = require('cheerio')//筛选网页信息
var myIconv = require('iconv-lite')
require('date-utils');

Node调用mysql

npm install mysql;

创建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;



定义要访问的网站

var source_name = "新华网";
var myEncoding = "utf-8";
var seedURL = 'http://www.xinhuanet.com/';

定义新闻页面里具体的元素的读取方式

定义哪些url可以作为新闻页面

var seedURL_format = "$('a')";
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var author_format = "$('.editor').text()";
var content_format = "$('.left_zw').text()";
var date_format = "$('.info').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}\//;
var regExp = /((\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2})|(\d{4}年\d{1,2}月\d{1,2}日)/

url_reg后面的正则表达式可以根据新闻网页链接的特征表示。
我这里通过链接上有年月日的时间这个特征来表示的。

构造一个模仿浏览器的request

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

读取种子页面

解析出种子页面里所有的链接

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

遍历种子页面里所有的链接

规整化所有链接,如果符合新闻URL的正则表达式就爬取

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

读取具体的新闻页面,构造一个空的fetch对象用于存储数据

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

读取新闻页面中的元素并保存到fetch对象里

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 = ""
        else fetch.date = eval(date_format); 
        
        try {
            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", ""); //摘要    
        } catch (e) { return; }
        // 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 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);
            }
        }); //mysql写入
        
    });
}

我这里try{}catch(e){return;}就是如果抓取不到的就跳过了;

到这里爬虫已经差不多写好了

六、构建网站访问数据库中爬取到的内容

首先创建一个7.03.html作为网页端(前端)

代码如下(示例):

<!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.js作为后端

代码如下(示例):

var express = require('express');
var mysql = require('./mysql.js')
var app = express();
var cors = require('cors');
app.use(cors());
//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 + "%'";
    console.log(fetchSql);
    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")

})

Node运行7.03.js后访问http://127.0.0.1:8080/7.03.html

爬虫 运行javascript js做爬虫_mysql_06


点击submit后得到

爬虫 运行javascript js做爬虫_mysql_07

用express脚手架来创建一个网站框架

在命令行中之前文件的目录下

express –e search_site

生成出一个search_site的文件夹。
由于我们需要使用mysql,因此将mysql.js拷贝进这个文件夹。
mysql.js拷贝后还需要在search_site文件夹内cmd运行。
依次进行:

npm install mysql –save
npm install

用vscode打开文件search_site/routes/index.js

添加

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

在search_site/public/下创建一个search.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('http://127.0.0.1:8080/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><td>author</td><td>publish_date</td></tr>');
                    data = JSON.parse(data);
                    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>

在search_site文件夹下cmd运行(node bin/www),然后用浏览器打开网址http://127.0.0.1:8080/search.html

爬虫 运行javascript js做爬虫_nodejs_08


差不多就成品就是这样了,还有些欠缺的地方,之后还要补充改进。