一、写在前面

先说下本文的背景,这是一道笔者遇到的Node后端面试题,遂记录下,通过本文的阅读,你将对楼下知识点有所了解:

  • midway项目的创建与使用
  • typescript在Node项目中的应用
  • 如何基于Node自身API封装请求
  • cheerio在项目中的应用
  • 正则表达式在项目中的应用
  • 单元测试

二、midway项目的创建和使用

第一步: 输入命令**npm init midway**初始化midway项目

第二步:选择**koa-v3 - A web application boilerplate with midway v3(koa)**,按下回车

➜  www npm init midway
npx: installed 1 in 4.755s
? Hello, traveller.
Which template do you like? … ⊙ v3
▸ koa-v3 - A web application boilerplate with midway v3(koa)
egg-v3 - A web application boilerplate with midway v3(egg)
faas-v3 - A serverless application boilerplate with midway v3(faas)
component-v3 - A midway component boilerplate for v3 ⊙ v2
web - A web application boilerplate with midway and Egg.js
koa - A web application boilerplate with midway and koa

第三步:输入你要创建的项目名称,例如**“midway-project”****, ****What name would you like to use for the new project? ‣ midway-project**

第四步:跟着提示走就好了,分别执行**cd midway-project****npm run dev**, 这个时候如果你没有特别设置的话,打开**http://localhost:7001**就可以看到效果了

➜  www npm init midway
npx: installed 1 in 4.755s
Hello, traveller.
Which template do you like? · koa-v3 - A web application boilerplate with midway v3(koa)
What name would you like to use for the new project? · midway-project
Successfully created project midway-project
Get started with the following commands: $ cd midway-project
$ npm run dev Thanks for using Midway Document Star: https://github.com/midwayjs/midway ╭────────────────────────────────────────────────────────────────╮
│ │
│ New major version of npm available! 6.14.15 → 8.12.1 │
│ Changelog: https://github.com/npm/cli/releases/tag/v8.12.1 │
│ Run npm install -g npm to update! │
│ │
╰────────────────────────────────────────────────────────────────╯ ➜ www

具体的官网已经写的很详细了,不再赘述,参见:

三、如何抓取百度首页的内容

3.1、基于node自身API封装请求

在node.js的https模块有相关的get请求方法可以获取页面元素,具体的如下请参见:,我把它封装了一下

import { get } from 'https';

async function getPage(url = 'https://www.baidu.com/'): Promise<string> {
let data = '';
return new Promise((resolve, reject) => {
get(url, res => {
res.on('data', chunk => {
data += chunk;
}); res.on('error', err => reject(err)); res.on('end', () => {
resolve(data);
});
});
});
}

额,你要测试这个方法,在node环境的话,其实也很简单的,这样写

(async () => {
const ret = await getPage();
console.log('ret:', ret);
})();

四、如何获取对应标签元素的属性

题目是,从获取的HTML源代码文本里,解析出id=lg的div标签里面的img标签,并返回此img标签上的src属性值

4.1、cheerio一把梭

如果你没赶上JQuery时代,那么其实你可以学下cheerio这个库,它有这个JQuery类似的API ------为服务器特别定制的,快速、灵活、实施的jQuery核心实现.具体的参见:,github地址是:

在了解了楼上的知识点以后呢,那其实就很简单了,调调API出结果。下文代码块的意思是,获取id为lg的div标签,获取它的子标签的img标签,然后调用了ES6中数组的高阶函数map,这是一个幂等函数,会返回与输入相同的数据结构的数据,最后调用get获取一下并字符串一下。

 @Get('/useCheerio')
async useCheerio(): Promise<IPackResp<IHomeData>> {
const ret = await getPage();
const $ = load(ret);
const imgSrc = $('div[id=lg]')
.children('img')
.map(function () {
return $(this).attr('src');
})
.get()
.join(','); return packResp({ func: 'useCheerio', imgSrc });
}

4.2、正则一把梭

看到一大坨字符串,嗯,正则也是应该要想到的答案。笔者正则不太好,这里写不出一步到位的正则,先写出匹配id为lg的div的正则,然后进一步匹配对应的img标签的src属性,是的,一步不行,那咱就走两步,最终结果和走一步是一样的。

 @Get('/useRegExp')
async useRegExp(): Promise<IPackResp<IHomeData>> {
const ret = await getPage();
// 匹配id为lg的div正则
const reDivLg = /(?<=<div.*?id="lg".*?>)(.*?)(?=<\/div>)/gi;
// 匹配img标签的src属性
const reSrc = /<img.*?src="(.*?)".*?\/?>/i;
const imgSrc = ret.match(reDivLg)[0].match(reSrc)[1]; return packResp({ func: 'useRegExp', imgSrc });
}

