专栏名称: YDJFE
伊的家前端团队
目录
相关文章推荐
公安部网安局  ·  网警给您拜大年 ·  21 小时前  
公安部网安局  ·  网警给您拜大年 ·  21 小时前  
上海应急守护  ·  忠诚守“沪” 蛇年安康 | 上海应急人给您拜年! ·  2 天前  
上海应急守护  ·  忠诚守“沪” 蛇年安康 | 上海应急人给您拜年! ·  2 天前  
51好读  ›  专栏  ›  YDJFE

爬百度文库有偿资料顺便学习mongoose

YDJFE  · 掘金  · 前端  · 2018-03-27 14:40

正文

爬百度文库有偿资料顺便学习mongoose

写本文大致分为以下几个心理活动。

本想做做爬虫,然后持久化到mongodDb。后来,有需求要下载百度文库的资料,又没有下载券,于是想想怎样能够免费下载资料,顺便保存下来。所以就有了获取百度文库的资料而顺便学习mongoose。

按心理活动排序本文叙述分为以下几点。


mongoDb安装

1.安装

sudo brew install mongodb

2. 创建一个数据库存储目录 /data/db:

sudo mkdir -p /data/db

3.启动Mongodb

sudo mongod

4.新开窗口,进入mongodb命令行模式

mongo

连接mongodb

  • 在根目录下安装mongodb数据驱动库
cd ~ && cnpm i mongodb
  • 新建一个连接文件connect.js
var MongoClient = require('mongodb').MongoClient;
// 连接数据库
var url_test = 'mongodb://localhost:27017/test'; //数据库test本不存在,连接时会自动创建

var insertData = function(db){
  // 往test数据库里新建一个site集合,并插入一条数据
  db.collection('site').insertOne({name: 'guojc', age: 99, hobby: 'movie'}, function(err, result){
    console.log('inserted successly');
    console.log(result);
    db.close();
    console.log('close');
  });
}

MongoClient.connect(url_test, function(err, db) {
  console.log('Connected successly to server.');
  insertData(db);
});
  • node connect.js,发现连接成功,但是插入数据报错 ==db.collection is not a function==

  • 解决方法

  • 我改成这样

var MongoClient = require('mongodb').MongoClient;
// 连接数据库
var url = 'mongodb://localhost:27017';

var insertData = function(client){
  // 往test数据库里新建一个site集合,并插入一条数据
  client.db('test').collection('site').insertOne({name: 'guojc', age: 99, hobby: 'movie'}, function(err, result){
    console.log('inserted successly');
    console.log(result);
    client.close();
    console.log('close');
  });
}

MongoClient.connect(url, function(err, client) {
  console.log('Connected successly to server.');
  insertData(client);
});
  • show dbs 能够看到创建的数据库
  • use test 选择创建爱的数据库
  • show tables 显示表
  • db.site.find() 查询该表所有数据

Mongoose简介

Mongoose是在node.js异步环境下对mongodb进行便捷操作的对象模型工具。本文将详细介绍如何使用Mongoose来操作MongoDB。

Mongoose是NodeJS的驱动,不能作为其他语言的驱动。Mongoose有两个特点

    1. 通过关系型数据库的思想来设计非关系型数据库
    1. 基于mongodb驱动,简化操作

Mongooose三个重要概念:

Schema: 相当于一个数据库的模板,Schema不具备操作数据库的能力。

Model: 由Schema编译而成的构造器,具有抽象属性和行为,可以对数据库进行增删查改。

Entity: 真实的数据。

Schema 生成 ModelModel 创造 DocumentModelDocument都可对数据库操作造成影响。

简单demo

const mongoose = require('mongoose');


mongoose.connect('mongodb://localhost:27017/test');
const con = mongoose.connection;
con.on('error', console.error.bind(console, '连接数据库失败'));
con.once('open',()=>{
    //定义一个schema
    let Schema = mongoose.Schema({
        name:String,
        age:Number
    });
    // 自定义方法
    Schema.methods.getAge = function(){
        console.log("I am "+this.age + "years old");
    }
    //继承一个schema
    let Model = mongoose.model("student",Schema);
    //生成一个document
    let student = new Model({
        name:'hanmeimei',
        age:16
    });
    //存放数据
    student.save((err,res)=>{
        if(err) return console.log(err);
        res.getAge();
        //查找数据
        Model.find({name:'hanmeimei'},(err,data)=>{
            console.log(data);
        })
    });
})

输出

