Fork me on GitHub

koa学习-建立数据库模型-4(下)

向数据库中导入爬去到的数据

在上面所有的数据的schema模型都已经建立完成之后,接下面我们需要将之前爬去到的数据导入我们的数据里面去。

在首先在启动文件里面调用一下爬虫脚本来抓取一波数据.

1
2
3
4
5
6
7
8
9
10
// somde codes...
(async () => {
await connect();
initSchemas();
// 把爬虫程序 require 进来,它就会自动去启动子进程去爬取数据
require('./tasks/movie');
})();
// some codes...

然后我们去爬虫的程序里面修改一下,将爬去下来的数据保存到 mongoDB 数据库里面去.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const cp = require('child_process');
const { resolve } = require('path');
const mongoose = require('mongoose');
const Movie = mongoose.model('Movie');
(async()=>{
const script = resolve(__dirname,'../crawler/trailer-list.js');
const child = cp.fork(script,[]);
let invoked = false;
child.on('error',err=>{
if(invoked){
return ;
} else {
invoked = true;
}
console.log(err);
})
child.on('exit',code => {
if(invoked){
return;
} else {
invoked = true;
}
let err = code === 0 ? null : new Error('exit code ' + code);
})
child.on('message',data => {
// result 为爬取下来的数据
let result = data.result;
console.log('result: ', result);
// 遍历爬取的数据,然后判断一下数据库里面有没有这组数据
result.forEach(async item => {
let movie = await Movie.findOne({
doubanId: item.doubanId
})
if(!movie){
movie = new Movie(item);
// 如果这条数据数据库里面没有,就把它存在数据
await movie.save();
}
})
})
})

执行完成之后,去数据库里面看一波,打开终端

1
2
3
4
5
6
7
8
> sudo service mongod start
> mongo
> show dbs
> use douban-test
> show tables
> db.movies.find({})
<!-- 如果要删除里面的数据,可以使用 -->
> db.movies.remove({})

看到里面有爬取的数据的话,就证明已经存取成功了(如果没有成功可以检查一下Schema设定的字段和爬取下面的数据的字段是否相同)。

数据存储好之后,我们可以利用爬取下来的数据的doubanId去和之前的豆瓣API去匹配以得到更多的信息。

那我们可以去 server/tasks目录下的 api.js 里面修改一波代码,之前我们这里使用的是写死的数据,现在我们使用从数据库里面拿到的数据来进行操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
const rp = require('request-promise-native');
const mongoose = require('mongoose');
const Movie = mongoose.model('Movie');
const Category = mongoose.model('Category');
async function fetchMovie(item){
const url = `http://api.douban.com/v2/movie/subject/${item.doubanId}?apikey=0df993c66c0c636e29ecbb5344252a4a`;
// 使用 rp 这个库去对url进行请求
const res = await rp(url);
// res 是 请求回来的数据,但是这就是个字符串,我们要对他进行一个 JSON.parse()的处理
let body ;
try {
body = JSON.pase(res);
} catch(err) {
console.log(err);
}
return body;
}
(async ()=> {
// 在爬取到的数据里面查询数据,满足一下几种条件的数据被拿出来
let movies = await Movie.find({
$or: [
// 满足随便下面的哪个条件
{ summary: { $exists: false } },
{ summary: null },
{ title: '' },
{ summary: '' }
]
})
for(let i = 0;i< [movies[0]].length;i++){
let movie = movies[i];
// 对数据库里面的数据去进行一个请求
let movieData = await fetchMovie(movie);
if(movieData){
let tags = movieData.tags || [];
movie.tags = tags;
movie.summary = movieData.summary || '';
movie.title = movieData.alt_title || movieData.title || '';
movie.rawTitle = movieData.title || '';
movie.movieTypes = movieData.genres || [];
for(let i = 0;i<movie.movieTypes.length;i++){
let item = movie.movieTypes[i];
// 在数据库里面查找一下有没有保存过这条数据
let cat = await Category.findOne({
name: item
})
if(!cat) {
// 如果没有没存储过,往里面添加一下
cat = new Category({
name: item,
movies: [movie._id]
})
} else {
// 如果里面存储过我们判断一下里面有没有保存过电影的id
if(cat.movies.indexOf(movie._id === -1)) {
cat.movies.push(cat._id);
}
}
// 将 cat 存进数据库
await cat.save();
if(!movie.category){
movie.category.push(cat._id);
} else {
if(movie.category.indexOf(cat._id) === -1) {
movie.category.push(cat._id);
}
}
}
let date = movieData.pubdates[0].split('(')[0] || '';
let country = movieData.countries || '未知';
let pubdates = [];
pubdates.push({
date: new Date(date),
country
})
movie.pubdate = pubdates;
for(let i = 0;i<movieData.tags.length;i++){
movie.tags.push(movieData.tags[i]);
}
// movieData.tags.forEach(function(tag){
// movie.tags.push(tag.name);
// })
await movie.save();
}
}
})();

