gzl的博客

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

dbmovie记录

发表于 2019-10-02 分类于 node.js

流程

  1. 抓取数据
  2. 数据入库
  3. 启动服务
  4. 渲染数据

依赖

1
2
3
4
5
6
7
8
9
10
"dependencies": {
"bluebird": "^3.5.5", // 这个项目中将数据库的query方法promisify
"cheerio": "^1.0.0-rc.3", // 像jquery一样操作
"debug": "^4.1.1", // cmd可以打印相关信息,简洁明了
"ejs": "^2.7.1", // 前端渲染页面
"express": "^4.17.1", // 开启后端服务器
"mysql": "^2.17.1", // 使用mysql数据库
"request": "^2.88.0", // 进行html爬取
"request-promise": "^4.2.4" // request的小弟
}

爬取导入库

read.js进行读取操作

  • 通过request抓取了html代码
  • cheerio将html转成了dom
  • 将需要的内容存在数组(名称|评分|地址|图片|id)
  • 返回结果数组并导出read方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const rp = require('request-promise');
const cheerio = require('cheerio');
const debug = require('debug')('movie:read');
const read = async (url) => {
debug('开始读取最近上映的电影');

const opts = {
url,
transform: body => {
return cheerio.load(body);
}
};

return rp(opts).then($ => {
let result = []; // 结果数组

$('#screening li.ui-slide-item').each((index, item) => {
// ...
});
return result;
});
};

module.exports = read;

db.js 连接数据库,导出promisify query 方法

write.js 将数据写入数据库

index.js为入口函数

1
2
3
4
5
6
7
8
9
10
11
(async () => {
// 异步抓取目标页面
const movies = await read(url); // 这里要用await,因为read()函数是async式的,返回的是promise,如下
// const movies = read(url);
// console.log(movies); Promise { <pending> }

// 写入数据到数据库
await write(movies); // 这里也要添加await关键字,等到写完再关闭程序
// 完毕后退出程序
process.exit();
})();

前端展示

使用 ejs 模板,查询到数据后渲染到 index.html 页面。

1
2
3
4
5
6
7
8
9
// 首页路由
app.get('/', async (req, res) => {
// 通过SQL查询语句拿到库里的movies表数据
const movies = await query('SELECT * FROM movies');
// 渲染首页模板并把movies数据传过去
res.render('index', {
movies
});
});

DEBUG

https://www.npmjs.com/package/debug

1
set DEBUG=movie:* # debug模块需要先设置

项目启动

在 README.md 里

bug

注意这个项目中 async 和 await 的使用,个人觉得非常巧妙,还有下面的 promisify,值得好好学习。

mysql 的 query 方法 promisify 后进行 bind 绑定,以免 this 混乱。

https://stackoverflow.com/questions/44004418/node-js-async-await-using-with-mysql/51690276#51690276

参考

https://juejin.im/post/5ac9bc56f265da238c3af18f#heading-11

hello-koa记录

发表于 2019-10-01 更新于 2019-10-02 分类于 node.js

基础用法

架设 HTTP 服务

完整代码

1
2
3
4
5
// demos/01.js
const Koa = require('koa');
const app = new Koa();

app.listen(3000);

打开浏览器,访问 http://127.0.0.1:3000 。你会看到页面显示”Not Found”,表示没有发现任何内容。这是因为我们并没有告诉 Koa 应该显示什么内容。

Context 对象

完整代码

Koa 提供一个 Context 对象,表示一次对话的上下文(包括 HTTP 请求和 HTTP 回复)。通过加工这个对象,就可以控制返回给用户的内容。

Context.response.body属性就是发送给用户的内容。

1
2
3
4
5
6
7
8
9
10
// demos/02.js
const Koa = require('koa');
const app = new Koa();

const main = ctx => {
ctx.response.body = 'Hello World';
};

app.use(main);
app.listen(3000);

上面代码中,main函数用来设置ctx.response.body。然后,使用app.use方法加载main函数。

你可能已经猜到了,ctx.response代表 HTTP Response。同样地,ctx.request代表 HTTP Request。

访问 http://127.0.0.1:3000 ,现在就可以看到”Hello World”了。

HTTP Response 的类型

完整代码

