爬百度文庫有償資料順便學習mongoose

YDJFE發表於2018-03-27

寫本文大致分為以下幾個心理活動。

本想做做爬蟲,然後持久化到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時圖片變空白
  • 改成儲存為圖片,部分圖片空白

爬百度文庫有償資料順便學習mongoose
發現滾動操作的時候會重新請求圖片資源,所以dom節點上面只會存在部分圖片。

看了看每張圖片的外層都有一個pageNo-x的ID,

爬百度文庫有償資料順便學習mongoose


根據這個為切入點的話,就改良了上面步驟。

  • 開啟連結(同上)
  • 儲存已經載入的圖片
  • 點選載入更多
  • 儲存載入的並且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);  
}
複製程式碼

實際執行情況

爬百度文庫有償資料順便學習mongoose

  • 開始,儲存前三張圖片
  • 發現沒有了,載入更多
  • 發現沒有了,下拉
  • 知道真的沒有了就儲存圖片

最後優化圖片背景

由於圖片背景會有這些教育機構,為了列印出來更加清晰,可以嘗試去掉背景

爬百度文庫有償資料順便學習mongoose

自己的思路是

  • 將儲存的圖片用canvas畫出來,然後對比畫素點rgb的值均大於100的話就變成白色,再儲存成圖片。
  • 當然,你可以用PS摳 :)

程式碼

結語

如果你能看到這裡,謝謝。文章、程式碼寫的較為之粗糙,只是將自己的想法用程式碼實現,本文初衷是爬蟲並持久化到mongoDb的,後來感覺偏離了路線。後面會在這個基礎上加上這方面功能。至於這個獲取百度文庫資源的,只是針對這篇三年級上冊數學期末試卷及答案,還沒做其他靈活處理,以後會考慮更多實際情況。

相關文章