这段主要是在数据库里面查找没有video值的数据,然后将这写数据发送给video.js,让video.js 根据查询到的doubanId去对数据进行爬取。

写完之后,再次回到index.js那边去启动一些这个脚本

1
2
3
4
5
6
7
8
9
10
(async () => {
await connect();
initSchemas();
// const Movie = mongoose.model('Movie');
// const movies = await Movie.find({});
// console.log('movies: ', movies);
// 把它 require 进来,他就会启动子进程去爬取数据,然后把爬取到的数据存在数据库里面
// require('./tasks/movie');
require('./tasks/api');
})();

然后运行启动脚本 nodemon server/index.js

一般情况下数据是能够存进去数据库里面的。

将海报图和视频资源存储到数据库

我们之前利用的是在tasks目录下面的 trailer.js 去启动一个子进程去对数据进行(预告片)爬取,然后之前里面的数据是写死的,我们这里可以利用数据库里面的数据去进行一个操作:

trailer.js里面引入数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 首先调用一个子进程,子进程和事件循环这个机制是有很大区别的.
const cp = require('child_process');
const { resolve } = require('path');
const mongoose = require('mongoose');
const Movie = mongoose.model('Movie');
const Category = mongoose.model('Category');
(async () => {
let movies = await Movie.find({
$or: [
// 满足 video 这个字段没有 或者 video 这个字段的值是空
{ video: { $exists: false } },
{ video: null },
]
})
const script = resolve(__dirname, '../crawler/video.js')
// 调用子进程上面的 fork 方法 ,它可以派生出一个子进程对象
const child = cp.fork(script, []);
// 设置一个invoked变量来标识爬虫脚本是否有被运行过
let invoked = false;
// 用一个回调函数来监听异常
child.on('error', err => {
if (invoked) {
return;
} else {
invoked = true;
}
console.log(err);
})
child.on('exit', code => {
if (invoked) {
return;
} else {
invoked = true;
}
let err = code === 0 ? null : new Error('exit code ' + code);
console.log('err: ', err);
})
child.on('message', async data => {
let doubanId = data.doubanId;
let movie = await Movie.findOne({
doubanId: doubanId
})
// 如果有预告片和封面图,就将这些值取下来存好,放进数据库。
if(data.video){
movie.video = data.video;
movie.cover = data.cover;
await movie.save();
} else {
// 如果我们抓取到的那个数据是没有video的话,我们直接将这条数据直接移走就行了,不需要没有预告片的数据
await movie.remove();
// 只是删了电影可能还不够,还需要从目录层里面删一下
let movieTypes = movie.movieTypes
for(let i = 0;i<movieTypes.length;i++){
let type = movieTypes[i];
// 拿到每一个type,然后查询一下
let cat = Category.findOne({
name: type
})
// 如果那一条电影被删了,那么分类里面对应的分类也要跟着删除
if(cat){
let idx = cat.movies.indexOf(movie._id)
if(idx > -1){
cat.movies = cat.movies.splice(idx,1);
}
await cat.save();
}
}
}
// console.log(data);
})
// 将数据库里面查询到的movies发送给video 去爬取
child.send(movies)
})();