I am 16years old
[ { _id: 5ab1cad40b0132e9a9e6c65b,
    name: 'hanmeimei',
    age: 16,
    __v: 0 } ]

查看数据库,发现多了一个students的table,Mongoose会将集合名称设置为模型名称的小写版。如果名称的最后一个字符是字母,则会变成复数;如果名称的最后一个字符是数字,则不变;如果模型名称为"MyModel",则集合名称为"mymodels";如果模型名称为"Model1",则集合名称为"model1"

参考:

mongoose基础入门

深入浅出mongoose


保存百度文库资料为图片

(PS) 前提是百度文库能看到内容,只是下载需要下载券。

一看到这个需求第一反应就是

  • 用爬虫
  • 打开百度文
  • 然后爬取需要的资料保存到本地

然而打开文库看了看里面的内容是多张 图片 来的, 而且有 加载更多按钮 emmmm...

那就用 puppeteer吧,之前也用过,于是思路分为以下几点

  • 打开链接
  • 点击全屏查看(感觉省了一堆功夫)
  • 点击加载更多
  • 去掉页面上的多余的dom节点
  • 保存为pdf/图片

直接上代码

1. 打开链接

await page.goto(url);

2. 点击全屏

page.click('a[data-toolsbar-log=fullscreen]')

3. 点击加载更多

page.click('.moreBtn')

4. 去掉页面上的多余的dom节点

await page.evaluate(v => {
    // dom操作
})

5. 保存为pdf

 page.pdf({path: 'page.pdf'});
 or 
 page.screenshot({
   path: '1.png',
   fullPage:true
 });

问题来了

  • 保存为pdf时图片变空白
  • 改成保存为图片,部分图片空白

发现滚动操作的时候会重新请求图片资源,所以dom节点上面只会存在部分图片。

看了看每张图片的外层都有一个pageNo-x的ID,


根据这个为切入点的话,就改良了上面步骤。

  • 打开链接(同上)
  • 保存已经加载的图片
  • 点击加载更多
  • 保存加载的并且id值不等于之前几个的图片
  • 下拉
  • 保存剩余的图片
  • 将最后合成的图片保存为图片(资料只需要打印出来,所以保存为图片也可以)
  • 优化(去掉图片背景的广告)

部分代码

// 找图片,并用一个新节点存起来
async function collectPng(index) {
  const res = await page.evaluate(v => {
      const div = document.getElementById('collection') || document.createElement('div')
      div.id = 'collection'
      document.getElementsByTagName('body')[0].appendChild(div)
      const item = document.getElementById('pageNo-'+v)
      const rpi = item?item.getElementsByClassName('reader-pic-item')[0]: null
      rpi&&(rpi.style.position = 'relative')
      rpi&&div.appendChild(rpi)
      return {index:v, exist:!!rpi}
  },index)
  return res
}
// 根据返回值,判断是否继续查找还是下拉页面
async function collecting(index) {
  const res = await collectPng(index)
  if(res.exist) {
    index+=1
    await collecting(index)
  } else {
    if(!hasLoadMore){
      console.log('加载更多')
      hasLoadMore = true
      await loadMore()
      await collecting(index)
    } else if(index<9){
      console.log('下拉')
      await pressDown()
      await collecting(index)      
    } else {

    }
  }
}
// 生成纯图片组合成的dom
async function createDom(){
  await page.evaluate(v => {
    const div = document.getElementById('collection');
    const body = document.createElement('body');
    body.appendChild(div)
    document.getElementsByTagName('body')[0].remove()
    document.getElementsByTagName('html')[0].appendChild(body)
  })
}
//pressDown
async function pressDown() {
  await page.keyboard.press('ArrowDown',{delay: 2500});
  await timeout(1000);  
}

实际执行情况

  • 开始,保存前三张图片
  • 发现没有了,加载更多
  • 发现没有了,下拉
  • 知道真的没有了就保存图片

最后优化图片背景

由于图片背景会有这些教育机构,为了打印出来更加清晰,可以尝试去掉背景

自己的思路是

  • 将保存的图片用canvas画出来,然后对比像素点rgb的值均大于100的话就变成白色,再保存成图片。
  • 当然,你可以用PS抠 :)

代码

结语

如果你能看到这里,谢谢。文章、代码写的较为之粗糙,只是将自己的想法用代码实现,本文初衷是爬虫并持久化到mongoDb的,后来感觉偏离了路线。后面会在这个基础上加上这方面功能。至于这个获取百度文库资源的,只是针对这篇三年级上册数学期末试卷及答案,还没做其他灵活处理,以后会考虑更多实际情况。