爬蟲,其實本就是這麼簡單

chenhongdong發表於2019-08-19

吸取我無,分享我有

時至今日,大前端思想已經深入人心,很多知識都要涉及到。所以對於現在的前端兒來說也是來著不拒的,練就吸星大法的時候,儘量多的吸收知識,最後達到物盡其用的效果

最近,我也是一直在學習關於爬蟲方面的知識,源於之前專案中需要用到的地鐵資訊資料並不是用爬蟲爬下來的資料,而是直接copy的

儘管這些資料一時半會確實不會有太大的變化,不過總覺得還是有些low的。於是學習了關於爬蟲的知識後,打算和大家一起探討交流一番,下面直接進入正題

首先,先來說一下爬蟲和Robots協議是什麼

然後再來介紹爬蟲的基本流程

最後根據實際栗子爬一個豆瓣最近上映的電影來小試牛刀一把

爬蟲及Robots協議

先看定義:爬蟲,是一種自動獲取網頁內容的程式。是搜尋引擎的重要組成部分,因此搜尋引擎優化很大程度上就是針對爬蟲而做出的優化。

再看下Robots協議的介紹,robots.txt是一個文字檔案,robots.txt是一個協議不是一個命令

robots.txt是爬蟲檢視第一個檔案,robots.txt告訴爬蟲在伺服器上什麼檔案是可以被檢視的,爬蟲機器人就會按照檔案中的內容來確定訪問範圍

下圖是豆瓣電影頁面關於robots協議列出來的訪問範圍

爬蟲,其實本就是這麼簡單
爬蟲和Robots協議是緊密相連的,圖上看到的不允許爬的頁面就不要去爬,萬一涉及到一些使用者隱私等方面的東西,之後會被發現而走到法律途徑的

所以在業內大家也都是認可這個Robots協議的,不讓你爬的頁面就不要爬,還網際網路一片安寧即可了

有點跑偏了,下面再看一張圖,來簡單梳理一下上面說的內容

爬蟲,其實本就是這麼簡單
其實有的人會問,爬蟲到底爬的是什麼

這是有一個很有見地的問題,說白了爬蟲拿到的一段是html程式碼,所以說這個對於我們來說並不陌生了,只要我們把它轉換成DOM樹就可以了

那麼,現在再看上圖的右半部份,這是一個對比圖

左邊的是沒有限定Robots協議的,按道理來說admin/private和tmp這三個資料夾是不能抓的,但是由於沒有Robots協議,人家就可以肆無忌憚的爬

再看右邊的是限定了Robots協議的,與之相反,像Google這樣的搜尋引擎也是通過Robots.txt檔案去看一下哪些是不能抓的,然後到admin或private這裡的時候就直接跳過,不去抓取了

好了,介紹的內容就說到這裡吧,不來點真刀真槍的東西全都是紙上談兵了

爬蟲的基本流程

其實對於使用爬蟲來說,流程無外乎這四步

  1. 抓取資料
  2. 資料入庫
  3. 啟動服務
  4. 渲染資料

抓取資料

下面就進入激動人心的環節了,大家不要停,跟著我一起手敲出一個爬取豆瓣電影的頁面出來供自己欣賞欣賞

先來看一下整體目錄結構

爬蟲,其實本就是這麼簡單
既然是抓取資料,我們就得使用業界較為出名的神器------request

request神器

那麼request到底如何用之,且聽風吟一起看程式碼

// 使用起來超簡單
let request = require('request');

request('http://www.baidu.com', function (error, response, body) {
    console.log('error:', error); // 當有錯誤發生時列印錯誤日誌
    console.log('statusCode:', response && response.statusCode); // 列印響應狀態碼
    console.log('body:', body); // 列印百度頁面的html程式碼
});
複製程式碼

看完上面的程式碼,難道你還覺得不明顯嘛。朋友,html程式碼已經出現在眼前了,那麼就別矜持了,只要轉成熟悉的DOM就可以為所欲為了

於是乎,cheerio登場了,大家都稱它是Node版的jq。你就完全按照jq的習慣來操作DOM就可以了

下面也不再繞彎子了,趕緊一起寫爬蟲吧!

讀取內容

首頁要先根據豆瓣電影的頁面來分析一下,哪些是正在熱映的電影,先來看看DOM結構

爬蟲,其實本就是這麼簡單
好了,看完了噻,我們需要的內容也都標註出來了,那麼進入read.js檔案中,一步到位開始擼了

// read.js檔案

// request-promise是讓request支援了promise的語法,可以說是request的小弟
const rp = require('request-promise');
// 將抓取頁面的html程式碼轉為DOM,可以稱之為是node版的jq
const cheerio = require('cheerio');
// 這個是為了在除錯時檢視日誌
const debug = require('debug')('movie:read');