Koa 默认的返回类型是text/plain,如果想返回其他类型的内容,可以先用ctx.request.accepts判断一下,客户端希望接受什么数据(根据 HTTP Request 的Accept字段),然后使用ctx.response.type指定返回类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// demos/03.js
const main = ctx => {
if (ctx.request.accepts('xml')) {
ctx.response.type = 'xml';
ctx.response.body = '<data>Hello World</data>';
} else if (ctx.request.accepts('json')) {
ctx.response.type = 'json';
ctx.response.body = { data: 'Hello World' };
} else if (ctx.request.accepts('html')) {
ctx.response.type = 'html';
ctx.response.body = '<p>Hello World</p>';
} else {
ctx.response.type = 'text';
ctx.response.body = 'Hello World';
}
};

访问 http://127.0.0.1:3000 ,现在看到的就是一个 XML 文档了。

网页模板

完整代码

实际开发中,返回给用户的网页往往都写成模板文件。我们可以让 Koa 先读取模板文件,然后将这个模板返回给用户。

1
2
3
4
5
6
7
// demos/04.js
const fs = require('fs');

const main = ctx => {
ctx.response.type = 'html';
ctx.response.body = fs.createReadStream('./demos/template.html');
};

访问 http://127.0.0.1:3000 ,看到的就是模板文件的内容了。

路由

原生路由

完整代码

网站一般都有多个页面。通过ctx.request.path可以获取用户请求的路径,由此实现简单的路由。

1
2
3
4
5
6
7
8
9
// demos/05.js
const main = ctx => {
if (ctx.request.path !== '/') {
ctx.response.type = 'html';
ctx.response.body = '<a href="/">Index Page</a>';
} else {
ctx.response.body = 'Hello World';
}
};

访问 http://127.0.0.1:3000/about ,可以看到一个链接,点击后就跳到首页。

koa-route 模块

完整代码

原生路由用起来不太方便,我们可以使用封装好的koa-route模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// demos/06.js
const route = require('koa-route');

const about = ctx => {
ctx.response.type = 'html';
ctx.response.body = '<a href="/">Index Page</a>';
};

const main = ctx => {
ctx.response.body = 'Hello World';
};

app.use(route.get('/', main));
app.use(route.get('/about', about));

上面代码中,根路径/的处理函数是main,/about路径的处理函数是about。

访问 http://127.0.0.1:3000/about ,效果与上一个例子完全相同。

静态资源

完整代码

如果网站提供静态资源(图片、字体、样式表、脚本……),为它们一个个写路由就很麻烦,也没必要。koa-static模块封装了这部分的请求。

1
2
3
4
5
6
// demos/12.js
const path = require('path');
const serve = require('koa-static');

const main = serve(path.join(__dirname));
app.use(main);

访问 http://127.0.0.1:3000/12.js,在浏览器里就可以看到这个脚本的内容。

重定向

完整代码

有些场合,服务器需要重定向(redirect)访问请求。比如,用户登陆以后,将他重定向到登陆前的页面。ctx.response.redirect()方法可以发出一个302跳转,将用户导向另一个路由。

1
2
3
4
5
6
7
// demos/13.js
const redirect = ctx => {
ctx.response.redirect('/');
ctx.response.body = '<a href="/">Index Page</a>';
};

app.use(route.get('/redirect', redirect));

访问 http://127.0.0.1:3000/redirect ,浏览器会将用户导向根路由。

中间件

Logger 功能

完整代码

Koa 的最大特色,也是最重要的一个设计,就是中间件(middleware)。为了理解中间件,我们先看一下 Logger (打印日志)功能的实现。

1
2
3
4
5
// demos/07.js
const main = ctx => {
console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
ctx.response.body = 'Hello World';
};

访问 http://127.0.0.1:3000 ,命令行就会输出日志。

1
1569916749773 GET /

中间件的概念

完整代码

上一个例子里面的 Logger 功能,可以拆分成一个独立函数

1
2
3
4
5
6
// demos/08.js
const logger = (ctx, next) => {
console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
next();
}
app.use(logger);

像上面代码中的logger函数就叫做”中间件”(middleware),因为它处在 HTTP Request 和 HTTP Response 中间,用来实现某种中间功能。app.use()用来加载中间件。

基本上,Koa 所有的功能都是通过中间件实现的,前面例子里面的main也是中间件。每个中间件默认接受两个参数,第一个参数是 Context 对象,第二个参数是next函数。只要调用next函数,就可以把执行权转交给下一个中间件。

访问 http://127.0.0.1:3000 ,命令行窗口会显示与上一个例子相同的日志输出。

中间件栈

完整代码

请看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// demos/09.js
const one = (ctx, next) => {
console.log('>> one');
next();
console.log('<< one');
}