这边响应一个’message’,那么子进程那边也要来一个.
直接修改一下 video.js 里面代码,让一个进程去process.on('message'),去里面获取数据即可,将之前写死的形式改一下。

子进程这边拿到了movies这个值,然后对数据去进行抓取,将抓取到的值封装成一个对象data 返回回去给trailer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 前缀是subject,后缀是豆瓣ID
const puppeteer = require('puppeteer');
const base = `https://movie.douban.com/subject/`;
// const doubanId = '1849031';
// const videoBase = `https://movie.douban.com/trailer/219491/#content`;
const sleep = time => new Promise(resolve => {
setTimeout(resolve, time);
});
process.on('message', async movies => {
console.log('开始访问详情界面');
// 相当于是个看不到的浏览器
const broswer = await puppeteer.launch({
args: ['--no-sandbox'],
dumpio: false
})
const page = await broswer.newPage();
// 在打开新页面去往目标页面之前,去遍历一波启动进程程序那边传过来的movies
for (let i = 0; i < movies.length ; i++) {
let doubanId = movies[i].doubanId;
// console.log(doubanId);
await page.goto(base + doubanId, {
waitUntil: 'networkidle2'
// 当网页空闲的时候,表示页面已经加载完成了
})
await sleep(1000);
// 来获取详情页面
const result = await page.evaluate(() => {
// 回调函数里面的代码都是在浏览器环境里面执行的,用 var 声明变量可能比较保险
var $ = window.$;
var it = $('.related-pic-video');
if (it && it.length > 0) {
var link = it.attr('href');
// 将括号里面的url提取出来
var cover = it.attr('style').match(/\((.+?)\)/g)[0].slice(1, -1);
return {
link,
cover
}
}
return {};
})
let video;
if (result.link || result.cover) {
await page.goto(result.link, {
waitUntil: 'networkidle2'
})
// 等待个2s
await sleep(1000);
video = await page.evaluate(() => {
var $ = window.$;
var it = $('source');
if (it && it.length >= 0) {
return it.attr('src');
}
return ''
})
}
const data = {
video,
doubanId,
cover: result.cover
}
process.send(
data
);
}
broswer.close();
process.exit(0);
})

然后在index.js里面运行一下trailer.js来将其启动起来。server/index.js 里面代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const Koa = require('koa');
// const ejs = require('ejs');
// const pug = require('pug');
// const { htmlTpl,ejsTpl,pugTpl } = require('./tpl');
const app = new Koa();
const views = require('koa-views');
const { resolve } = require('path');
const mongoose = require('mongoose');
const { connect,initSchemas } = require('./database/init');
(async () => {
await connect();
initSchemas();
// const Movie = mongoose.model('Movie');
// const movies = await Movie.find({});
// console.log('movies: ', movies);
// 把它 require 进来,他就会启动子进程去爬取数据,然后把爬取到的数据存在数据库里面
// require('./tasks/movie');
// require('./tasks/api');
require('./tasks/trailer');
})();
app.use(views(resolve(__dirname, './views'), {
extension: 'pug'
}))
app.use(async (ctx, next) => {
await ctx.render('index', {
you: 'zoomdong',
me: 'fireairforce'
})
})
app.listen(4469);

运行之后就可以在数据库里面存储到我们的数据了.

将数据存储到CDN节点上

