1.前言
由於公司有幾款新聞,視頻類的app產品,於是乎文章和視頻的穩定來源成爲一個必須解決的問題。 公司也研究了很多的
爬蟲方案,最後使用puppeteer開發了一個文章的採集中心。 這是一個基於node的服務器,主要設計的思路是:當接收到抓取某個站點文章的任務後,node服務器就啓動一個 爬蟲器,將該網站的文章信息解析出來,然後上報給一個java服務器,由java負責數據的處理和存儲。在此簡單介紹一下node端的實現,這是一個簡化版的。
爬蟲方案,最後使用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;
由於時間的原因,上述代碼也寫的比較糟心,附上碼雲地址吧,後期碼雲上會有更新版的。源碼地址:https://gitee.com/xiaoxia_dyh/PuppeteerDemo
puppeteer相關api地址:https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md
puppeteer相關api地址:https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md