const two = (ctx, next) => {
console.log('>> two');
next();
console.log('<< two');
}

const three = (ctx, next) => {
console.log('>> three');
next();
console.log('<< three');
}

app.use(one);
app.use(two);
app.use(three);

访问 http://127.0.0.1:3000 ,命令行窗口会有如下输出。

1
2
3
4
5
6
>> one
>> two
>> three
<< three
<< two
<< one

如果中间件内部没有调用next函数,那么执行权就不会传递下去。作为练习,你可以将two函数里面next()这一行注释掉再执行,看看会有什么结果。

异步中间件

完整代码

迄今为止,所有例子的中间件都是同步的,不包含异步操作。如果有异步操作(比如读取数据库),中间件就必须写成 async 函数。

1
2
3
4
5
6
7
8
9
10
11
12
// demos/10.js
const fs = require('fs.promised');
const Koa = require('koa');
const app = new Koa();

const main = async function (ctx, next) {
ctx.response.type = 'html';
ctx.response.body = await fs.readFile('./demos/template.html', 'utf8');
};

app.use(main);
app.listen(3000);

上面代码中,fs.readFile是一个异步操作,必须写成await fs.readFile(),然后中间件必须写成 async 函数。

1
node demos/10.js

访问 http://127.0.0.1:3000 ,就可以看到模板文件的内容。

中间件的合成

完整代码

koa-compose模块可以将多个中间件合成为一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// demos/11.js
const compose = require('koa-compose');

const logger = (ctx, next) => {
console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
next();
}

const main = ctx => {
ctx.response.body = 'Hello World';
};

const middlewares = compose([logger, main]);
app.use(middlewares);

访问 http://127.0.0.1:3000 ,就可以在命令行窗口看到日志信息。

错误处理

500错误

完整代码

如果代码运行过程中发生错误,我们需要把错误信息返回给用户。HTTP 协定约定这时要返回500状态码。Koa 提供了ctx.throw()方法,用来抛出错误,ctx.throw(500)就是抛出500错误。

1
2
3
4
// demos/14.js
const main = ctx => {
ctx.throw(500);
};

访问 http://127.0.0.1:3000,你会看到一个500错误页"Internal Server Error”。

404错误

完整代码

如果将ctx.response.status设置成404,就相当于ctx.throw(404),返回404错误。

1
2
3
4
5
// demos/15.js
const main = ctx => {
ctx.response.status = 404;
ctx.response.body = 'Page Not Found';
};

访问 http://127.0.0.1:3000 ,你就看到一个404页面”Page Not Found”。

处理错误的中间件

完整代码

为了方便处理错误,最好使用try...catch将其捕获。但是,为每个中间件都写try...catch太麻烦,我们可以让最外层的中间件,负责所有中间件的错误处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// demos/16.js
const handler = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.response.status = err.statusCode || err.status || 500;
ctx.response.body = {
message: err.message
};
}
};

const main = ctx => {
ctx.throw(500);
};

app.use(handler);
app.use(main);

访问 http://127.0.0.1:3000 ,你会看到一个500页,里面有报错提示 {"message":"Internal Server Error"}。

error 事件的监听

完整代码

运行过程中一旦出错,Koa 会触发一个error事件。监听这个事件,也可以处理错误。

1
2
3
4
5
6
7
8
// demos/17.js
const main = ctx => {
ctx.throw(500);
};

app.on('error', (err, ctx) =>
console.error('server error', err);
);

访问 http://127.0.0.1:3000 ,你会在命令行窗口看到”server error xxx”。

释放 error 事件

完整代码

需要注意的是,如果错误被try...catch捕获,就不会触发error事件。这时,必须调用ctx.app.emit(),手动释放error事件,才能让监听函数生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// demos/18.js`
const handler = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.response.status = err.statusCode || err.status || 500;
ctx.response.type = 'html';
ctx.response.body = '<p>Something wrong, please contact administrator.</p>';
ctx.app.emit('error', err, ctx);
}
};

const main = ctx => {
ctx.throw(500);
};

app.on('error', function(err) {
console.log('logging error ', err.message);
console.log(err);
});

上面代码中,main函数抛出错误,被handler函数捕获。catch代码块里面使用ctx.app.emit()手动释放error事件,才能让监听函数监听到。

访问 http://127.0.0.1:3000 ,你会在命令行窗口看到logging error。

Web App 的功能

Cookies

完整代码