五、单元测试

这里要实现两个测试点是,1、如果接口请求时间超过1秒钟,则Assert断言失败, 2、如果接口返回值不等于"//www.baidu.com/img/bd_logo1.png",则Assert断言失败

midway集成了jest的单元测试, 官网已经写的很详细了,具体的参见:

关于1秒钟这事,我们可以计算下请求的时间戳,具体的如下:

const startTime = Date.now();
// make request
const result: any = await createHttpRequest(app).get('/useRegExp');
const cost = Date.now() - startTime;

最后再断言下就好了 expect(cost).toBeLessThanOrEqual(1000);

最终的代码如下:

  it.only('should GET /useRegExp', async () => {
const startTime = Date.now();
// make request
const result: any = await createHttpRequest(app).get('/useRegExp');
const cost = Date.now() - startTime; // 2. 如果接口请求时间超过1秒钟,则Assert断言失败
const {
data: { imgSrc },
} = result.body as IPackResp<IHomeData>; expect(imgSrc).not.toBe('//www.baidu.com/img/bd_logo1.png');
notDeepStrictEqual(imgSrc, '//www.baidu.com/img/bd_logo1.png');
expect(cost).toBeLessThanOrEqual(1000);
expect(imgSrc).toBe('//www.baidu.com/img/flexible/logo/pc/index.png');
deepStrictEqual(imgSrc, '//www.baidu.com/img/flexible/logo/pc/index.png');
}); it.only('should GET /useCheerio', async () => {
const startTime = Date.now();
// make request
const result: any = await createHttpRequest(app).get('/useCheerio');
const cost = Date.now() - startTime; const {
data: { imgSrc },
} = result.body as IPackResp<IHomeData>; expect(imgSrc).not.toBe('//www.baidu.com/img/bd_logo1.png');
notDeepStrictEqual(imgSrc, '//www.baidu.com/img/bd_logo1.png');
expect(cost).toBeLessThanOrEqual(1000);
expect(imgSrc).toBe('//www.baidu.com/img/flexible/logo/pc/index.png');
deepStrictEqual(imgSrc, '//www.baidu.com/img/flexible/logo/pc/index.png');
});

六、写在后面

这里,如果你眼睛够细,你会发现一个很有意思的现象,你从浏览器打开百度首页,然后控制台输出楼上的需求是这样的

const lg = document.getElementById('lg');
undefined
lg.childNodes.forEach((node) => { if(node.nodeName.toLowerCase() === 'img') { console.log(node.src) } })
2VM618:1 https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/logo_white-d0c9fe2af5.png
VM618:1 https://www.baidu.com/img/PCfb_5bf082d29588c07f842ccde3f97243ea.png
undefined

然而,通过Node自带的https库,你会发现//www.baidu.com/img/flexible/logo/pc/index.png这个

咦,震惊.jpg. 发生了什么?莫不是度度做了什么处理?