// 讀取頁面的方法,重點來了
const read = async (url) => {
    debug('開始讀取最近上映的電影');

    const opts = {
        url,    // 目標頁面
        transform: body => {
            // body為目標頁面抓取到的html程式碼
            // 通過cheerio.load方法可以把html程式碼轉換成可以操作的DOM結構
            return cheerio.load(body);
        }
    };

    return rp(opts).then($ => {
        let result = [];    // 結果陣列
        // 遍歷這些熱映電影的li
        $('#screening li.ui-slide-item').each((index, item) => {
            let ele = $(item);
            let name = ele.data('title');
            let score = ele.data('rate') || '暫無評分';
            let href = ele.find('.poster a').attr('href');
            let image = ele.find('img').attr('src');
            // 影片id可以從影片href中獲取到
            let id = href && href.match(/(\d+)/)[1];
            // 為了防止豆瓣防盜鏈導致裂圖,換成webp格式載入圖片
            image = image && image.replace(/jpg$/, 'webp');

            if (!name || !image || !href) {
                return;
            }

            result.push({
                name,
                score,
                href,
                image,
                id
            });
            debug(`正在讀取電影:${name}`);
        });
        // 返回結果陣列
        return result;
    });
};
// 匯出方法
module.exports = read;
複製程式碼

程式碼寫完了,回味一下都做了什麼事情吧

  • 通過request抓取了html程式碼
  • cheerio將html轉成了dom
  • 將需要的內容存在陣列(名稱|評分|地址|圖片|id)
  • 返回結果陣列並匯出read方法

資料入庫

這裡我們通過mysql來建立資料庫儲存資料,不太瞭解的也沒有關係,先跟我一步一步做下去。我們先安裝XAMPPNavicat視覺化資料庫管理工具,安裝完畢後按照我下面的來操作即可

XAMPP啟動mysql

爬蟲,其實本就是這麼簡單

Navicat連線資料庫及建表

隻言片語可能都不及有圖有真相的實際,這塊就先看看圖吧

爬蟲,其實本就是這麼簡單
爬蟲,其實本就是這麼簡單
爬蟲,其實本就是這麼簡單
爬蟲,其實本就是這麼簡單
爬蟲,其實本就是這麼簡單
爬蟲,其實本就是這麼簡單
好了讀圖的時代,到這裡就暫告一段落了。消耗了大家不少流量,實在有愧。下面讓我們回到擼程式碼的階段吧

連線資料庫

首先,我們需要在src目錄下建立一個sql檔案,這裡要和剛才建立的資料庫同名,就叫它my_movie.sql了(當然目錄結構已經建立過了)

然後,再回到db.js檔案裡,寫入連線資料庫的程式碼

// db.js

const mysql = require('mysql');
const bluebird = require('bluebird');

// 建立連線
const connection = mysql.createConnection({
    host: 'localhost',      // host
    port: 3306,             // 埠號預設3306
    database: 'my_movie',   // 對應的資料庫
    user: 'root',
    password: ''
});

connection.connect();  // 連線資料庫

// bluebird是為了方便支援promise語法化
// 然後直接把資料庫的query查詢語句匯出方便之後呼叫
module.exports = bluebird.promisify(connection.query).bind(connection);
複製程式碼

上面程式碼就已經建立了連線Mysql資料庫的操作了,接下來,不放緩腳步,直接把內容寫進資料庫吧

寫入資料庫

這時我們來看一下write.js這個檔案,沒錯顧名思義就是用來寫入資料庫的,直接上程式碼

// write.js檔案

// 從db.js那裡匯入query方法
const query = require('./db');
const debug = require('debug')('movie:write');
// 寫入資料庫的方法
const write = async (movies) => {
    debug('開始寫入電影');
    
    // movies即為read.js讀取出來的結果陣列
    for (let movie of movies) {
        // 通過query方法去查詢一下是不是已經在資料庫裡存過了
        let oldMovie = await query('SELECT * FROM movies WHERE id=? LIMIT 1', [movie.id]);
        
        // sql查詢語句返回的是一個陣列,如果不為空陣列的話就表示有資料存過
        // 直接就進行更新操作了
        if (Array.isArray(oldMovie) && oldMovie.length) {
            // 更新movies表裡的資料
            let old = oldMovie[0];
            await query('UPDATE movies SET name=?,href=?,image=?,score=? WHERE id=?', [movie.name, movie.href, movie.image, movie.score, old.id]);
        } else {
            // 插入內容到movies表
            await query('INSERT INTO movies(id,name,href,image,score) VALUES(?,?,?,?,?)', [movie.id, movie.name, movie.href, movie.image, movie.score]);
        }

        debug(`正在寫入電影:${movie.name}`);
    }
};

module.exports = write;
複製程式碼