ctx.cookies用来读写 Cookie。

1
2
3
4
5
6
// demos/19.js
const main = function(ctx) {
const n = Number(ctx.cookies.get('view') || 0) + 1;
ctx.cookies.set('view', n);
ctx.response.body = n + ' views';
}

访问 http://127.0.0.1:3000 ,你会看到1 views。刷新一次页面,就变成了2 views。再刷新,每次都会计数增加1。

表单

完整代码

Web 应用离不开处理表单。本质上,表单就是 POST 方法发送到服务器的键值对。koa-body模块可以用来从 POST 请求的数据体里面提取键值对。

1
2
3
4
5
6
7
8
9
10
// demos/20.js
const koaBody = require('koa-body');

const main = async function(ctx) {
const body = ctx.request.body;
if (!body.name) ctx.throw(400, '.name required');
ctx.body = { name: body.name };
};

app.use(koaBody());

打开另一个命令行窗口,运行下面的命令。

1
2
3
4
5
$ curl -X POST --data "name=Jack" 127.0.0.1:3000
{"name":"Jack"}

$ curl -X POST --data "name" 127.0.0.1:3000
name required

上面代码使用 POST 方法向服务器发送一个键值对,会被正确解析。如果发送的数据不正确,就会收到错误提示。

文件上传

完整代码

koa-body模块还可以用来处理文件上传。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// demos/21.js
const os = require('os');
const path = require('path');
const koaBody = require('koa-body');

const main = async function(ctx) {
const tmpdir = os.tmpdir();
const filePaths = [];
const files = ctx.request.body.files || {};

for (let key in files) {
const file = files[key];
const filePath = path.join(tmpdir, file.name);
const reader = fs.createReadStream(file.path);
const writer = fs.createWriteStream(filePath);
reader.pipe(writer);
filePaths.push(filePath);
}

ctx.body = filePaths;
};

app.use(koaBody({ multipart: true }));

打开另一个命令行窗口,运行下面的命令,上传一个文件。注意,/path/to/file要更换为真实的文件路径。

1
2
$ curl --form upload=@/path/to/file http://127.0.0.1:3000
["/tmp/file"]

curl

发表于 2019-10-01

查看网页源码

直接在curl命令后加上网址,就可以看到网页源码。我们以网址www.sina.com为例(选择该网址,主要因为它的网页代码较短):

1
$ curl www.sina.com

如果要把这个网页保存下来,可以使用-o参数,这就相当于使用wget命令了。这样在当前目录下就会有一个sina.txt的文件存放网页源码。

1
2
3
4
$ curl -o sina.txt www.sina.com
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 178 100 178 0 0 2825 0 --:--:-- --:--:-- --:--:-- 2825

自动跳转

有的网址是自动跳转的。使用-L参数,curl就会跳转到新的网址。

1
$ curl -L www.sina.com

键入上面的命令,结果就自动跳转为www.sina.com.cn。

显示头信息

-i参数可以显示http response的头信息,连同网页代码一起。-I参数则是只显示http response的头信息。

1
$ curl -i www.sina.com

显示通信过程

-v参数可以显示一次http通信的整个过程,包括端口连接和http request头信息。

1
$ curl -v www.sina.com

如果你觉得上面的信息还不够,那么下面的命令可以查看更详细的通信过程。

1
$ curl --trace output.txt www.sina.com

或者

1
$ curl --trace-ascii output.txt www.sina.com

运行后,请打开output.txt文件查看。

发送表单信息

发送表单信息有GET和POST两种方法。GET方法相对简单,只要把数据附在网址后面就行。

1
$ curl example.com/form.cgi?data=xxx

POST方法必须把数据和网址分开,curl就要用到–data参数。

1
$ curl -X POST --data "data=xxx" example.com/form.cgi

如果你的数据没有经过表单编码,还可以让curl为你编码,参数是--data-urlencode。

1
$ curl -X POST--data-urlencode "date=April 1" example.com/form.cgi

HTTP动词

curl默认的HTTP动词是GET,使用-X参数可以支持其他动词。

1
$ curl -X POST www.example.com
1
$ curl -X DELETE www.example.com

参考

http://www.ruanyifeng.com/blog/2011/09/curl.html

http://www.ruanyifeng.com/blog/2019/09/curl-reference.html

1…101112…32

gzl

96 日志
14 分类
37 标签
© 2020 gzl
由 Hexo 强力驱动 v3.7.1
|
主题 – NexT.Pisces v7.2.0