前面已经拿到了所有数据的预告片和海报信息,现在我们就要将这些信息全部传到我们的七牛云 CDN 上面去。因此我们需要在qiniu.js里面来引入一下这些数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
const qiniu = require('qiniu');
const nanoid = require('nanoid');
const config = require('../config');
const bucket = config.qiniu.bucket;
const mac = new qiniu.auth.digest.Mac(config.qiniu.AK, config.qiniu.SK);
const cfg = new qiniu.conf.Config();
// 首先在qiniu.js里面声明一个上传对象
const client = new qiniu.rs.BucketManager(mac, cfg);
const mongoose = require('mongoose');
const Movie = mongoose.model('Movie');
const uploadToQiniu = async (url, key) => {
return new Promise((resolve, reject) => {
// 上传对象有个方法叫做fetch,它能够从网络上来获取某一份静态的资源
client.fetch(url, bucket, key, (err, ret, info) => {
if (err) {
reject(err);
} else {
if (info.statusCode === 200) {
resolve({
key
});
} else {
reject(info);
}
}
});
})
};
(async () => {
let movies = await Movie.find({
$or:[
{ videoKey:{$exists: false} },
{ videoKey: null },
{ videoKey: '' },
]
})
for(let i = 0;i<movies.length;i++){
let movie = movies[i];
// 如果movie对象里面有视频,并且这个视频没有被上传过的话
if (movie.video && !movie.key) {
try {
console.log('开始上传 video');
let videoData = await uploadToQiniu(movie.video, nanoid() + '.mp4');
console.log('开始上传 cover');
let coverData = await uploadToQiniu(movie.cover, nanoid() + '.png');
console.log('开始上传 poster');
let posterData = await uploadToQiniu(movie.poster, nanoid() + '.png');
if (videoData.key) {
movie.videoKey = videoData.key;
}
if (coverData.key) {
movie.coverKey = coverData.key;
}
if (posterData.key) {
movie.posterKey = posterData.key;
}
console.log(movie);
await movie.save();
} catch (err) {
console.log(err);
}
}
}
})()

然后在index.js里面调用一下,使其能够运行起来。

server/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const Koa = require('koa');
// const ejs = require('ejs');
// const pug = require('pug');
// const { htmlTpl,ejsTpl,pugTpl } = require('./tpl');
const app = new Koa();
const views = require('koa-views');
const { resolve } = require('path');
const mongoose = require('mongoose');
const { connect,initSchemas } = require('./database/init');
(async () => {
await connect();
initSchemas();
// const Movie = mongoose.model('Movie');
// const movies = await Movie.find({});
// console.log('movies: ', movies);
// 把它 require 进来,他就会启动子进程去爬取数据,然后把爬取到的数据存在数据库里面
// require('./tasks/movie');
// require('./tasks/api');
// require('./tasks/trailer');
require('./tasks/qiniu');
})();
app.use(views(resolve(__dirname, './views'), {
extension: 'pug'
}))
app.use(async (ctx, next) => {
await ctx.render('index', {
you: 'zoomdong',
me: 'fireairforce'
})
})
app.listen(4450);

初始化管理员账号

我们之前已经在init.js 里面初始化过我们的schema模型了,现在我们同样需要在里面初始化一个管理员的账号出来,便于后面的管理,因而我们可以在里面新建一个方法用于数据的创建,代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
exports.initAdmin = async() => {
const User = mongoose.model('User');
// 先在数据库里面查询一下我们要创建的数据里面是否存在
let user = await User.findOne({
username:'zoomdong'
})
if(!user){
const user = new User({
username:'zoomdong',
email:'wudong@hrsoft.net',
password:'wd1344492820.',
})
await user.save();
}
}

创建完成之后,可以看看存在数据库里面的密码值是否已经进行了明文加密。

然后在index.js里面加入这个启动脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
(async () => {
await connect();
initSchemas();
await initAdmin();
// const Movie = mongoose.model('Movie');
// const movies = await Movie.find({});
// console.log('movies: ', movies);
// 把它 require 进来,他就会启动子进程去爬取数据,然后把爬取到的数据存在数据库里面
// require('./tasks/movie');
// require('./tasks/api');
// require('./tasks/trailer');
// require('./tasks/qiniu');
})();

运行启动之后,看到数据库里面insert了一条数据则证明成功了。

-------------本文结束感谢您的阅读-------------