puppeteer實戰之網頁爬蟲,模擬操作《二》

1.前言

 由於公司有幾款新聞,視頻類的app產品,於是乎文章和視頻的穩定來源成爲一個必須解決的問題。 公司也研究了很多的
爬蟲方案,最後使用puppeteer開發了一個文章的採集中心。 這是一個基於node的服務器,主要設計的思路是:當接收到抓取某個站點文章的任務後,node服務器就啓動一個 爬蟲器,將該網站的文章信息解析出來,然後上報給一個java服務器,由java負責數據的處理和存儲。在此簡單介紹一下node端的實現,這是一個簡化版的。


2.使用node寫一個接口,負責接收中心服務器的文章爬取任務

 node搭建一個微服務的話有很多種,這裏使用的是express , 使用 npm install --save express 即可。

var express = require('express');
var app = express();
var download163 = require('../download163.js');

// 設置跨域訪問
app.all('*',function(req,res,next){
	res.header("Access-Control-Allow-Origin", "*");
	res.header("Access-Control-Allow-Headers", "X-Requested-With");
	res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
	res.header("X-Powered-By",' 3.2.1');
	res.header("Content-Type", "application/json;charset=utf-8");
	next();
});

app.get('/start',function(req,res){
	console.log('接收到分配任務.....');
	var url = req.query.url;
	console.log('url:'+req.query.url)
    console.log('pas:'+req.query.pas)
    try {
    	console.log('開始執行任務.....');
    	new download163(url);
    	res.json({
    		code:200,
    		work:true
    	});

    } catch(e){
    	http404(req,res)
    }
});

var http404 = app.get('/404',function(req,res){
	res.end("404");
});

// 配置服務端口
var server = app.listen(3000,function(){
	var host = server.address(),address;
	var port = server.address().port;
	console.log("正在啓動服務器,監聽3000.........");
});

接收到 開始任務的時候,創建一個下載網易新聞 對象。

2.寫一個工具類,請求中心服務器,獲取目標網站解析方式的json

var request = require('request');
var qs = require('querystring');

class Tools{
	static timeout(delay){
		return new Promise((resolve,reject) => {
			setTimeout(() => {
				try{
					resolve(1);
				} catch(e){
					reject(0)
				}
			},delay);
		});
	}

    static getArticeRule(type){
    	return new Promise((resolve,reject) => {
    		request('******?uuid='+type,function(error,response,body){		
				if (!error && response.statusCode == 200) {
					console.log('body-------->'+body);
					resolve(JSON.parse(body));
				} else{
					reject(error)
				}
			});
    	});
	}
}

module.exports = Tools;


這裏有個非常需要注意的點,由於js都是異步執行的, 返回值都是需要 使用 Promise容器裏面的。不然你會發現一個很坑的事情,就是 上述的 getAriceRule方法的返回值一直是個 undefined ,打日誌的話你會發現, 這個方法在 賦值之後才執行的。如下所示:
    static getArticeRule(type){

		request('http://ck.chatting365.xyz/api/imgCode/rfCode?uuid='+type,function(error,response,body){		
			if (!error && response.statusCode == 200) {
				console.log('body-------->'+body);
				return body;
			} 
		});
		
   //  	return new Promise((resolve,reject) => {
   //  		request('http://ck.chatting365.xyz/api/imgCode/rfCode?uuid='+type,function(error,response,body){		
			// 	if (!error && response.statusCode == 200) {
			// 		console.log('body-------->'+body);
			// 		resolve(JSON.parse(body));
			// 	} else{
			// 		reject(error)
			// 	}
			// });
   //  	});
	}
這個tool我是用下面的js調用:
const puppeteer = require('puppeteer')
var tools = require('./src/tools.js');
var type = require('./src/model/type.js')();

(async() => {
	console.log('開始打開瀏覽器...');
	var a = await tools.getArticeRule(1);
	console.log('lala---------->'+JSON.stringify(a));
})();
結果就是: lala 先打印了 。。。 我們理想的結果應該是先請求中心服務器的接口, 然後才輸出返回的結果值。 有興趣的同學可以去研究一下 Promise。這裏不過多介紹了。


3.寫一個puppeteer 去抓取目標網站的內容, 然後將結果上報給中心服務器

const puppeteer = require('puppeteer');
var tools = require('./tools.js');
var request = require('request');

var delay = 2000;

class Download163 {
    constructor(url, type) {
        // this.url = 'http://sports.163.com';
        this.url = url;
        this.type = type;
        this.init();
    }

    async init() {
        console.log('正在啓動瀏覽器...');
        this.browser = await puppeteer.launch({headless: false});
        console.log('正在打開新頁面...');
        this.page = await this.browser.newPage();
        await this.loadSport163(this.url);

        console.log('正在關閉瀏覽器...');
        await tools.timeout(delay);
        await this.browser.close();
    }

    async loadSport163(url) {
        console.log('163 is ready ----------------');
        let page = this.page;
        await page.goto(url);

        try {
            await page.keyboard.down('PageDown');
            await page.keyboard.up('PageDown');
            console.log('向下翻頁......');
            await tools.timeout(3000);
            // 開始抓取文章列表頁
            var listMap = await page.evaluate(() => {
                var alist = [...document.querySelectorAll('.topnews_news ul li a')];
                return alist.map((el) => {
                    return {
                        href: el.href,
                        title: el.innerText
                    }
                });
            });
            for (var i = 0; i < listMap.length; i++) {
                var a = listMap[i].href;
                await this.loadContent(a, this.type);
            }
        } catch (e) {
            console.log(e);
        }

    }

    async loadContent(url, type) {
        try {
            await tools.timeout(2000);
            let page = this.page;
            await page.goto(url);
            // 這裏使用tools請求服務器,獲取到 網站解析方式,其實就是各個節點
            // 的選擇器 page.$eval的第一個參數就是一個css選擇器。網易體育的新聞詳情頁差不多就如下:
            /**
             {
                 "id": "0",
                 "target": "163",
                 "title": "#epContentLeft h1",
                 "meta": "meta[name=keywords]",
                 "content": "#endText p",
                 "source": "#ne_article_source"
               }
             */
            var rule = tools.getArticeRule(type);
            var title = await page.$eval(rule.title, el => el.innerText);
            var source = await page.$eval(rule.source, el => el.innerText);

            var contentP = await page.evaluate(() => {
                var pList = [...document.querySelectorAll(rule.content)];
                return pList.map(el => {
                    return {
                        p: el.innerHTML
                    };
                });
            });
            var meta = await page.$eval(rule.meta, el => el.content);

            var data = {
                title: title,
                source: source,
                content: contentP,
                meta: meta
            }
            // 上傳服務器
            request('*****', data, function (error, response, body) {
                if (!error && response.statusCode == 200) {
                    console.log('上傳成功-------->' + body);

                } else {
                    console.log("上傳失敗--------->" + error)
                }
            })

        } catch (e) {
            console.log(e);
        }
    }
}
module.exports = Download163;
由於時間的原因,上述代碼也寫的比較糟心,附上碼雲地址吧,後期碼雲上會有更新版的。









發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章