利用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搞定