前言
写给广大网友: 如果不是为了作业我甚至不会写这篇博客, 这篇博客没有任何技术参考价值, 反而可能给各位造成误导, 请各位慎重参考莫要完全置信!
写给助教: 大多数内容是我学习笔记直接Paste上来的...未必代表我的学习顺序.
任务: 爬取网页
小任务: 学会使用request
request的第一个参数包括请求头, url, 编码方式等信息, 代表请求; 第二个参数代表回调函数, 其中body部分相当于网页中的document对象
//重新包装后柯里化的request函数
let my_req = (callback) => (url) => {
let options = {
url: url,
encoding: null,
headers: headers,
timeout: 1000
}
try {
request(options, (error, response, body) => {//request的第一个参数包括请求头, url, 编码方式等信息, 代表请求; 第二个参数代表回调函数, 其中body部分相当于网页中的document对象
try {
if (!error && response.statusCode === 200)
callback(body);
else {
console.error("访问 " + url + " 超时, 原因可能是网络不佳或爬虫被屏蔽 " + error + " " + response);
fails++, console.log("有" + fails + "次请求中的失败");
}
} catch (e) { console.log("回调" + url + " 时出现错误 " + e) }
})
} catch (e) { console.error("访问不成功, 可能是非法的url格式, 或回调函数中出现了错误" + e); }
}
小任务: 用正则表达式匹配新闻网页的url
分支小任务: 初步掌握Js类和对象
Js是一种动态语言, 因此它的对象并不是通过声明来固定某种类型, 而是通过调用构造函数生成对象并将它赋值给某个变量.
对象的成员变量(或叫属性)和函数和
分支小任务: 获得一个正则表达式
只需要调用正则表达式的构造函数或者通过简便写法'/a/'就能
var regexp = new RegExp("a");
var regexp_ = /a/;
正则表达式能够作为字符对象match函数的参数(模式串)使用, match函数的返回值是一个子串, 使得其存在从第0个字符开始的子串能够与模式串匹配.
分支小任务: 正则表达式的匹配逻辑
由于在Leetcode上写过正则表达式那个题, 所以我对两种特殊的模式串'.'和'*'还是比较熟悉的.
bool isMatch(string &s, string &p, int _s = 0, int _p = 0) {
if (_p + 1 < p.size() && p[_p + 1] == '*') {
if (isMatch(s, p, _s, _p + 2)) return true;
for (int i = 1; _s + i - 1 < s.size(); i++) {
if (p[_p] != '.' && p[_p] != s[_s + i - 1]) break;
if (isMatch(s, p, _s + i, _p + 2)) return true;
}
return false;
}
if (_s >= s.size() && _p >= p.size()) return true;
if (_s >= s.size() || _p >= p.size()) return false;
return (p[_p] == '.' || p[_p] == s[_s]) && (isMatch(s, p, _s + 1, _p + 1));
}
能匹配一类字符的特殊字符: '\d'匹配所有数字和'\w'匹配所有字母; '[abc]'匹配方括号内的任意一个字符.
决定匹配次数的特殊字符: '*'匹配它前面的字符任意次(包括0次); '?'匹配它前面的字符0次或1次; '+'匹配它前面的字符1次或更多次.
决定匹配开始和结束的特殊字符: '^'待匹配串的开头是模式串; '$'它之前的待匹配串的结尾是模式串.
分支小任务: 解析新闻网页的url并写出正确的正则表达式
一个网页是新浪主页上的新闻网页, 当网页的url不是.html就是.shtml或者.phtml结尾, 且用https:/作为开头.
var news_reg = /https?:\/\/[\S]*.s?p?html$/;
分支小任务: 通过种子页面将url传给负责处理新闻页面的函数
其中check函数是通过上面的正则表达式实现的, 过于trivial于是不贴chek函数的代码.
const open_all_ahref_with = (open_) => my_req((body) => {
try {
var html = myIconv.decode(body, my_encoding);
var $$ = myCheerio.load(html, { decodeEntities: true });
} catch (e) { console.log("解析含a链接的网页失败" + e) }
try { var a_tags, a_tags = $$('a') }
catch (e) { console.log("解析网页中的a链接失败" + e) }
if (a_tags == undefined)
return console.log("没有发现a链接");
a_tags.each((i, elem) => {
let href = $$(elem).attr("href");
href = formated_as_whole_url(href);
console.log(href);
if (check(href)) open_(href);
})
})
小任务: 通过url访问新闻页面
分支小任务: html标签与jQuery选择器
最基础的用法
匹配某个类型的标签
$('a')
分支小任务: jQuery对象方法
遍历使用forEach, 其参数是回调函数, 函数的参数是(i, elem)的二元组, 函数体内放上与正常for循环该有的代码, 当需要循环中断时, 返回false, 需要继续执行, 返回true. 语法与for非常不同, 但功能上提供了break和continue类似的东西.
获取标签的属性和内容
$('a').text()//第一个a链接中装着的内容
$('a').attr('href')//第一个a链接中的href属性的值
分支小任务: 解析新闻页面并写出处理它的函数
处理新闻的过程非常傻, 基本上完全依赖于网页上实际有啥.
const store_news_with = (store_) => (url) => my_req((body) => {
try {
var html = myIconv.decode(body, my_encoding);
var $$ = myCheerio.load(html, { decodeEntities: true });
} catch (e) { console.log("解析新闻页面失败" + e) }
try {
var fetch = {
title: "",
content: "",
url: url,
keywords: "",
publish_time: "",
crawl_time: new Date(),
source_name: seed,
source_encoding: my_encoding,
}
} catch (e) { console.log("定义当前网页的抓取(fetch)对象失败" + e) }
try {
var possible_title = $$('title');
possible_title.each((i, elem) => {
fetch.title = fetch.title + $$(elem).text() + "\n";
})
} catch (e) {
console.log("没能从title标签中捕获标题" + e);
try {
var possible_title = $$('h1');
possible_title.each((i, elem) => {
let text = $$(elem).text();
if (/�/.test(text))
return false;
fetch.title = fetch.title + text + "\n";
})
} catch (e) { console.log("没能从h1标签中捕获标题" + e); return; }
}
try {
var possible = $$('p');
possible.each((i, elem) => {
let text = $$(elem).text();
if (/�/.test(text))
return false;
if (text.length > 20 && !/\|/.test(text) && !/ /.test(text))
fetch.content = fetch.content + text + "\n";
})
} catch (e) { console.log("没能从p标签中捕获内容" + e); return; }
try {
var possible = $$('meta[name="keywords"]').first();
fetch.keywords = possible.attr("content");
} catch (e) { console.log("没能捕获关键词" + e); return; }
try {
var possible = $$('meta[property*="time"]').first();
fetch.publish_time = possible.attr("content");
} catch (e) { console.log("没能从meta标签捕获发行时间" + e) }
if (fetch.publish_time === "")
try {
var possible = $$('span[class*="time"]').first();
fetch.publish_time = possible.text();
} catch (e) { console.log("没能从span标签捕获发行时间" + e); return; }
var no_undefined = true;
if (fetch == undefined) {
console.assert("fetch对象为空");
return;
}
try {
for (let i in fetch)
if (fetch[i] == "" || fetch[i] == undefined)
return console.log("fetch对象中的" + i + "为空")
} catch (e) { console.log("fetch对象不能被迭代" + e), no_undefined = false }
if (no_undefined)
store_(fetch);
})(url)
小任务: 将新闻存储到mysql
分支小任务: mysql建表
CREATE TABLE `fetches` (
`id_fetches` int(11) NOT NULL AUTO_INCREMENT,
`url` varchar(200) DEFAULT NULL,
`source_name` varchar(200) DEFAULT NULL,
`source_encoding` varchar(45) DEFAULT NULL,
`title` varchar(200) DEFAULT NULL,
`keywords` varchar(200) DEFAULT NULL,
`publish_time` date DEFAULT NULL,
`crawl_time` 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;
分支小任务: 学习异步函数与Promise对象
异步函数: 在定义函数时使用的function关键字前或者在定义箭头函数的参数列表前加上async关键字就能将这个函数声明为异步函数.
异步函数中, 各条命令没有执行顺序的限制, 所以它会比非异步的情况更快完成(尤其是在网络请求和渲染异步进行的业务场景中非常必要).
当某条命令依赖于某个变量的值时, 这个变量很可能没有变成程序员所需要的那样. 此时Promise对象提供了一种解决方案.
Promise对象的构造函数的参数是一个函数, 这个函数的入参是两个函数resolve和reject, 函数体内包含了一系列的指令, 这些指令最终产生的结果将作为resolve的入参.
对Promise对象使用await关键字时, 它将返回resolve的入参. 这使得异步编程变得更自然.
就我的观点而言Promise是某种扭曲的产物, 是Js社区中同时存在函数式编程和对象式编程两种范式的不良后果. 事实上, 纯函数式编程中并不需要Promise, 而纯对象式编程不会这样来定义Promise的构造函数.
分支小任务: 封装mysql有关的查询方法
首先封装连接池(通过node_module中的mysql组件)
const mysql = require('mysql');
var pool_ = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'crawl'
});
module.exports = { pool_ };
然后封装请求指令: 指令中需要先从池中抽取连接, 再通过连接来进行请求, 故写作异步函数时需要用Promise保证其先执行. (实际上此处完全没有必要, 因为它与非异步的效果几乎相同)
const { pool_ } = require('./pool_');
const findUser = () => {
return new Promise((resolve, reject) => {
try {
pool_.getConnection((err, conn) => {
if (err) { console.warn("Cannot connect MySql " + err); }
else { resolve(conn); return; }
})
} catch (e) { reject(undefined); }
})
}
const sqlquery_ = async (sql, param, callback) => {
let connection = await findUser();
return new Promise(() => connection.query(sql, param, callback)).then(connection.release());
}
const sqlquery_noparam = async (sql, callback) => {
let connection = await findUser();
return new Promise(() => connection.query(sql, callback)).then(connection.release());
}
exports.query_ = sqlquery_;
exports.query_noparam = sqlquery_noparam;
分支小任务: 通过自己写的mysql函数来向mysql写入新闻
const store_to_mysql_if_no_repetition = (fetch) => {
const fetchAddSql = 'INSERT INTO fetches(url,source_name,source_encoding,title,' +
'keywords,publish_time,crawl_time,content) VALUES(?,?,?,?,?,?,?,?)';
const fetchAddSql_Params = [fetch.url, fetch.source_name, fetch.source_encoding,
fetch.title, fetch.keywords, fetch.publish_time,
fetch.crawl_time.toFormat("YYYY-MM-DD HH24:MI:SS"), fetch.content
];
sql.query_(fetchAddSql, fetchAddSql_Params, (err, vals, fields) => {
if (err) {
console.log("存入MySql时: " + err);
} else {
console.log("存储成功:" + fetch.url);
}
})
}
任务 实现搜索并用表格展示
小任务: 实现搜索栏
分支小任务: 网页里放点啥标签
首先需要一个表单来向服务器提交数据, 因而有一个<form>标签; 然后搜索数据需要关键字类型(关键词, 标题, 日期, 内容等)就在里面放个<select>标签再在里面套个<option>标签;
搜索按钮用<input type = 'button'>, 搜索框用<input type = 'text'>.
需要实现搜索下拉栏, 它是个无序列表, 所以在下面放个<div>再套个<ul>, 拿到数据之后就放到里面去.
<div class="wrapper">
<img src="Images/DASE.png" class="logo">
<form class="SearchBox">
<div class="MainBox" style="display: block;">
<select class="SearchOptions">
<option value="title">Title</option>
<option value="keywords">KeyWords</option>
<option value="publish_time">Publish Time</option>
<option value="source_name">Source</option>
<option value="crawl_time">Crawl Time</option>
</select>
<input type="text" class="SearchTextBox">
<input type="button" value="Search" class="SearchButton">
</div>
<div class="Sublist" style="display: inline-block;">
<ul class="Suggestion">
</ul>
</div>
</form>
</div>
分支小任务: css样式设置
令人欣慰的是: css的选择器和jQuery的选择器语法居然大差不差, 省掉了我很多力气.
伪类选择器的关键字多了个:hover用来判断鼠标悬停事件, 由此就能做出下拉框的动态效果了.
<style>
.wrapper {
text-align: center;
margin-top: 150px;
z-index: 0;
}
.logo {
display: inline-block;
margin-left: 10px;
margin-block-end: 30px;
width: 375px;
height: auto;
}
.MainBox {
text-align: center;
display: block;
}
.SearchBox {
display: block;
}
.SearchOptions {
display: inline-flex-box;
vertical-align: top;
position: relative;
left: 6px;
width: 115px;
height: 16px;
z-index: 10;
padding: 12px 16px;
font-size: 16px;
margin: 0 0 0 0;
outline: 0;
box-shadow: none;
border-radius: 10px 0 0 10px;
border: 2px solid #3e0079;
background: #fff;
color: #222;
box-sizing: content-box;
text-align: center;
}
.SearchTextBox {
display: inline-flex;
vertical-align: top;
width: 512px;
height: 16px;
padding: 12px 16px;
margin: 0, 0, 0, 0;
font-size: 16px;
box-shadow: none;
border-radius: 0 0 0 0;
border: 2px solid #3e0079;
background: #fff;
color: #222;
box-sizing: content-box;
}
.SearchButton {
display: inline-flexbox;
z-index: 100;
vertical-align: top;
position: relative;
left: -6px;
cursor: pointer;
width: 108px;
height: 44px;
line-height: 45px;
line-height: 44px\9;
padding: 0;
margin: 0, 0, 0, 0;
background: 0 0;
background-color: #3e0079;
border-radius: 0 10px 10px 0;
font-size: 17px;
color: #fff;
box-shadow: none;
font-weight: 400;
border: none;
outline: 0;
}
.Suggestion {
display: inline-block;
position: relative;
left: -17px;
top: -17px;
list-style: none;
width: 512px;
height: auto;
margin: 0, 0, 0, 0;
}
.SuggestionWord {
display: block;
width: 512px;
height: auto;
padding: 12px 16px;
margin: 0, 0, 0, 0;
font-size: 16px;
border: 2px solid #3e0079;
border-top: none;
background: rgb(253, 250, 255);
color: #222;
}
div.SuggestionWord:hover {
background-color: rgb(243, 229, 243);
color: #3e0079;
}
.SuggestionWord:first-child {
border-top: 2px solid #3e0079;
}
.SuggestionWord:last-child {
border-radius: 0 0 10px 10px;
}
a {
text-decoration: none;
z-index: 100;
}
</style>
由于某种未知的原因