于是乎,我用wget测试了下wget -O baidu.html [https://www.baidu.com](https://www.baidu.com), 发现正常发请求是这样的

➜  tmp wget -O baidu.html https://www.baidu.com
--2022-06-10 00:36:17-- https://www.baidu.com/
Resolving www.baidu.com (www.baidu.com)... 182.61.200.6, 182.61.200.7
Connecting to www.baidu.com (www.baidu.com)|182.61.200.6|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2443 (2.4K) [text/html]
Saving to: ‘baidu.html’ baidu.html 100%[=====================================================================================================================================================>] 2.39K --.-KB/s in 0s 2022-06-10 00:36:18 (48.3 MB/s) - ‘baidu.html’ saved [2443/2443] ➜ tmp cat baidu.html
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');
</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
➜ tmp

但是当我给上模拟浏览器的请求后wget --user-agent="Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16" [https://www.baidu.com](https://www.baidu.com)

➜  tmp wget --user-agent="Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16"  https://www.baidu.com
--2022-06-10 00:38:53-- https://www.baidu.com/
Resolving www.baidu.com (www.baidu.com)... 182.61.200.7, 182.61.200.6
Connecting to www.baidu.com (www.baidu.com)|182.61.200.7|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: ‘index.html’ index.html [ <=> ] 350.76K --.-KB/s in 0.01s 2022-06-10 00:38:53 (35.1 MB/s) - ‘index.html’ saved [359175] ➜ tmp

这个是跟浏览器的行为一直的,输出的结果是三个img标签。

关于Node.js的https库对这块的处理我没有去深究了,我就是通过楼上的例子猜了下,应该是它那边服务器做了对客户端的相关判定,然后返回相应html文本,所以这里想办法给node.js设置一个楼上的user-agent我猜是可以得到跟PC一样的结果的,这个作业就交给读者了,欢迎在下方留言讨论!

项目地址: https://github.com/ataola/play-baidu-midway-crawler

线上访问: http://106.12.158.11:8090/

midway的使用教程的更多相关文章

  1. Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求

    上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...

  2. Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数

    上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...

  3. Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数

    上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...

  4. Angular2入门系列教程4-服务

    上一篇文章 Angular2入门系列教程-多个组件,主从关系 在编程中,我们通常会将数据提供单独分离出来,以免在编写程序的过程中反复复制粘贴数据请求的代码 Angular2中提供了依赖注入的概念,使得 ...

  5. Angular2入门系列教程1-使用Angular-cli搭建Angular2开发环境

    一直在学Angular2,百忙之中抽点时间来写个简单的教程. 2016年是前端飞速发展的一年,前端越来越形成了(web component)组件化的编程模式:以前Jquery通吃一切的田园时代一去不复 ...

  6. wepack+sass+vue 入门教程(三)

    十一.安装sass文件转换为css需要的相关依赖包 npm install --save-dev sass-loader style-loader css-loader loader的作用是辅助web ...

  7. wepack+sass+vue 入门教程(二)

    六.新建webpack配置文件 webpack.config.js 文件整体框架内容如下,后续会详细说明每个配置项的配置 webpack.config.js直接放在项目demo目录下 module.e ...

  8. wepack+sass+vue 入门教程(一)

    一.安装node.js node.js是基础,必须先安装.而且最新版的node.js,已经集成了npm. 下载地址 node安装,一路按默认即可. 二.全局安装webpack npm install ...

  9. Virtual Box配置CentOS7网络(图文教程)

    之前很多次安装CentOS7虚拟机,每次配置网络在网上找教程,今天总结一下,全图文配置,方便以后查看. Virtual Box可选的网络接入方式包括: NAT 网络地址转换模式(NAT,Network ...

随机推荐

  1. c++对c的拓展_常量引用

    常量引用:不能通过引用去修改引用所指向的内容 const int &ref =val; // const int  *const ref =&val; 注意:可引用常量 (普通引用无法 ...

  2. mybatis-day1入门案例

    首先应先创建maven工程 (jar包要导入,五个核心jar包) 如果测试运行时出现了不支持版本5,则要修改以下内容 类的路径如下 1.配置pom.xml依赖 <?xml version=&qu ...

  3. python向上取整以50为界

    import math def getNum(limit_num,num): if num%limit_num==0: print(num) else: num=math.ceil(num/limit ...

  4. zabbix proxy cannot perform check now for itemid [xxxxx]: item is not in cache

    情况 接上次做完容器部署proxy后,为其添加host进行添加任务. 发现一直没有数据,就到item里面执行 execute now. 然后过了几分钟回来一看,还是没有. Emmm,看下log吧. S ...

  5. Docker部署Nginx启动成功,浏览器拒绝访问

    今天下午部署完tomcat和mysql之后就接着部署Nginx,本以为Nginx也和之前两个一样简单,但是就因为标题这个问题,花费了我一个小时纠错. 过程复现: 解决完上一篇博客(https://ww ...

  6. OpenHarmony标准设备应用开发(三)——分布式数据管理

    (以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点) 邢碌 上一章,我们通过分布式音乐播放器.分布式炸弹.分布式购物车,带大家讲解了 OpenAtom OpenHarmon ...

  7. JavaScript学习基础2

    ##JavaScript基本对象 1 .function:函数(方法)对象 * 创建: 1.var fun =new Function(形式参数,方法体): 2.function 方法名(参数){ 方 ...

  8. background 属性

    一.background 属性集 值 说明 background-color 指定要使用的背景颜色 background-position 指定背景图像的位置 background-size 指定背景 ...

  9. python基础练习题(九九乘法表)

    又把python捡起来了,动手能力偏弱,决定每日一练,把基础打好! ------------------------------------------------------------------ ...

  10. .NET宝藏API之:OutputFormatter,格式化输出对象

    相信大家在项目中都用过统一响应参数模板. 先声明一个响应模板类: public class ResponseDto { public int code { get; set; } public str ...