上面寫完可能會有點蒙圈,畢竟純前端還是很少去寫SQL語句的。不過不要方,待我先把上面的程式碼梳理之後再簡單介紹一下SQL語句部分啊

write.js裡到底寫了哪些?

  • 引入query方法用來書寫SQL語句
  • 遍歷讀取到的結果陣列
  • 查詢是否有資料存過
    • 有: 更新資料
    • 沒有: 插入資料

好了,上面也實現了寫入資料庫的方法,接下來趁熱打鐵,稍微講一下SQL語句吧

SQL語句學習

?表示佔位符 這裡順便簡單的說一下SQL語句裡會用到的語法,無處不在的增刪改查

  1. 插入資料
語法: 
    INSERT INTO 表名(列名) VALUES(列名值)
栗子:
    INSERT INTO tags(name,id,url) VALUES('爬蟲',10,'https://news.so.com/hotnews')
解釋:
    向標籤表(tags)裡插入一條,姓名,id和訪問地址分別為VALUES內對應的值
複製程式碼
  1. 更新資料
語法:
    UPDATE 表名 SET 列名=更新值 WHERE 更新條件
栗子:
    UPDATE articles SET title='你好,世界',content='世界沒你想的那麼糟!' WHERE id=1
解釋:
    更新id為1的文章,標題和內容都進行了修改
複製程式碼
  1. 刪除資料
語法:
    DELETE FROM 表名 WHERE 刪除條件
栗子:
    DELETE FROM tags WHERE id=11
解釋:
    從標籤表(tags)裡刪除id為11的資料
複製程式碼
  1. 查詢
語法:
    SELECT 列名 FROM 表名 WHERE 查詢條件 ORDER BY 排序列名
栗子:
    SELECT name,title,content FROM tags WHERE id=8
解釋:
    查詢id為8的標籤表裡對應資訊
複製程式碼

到這裡已經把讀寫的方法全寫完了,想必大家看的也有些疲憊了。也是時候該檢驗一下成果了,不然都是在扯淡的狀態

執行讀寫操作

現在就來到index.js中,開始檢驗一番吧

// index.js檔案

const read = require('./read');
const write = require('./write');
const url = 'https://movie.douban.com'; // 目標頁面

(async () => {
    // 非同步抓取目標頁面
    const movies = await read(url);
    // 寫入資料到資料庫
    await write(movies);
    // 完畢後退出程式
    process.exit();
})();
複製程式碼

完畢,執行一下看看是什麼效果,直接上圖

爬蟲,其實本就是這麼簡單
程式碼已經執行完了,接下來再回到Navicat那裡,看看資料到底有沒有寫進去呢,還是用圖說話吧
爬蟲,其實本就是這麼簡單
至此資料抓取及入庫操作我們都搞定了,不過似乎還差點什麼?

那就是我們需要寫個頁面來給展示出來了,由於抓取和寫入資料都是在node環境下才允許。所以我們還要建立一個web服務用來展示頁面的,堅持一下馬上就OK了,加油

啟動服務

由於要建立web服務了,所以開始寫server.js的內容吧

server服務

// server.js檔案

const express = require('express');
const path = require('path');
const query = require('../src/db');
const app = express();

// 設定模板引擎
app.set('view engine', 'html');
app.set('views', path.join(__dirname, 'views'));
app.engine('html', require('ejs').__express);

// 首頁路由
app.get('/', async (req, res) => {
    // 通過SQL查詢語句拿到庫裡的movies表資料
    const movies = await query('SELECT * FROM movies');
    // 渲染首頁模板並把movies資料傳過去
    res.render('index', { movies });
});
// 監聽localhost:9000埠
app.listen(9000);
複製程式碼

寫完了server服務了,最後再到index.html模板裡看看吧,這可是最後的東西了,寫完就全部大功告成了

渲染資料

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>熱映的電影</title>
</head>
<body>
    <div class="container">
        <h2 class="caption">正在熱映的電影</h2>
        <ul class="list">
            <% for(let i=0;i<movies.length;i++){ 
                let movie = movies[i];  
            %>
                <li>
                    <a href="<%=movie.href%>" target="_blank">
                        <img src="<%=movie.image%>" />
                        <p class="title"><%=movie.name%></p>
                        <p class="score">評分:<%=movie.score%></p>
                    </a>
                </li>
            <% } %>
        </ul>
    </div>
</body>
</html>
複製程式碼

通過模板引擎遍歷movies陣列,然後進行渲染就可以了

Now,看下最終的效果

爬蟲,其實本就是這麼簡單
跟著一起走到了這裡,就是緣分,很高興經歷了這麼長的文章學習,大家應該會對爬蟲的知識有了很好的認識了

這裡順便發一下程式碼,以方便大家參考敲敲敲

感謝大家的觀看,886

相關文章