【Mac】从0开始用node.js制作爬取结果的查询网站
实验要求
新闻爬虫及爬取结果的查询网站
核心需求:
1、选取3-5个代表性的新闻网站(比如新浪新闻、网易新闻等,或者某个垂直领域权威性的网站比如经济领域的雪球财经、东方财富等,或者体育领域的腾讯体育、虎扑体育等等)建立爬虫,针对不同网站的新闻页面进行分析,爬取出编码、标题、作者、时间、关键词、摘要、内容、来源等结构化信息,存储在数据库中。
2、建立网站提供对爬取内容的分项全文搜索,给出所查关键词的时间热度分析。
技术要求:
1、必须采用Node.JS实现网络爬虫
2、必须采用Node.JS实现查询网站后端,HTML+JS实现前端(尽量不要使用任何前后端框架)
刚接触半年编程的小白在第一节课看到实验要求时是心态崩溃的,还好经过一段时间的学习与不断摸索,有了一些浅显的理解,希望可以帮助到大家。
PS:发现用Mac完成一些操作与Windows不同,有一些天坑。希望此篇可以帮助大家避坑。
实验结果展示
1.中国新闻网试运行
2.自己爬虫——新浪新闻网
3.爬取东方财富网
4.爬取中国证券网(这里出现了一些问题待解决)(MYSQL出现问题待解决,现只能暂时存在本地)
简单了解实验项目所需知识
开始爬虫之前(心理建设无数次之后 )
通过仔细、认真、完整地研究了一遍老师的PPT,了解到从0入手node.js爬虫大概需要以下知识。
1 爬虫原理
1 、首先我们选定网站查看源代码,获取我们想要的子网页的URL
- 通过request请求,cheerio解析,each遍历
(cheerio模块囊括了对html页面的解析,分块,提取等多个功能,是实现爬虫功能最主要的工具。)
(为了实现爬虫功能,通常要在代码头部用require函数引入request、cheerio、iconv-lite和fs四个模块,其中fs是node.js内置的,其他三个需要安装)
2 、搜索出我们子网页页面中我们需要的信息:标题、正文关键字等
- 搜索后,写入代码,爬虫功能通过request请求,cheerio解析实现
3 、将这些我们需要的信息保存下来,通过各种形式访问到这种信息
- 建立fetch(文件对象),输入文件信息,fs /mysql模块写入(即本地保存或数据库保存)
2 JavaScript语法
主要介绍一些基本语法(由于js要用JavaScript编写,需要理解一些基本语法,其他更多的语法就不多谢啦,要看专门的介绍👀)
- JavaScript 使用关键字 var 来定义变量, 使用等号来为变量赋值
var x, length;
x = 5;
length = 6;
- JavaScript 数据类型
JavaScript 有多种数据类型:数字,字符串,数组,对象等等:
var length = 16; // Number 通过数字字面量赋值
var points = x * 10; // Number 通过表达式字面量赋值
var lastName = "Johnson"; // String 通过字符串字面量赋值
var cars = ["Saab", "Volvo", "BMW"]; // Array 通过数组字面量赋值
var person = {
firstName:"John", lastName:"Doe"}; // Object 通过对象字面量赋值
- JavaScript 函数
JavaScript 语句可以写在函数内,函数可以重复引用:
引用一个函数 = 调用函数(执行函数内的语句)。
function myFunction(a, b) {
return a * b; // 返回 a 乘以 b 的结果
}
3 网络元素的提取
- 观察老师给的源码的这个部分
- 下面以我爬取的新浪新闻网做解释
var source_name = "新浪新闻网";
var myEncoding = "utf-8";
var seedURL = 'https://news.sina.com.cn/china/';
//定义新闻页面内具体元素的读取方式
var seedURL_format = "$('a')";
/*<meta name="keywords" content="新冠肺炎,新型冠状病毒肺炎疫情" />*/
var keywords_format = "$('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
/*<meta property="article:published_time" content="2021-04-25T09:41:41+08:00" /> */
var date_format = "$('meta[property=\"article:published_time\"]').eq(0).attr(\"content\")"; //从指定标签的第0个位置,即首位,开始取出content内容,上同
/*<p class="show_author">责任编辑:张迪 </p> */
var author_format = "$('.show_author').text()"; //class用'.'号标识符取出
/*<div class="article-content clearfix" id='article_content'> */
var content_format = "$('.article').text()";
/*<meta name="description" content="原标25最简……施计划免疫工作至今," /> */
var desc_format = " $('meta[name=\"description\"]').eq(0).attr(\"content\")";
/*class="source" data-sudaclick="content_media_p" rel="nofollow">北京日报 */
var source_format = "$('.source').text()";
var url_reg = /\/((\d{4})-(\d{2})-(\d{2}))\/(doc-(\w{15})).shtml/; //正则表达式筛选不同的部分
var regExp = /((\d{
4})-(\d{
2})-(\d{
2}))/ //筛去无用信息
- 首先,更改网页名字与网页的URL;
- 第二,注释中为查看网页源码后找到所需爬取元素所在的位置
(Mac在查看源代码后按住Fn 出现F12键搜索想得到的元素) - 最后,可以直接定义变量提取网络元素(书写规则可找规律,如在class中、meta中等等)
4 正则表达式
下图为老师给出“中国新闻网”的正则表达式
(现在也觉得真的好难)
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}日)/
我先在网上查了菜鸟教程,发现以我的耐心和脑力看一半就心力交瘁😇,于是还是去B站找了视频教学,都贴上链接,希望有助于大家。
B站视频:正则表达式入门
菜鸟教程:正则表达式语法|菜鸟教程
写完之后可以先在网页上测试一遍是否匹配
测试页面:测试网址
当然,完整掌握正则表达式还需要更多学习,建议参考犀牛书中介绍
5 其他有关MySQL数据库
最广泛的开源数据库MySQL
可以实现创建表、插入表、删除表等功能
这部对我来说是最困难的,因为网上的。大部分教程针对Windows,而Mac的MySQL又一直在出现无法预料的问题,解决和未解决的问题都将在「问题」部分解释。
6 前端方面
HTML基础
内容包括:(HTML简介与历史版本、常用开发软件、常见标签与属性、
表格与表单、标签规范与标签语义化、实战:网页结构布局)
CSS基础
内容包括:(css简介与基本语法、常见的各种样式属性、CSS选择器与标签类型)
目标:HTML+CSS开发网页
7 后端方面
本项目主要使用 SQL 语言
- 数据库方面要精通mysql的sql语法;大数据场景主要掌握Hive SQL(Impala和Spark SQL等均在兼容Hive SQL)
- 核心知识点区分DDL和DML经典查询语句主要结构表连接(内连接,左外连接,右外连接,全连接)函数(普通函数,聚合函数)索引分区
完整爬取一个网站的过程
项目示例1
- 先在终端安装cheerio与request包以实现功能
npm install cheerio
在终端输入即可
npm install request
- 最简单的代码示例
(我将老师代码中学校网站改为了其他网站做了一下尝试)
在终端中运行
var myRequest = require('request')
var myCheerio = require('cheerio')
// var myURL = 'https://www.ecnu.edu.cn/e5/bc/c1950a255420/page.htm'
var myURL = 'https://www.nogizaka46.com/'
function request(url, callback) {
//request module fetching url
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());
//console.log("title: " + $('title').text());
//console.log("description: " + $('meta[name="description"]').eq(0).attr("content"));
})
分析:
1:引入Cheerio包,Request包;
2:把要爬取的URL定义在myURL这个变量;
3:创建request函数;
4:用console.log把这个html打印出来。
- 其他可用函数
var schedule = require('node-schedule') //设置定时爬虫
var myIconv = require(‘iconv-lite’) //编码转换 GB2312到UTF-8
var fs = require(‘fs’); //保存到本地文件
var mysql = require(‘mysql’); //保存到mysql数据库
可引入的包
npm install elasticsearch //可以用elasticsearch构建爬取数据的索引
- 运行结果界面
以上,一个最简单的爬虫就完成了!
项目二示例
- 如何爬取一个新闻网站种子页面中所包含的各新闻的信息。
(以我更改后爬取的东方财富网为例)
点开一个页面,查看源代码,找到想要爬取的元素,按照上述方法写入代码
代码如下
var source_name = "东方财富";
var myEncoding = "utf-8";
var seedURL = 'https://www.eastmoney.com/';
var seedURL_format = "$('a')";
// <meta name="keywords" content="机构论市,调仓,机会,共识,八大,反弹,牛市,居民,资产配置,沪指" />
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
// <div class="time">2021年04月25日 19:04</div>
var date_format = "$('.time').text()";
//<p class="res-edit">
var author_format = "$('.res-edit').text()";
//<div class="mainFrame">
var content_format = "$('.mainFrame').text()";
//<meta name="description" content="
var desc_format = " $('meta[name=\"description\"]').eq(0).attr(\"content\")";
//<div class="source data-source" data-source="东方财富研究中
var source_format = "$('.data-source').text()";
var url_reg = /\/(\d{4})(\d{2})(\d{2})(\d{10}).html/;
var regExp = /((\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2})|(\d{4}年\d{1,2}月\d{1,2}日)/
var fs = require('fs');
var myRequest = require('request')
var myCheerio = require('cheerio')
var myIconv = require('iconv-lite')
require('date-utils');
//防止网站屏蔽我们的爬虫
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)
}
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