????????上一篇我們講述了爬取小說(shuō)的基本邏輯灵迫,但是這樣還是遠(yuǎn)遠(yuǎn)不夠的书幕。一來(lái)沒(méi)有一些異常的處理象迎,二來(lái)爬取速度過(guò)快還容易引起小說(shuō)網(wǎng)站的反爬蟲(chóng)機(jī)制導(dǎo)致IP被禁,所以接下來(lái)我們一一把這些邏輯優(yōu)化處理下慰枕,讓我們更加優(yōu)雅的爬取到我們想要的內(nèi)容具则。
使用瀏覽器代理
????????無(wú)論怎樣,爬取小說(shuō)最好都是要使用代理服務(wù)器具帮。這樣即使觸發(fā)了反爬蟲(chóng)機(jī)制對(duì)自身的IP也不會(huì)有太大的影響博肋,這對(duì)開(kāi)發(fā)階段尤其重要,甚至對(duì)以后再次爬取也有不小的幫助蜂厅。
原理也很簡(jiǎn)單
var browser = await puppeteer.launch({
headless:true,
args: [
'--proxy-server=socks5://127.0.0.1:1080'
]
})
????????在創(chuàng)建瀏覽器的時(shí)候添加--proxy-server
參數(shù)即可匪凡。當(dāng)然也有很多其他可選的參數(shù)。具體可以參考chrome瀏覽器運(yùn)行參數(shù)幫助掘猿。這里我提供一個(gè)chrome瀏覽器運(yùn)行參數(shù)的參考網(wǎng)站病游,里面涵蓋了幾乎所有chrome的運(yùn)行參數(shù)。chrome args helper
????????ok稠通,原理知道后我們就去找代理的服務(wù)器吧衬衬。好在這方面網(wǎng)上一點(diǎn)也不缺,隨便搜以下代理服務(wù)器改橘,我就找到不少滋尉。這里提供一個(gè)網(wǎng)站,提供 免費(fèi)代理服務(wù)器唧龄。
????????具體怎么去獲取呢?這里也不繞彎子了奸远。還是通過(guò)puppeteer來(lái)獲得這里我們所需要的信息既棺。
新建一個(gè)文件 proxyserver.js
const puppeteer = require('puppeteer')
//max retry times
const MAX_RT = 3;
function getproxylist() {
return new Promise(async (resolve, reject) => {
var tempbrowser;
for (var i = MAX_RT; i > 0; i--) {
if (tempbrowser) {
break;
}
console.log('start to init browser...');
tempbrowser = await puppeteer.launch({
headless:true
}).catch(ex => {
if (i-1 > 0) {
console.log('browser launch failed. now retry...');
} else {
console.log('browser launch failed!');
}
});
}
if (!tempbrowser) {
reject('fail to launch browser');
return;
}
const browser = tempbrowser;
console.log('start to new page...');
var page = await browser.newPage().catch(ex=>{
console.log(ex);
});
if (!page) {
reject('fail to open page!');
return;
}
var respond;
for (var i = MAX_RT; i > 0; i--) {
if (respond) {
break;
}
console.log('start to goto page...');
respond = await page.goto("https://www.socks-proxy.net/", {
'waitUntil':'domcontentloaded',
'timeout':120000
}).catch(ex=>{
if(i-1 > 0) {
console.log('fail to goto website. now retry...');
} else {
console.log('fail to goto website!');
}
});
}
if (!respond) {
reject('fail to go to website!');
return;
}
console.log('start to find element in page...');
var layoutVisible = await page.waitForSelector('#list .container table tbody').catch(ex=>{
console.log("oh....no...!!!, i can not see anything!!!");
});
if (!layoutVisible) {
reject('layout is invisible!');
return;
}
console.log('start to get info from element...');
var proxyModelArray = await page.evaluate(async () => {
let list = document.querySelectorAll('#list .container table tbody tr');
if (!list) {
return;
}
let result = [];
for (var i = 0; i < list.length; i++) {
var row = list[i];
var cells = row.cells;
var ip = cells[0].textContent;
var port = cells[1].textContent;
var code = cells[2].textContent;
var version = cells[4].textContent;
var proxyServerModel = {
'ip' : ip,
'port' : port,
'code' : code,
'version' : version
}
result.push(proxyServerModel);
}
return result;
});
await browser.close().catch(ex=>{
console.log('fail to close the browser!');
});
console.log('close the browser');
//console.log(proxyModelArray);
if (!proxyModelArray || proxyModelArray.length === 0) {
reject();
return;
}
resolve(proxyModelArray);
})
}
// async function test() {
//
// var proxylist;
// for (var i = 0; i < MAX_RT; i++) {
//
// if (proxylist) {
// break;
// }
//
// console.log('start get proxylist from web...');
// proxylist = await getproxylist().catch(ex=> {
// if (i+1<MAX_RT) {
// console.log('fail to get proxylist. now retry...');
// } else {
// console.log('fail to get proxylist. end!!!');
// }
// });
// }
// if (!proxylist) {
// console.log('fail to get proxylist!!!');
// return;
// }
// console.log(proxylist);
// }
// test();
module.exports.getProxyList = getproxylist;
把test的注釋取消運(yùn)行下可以得到以下結(jié)果:
start get proxylist from web...
start to init browser...
start to new page...
start to goto page...
start to find element in page...
start to get info from element...
close the browser
[ { ip: '103.214.200.58',
port: '1080',
code: 'BD',
version: 'Socks4' },
{ ip: '45.55.202.229',
port: '10080',
code: 'US',
version: 'Socks5' },
{ ip: '150.129.207.75',
port: '6667',
code: 'IN',
version: 'Socks5' },
{ ip: '72.250.134.235',
port: '6001',
code: 'US',
version: 'Socks4' },
......
這樣我們就獲取到代理服務(wù)器的最關(guān)鍵部分了。當(dāng)然啦懒叛,這個(gè)網(wǎng)站上的代理對(duì)于國(guó)內(nèi)的IP支持并不好丸冕,大家可以找國(guó)內(nèi)的代理服務(wù)器網(wǎng)站來(lái)抓取,仿照來(lái)寫(xiě)一個(gè)薛窥。
設(shè)定重試次數(shù)并使用休眠
????????不知道大家發(fā)現(xiàn)沒(méi)有胖烛,在 proxyserver.js 中眼姐,我們獲取瀏覽器實(shí)例到打開(kāi)頁(yè)面,都使用了一個(gè)循環(huán)來(lái)處理佩番。循環(huán)里面才是執(zhí)行的邏輯众旗。這個(gè)循環(huán)就是重試機(jī)制,他設(shè)定了最大重試次數(shù) MAX_RT 趟畏。即失敗會(huì)再次請(qǐng)求當(dāng)前操作贡歧。
????????有這個(gè)必要嗎浓冒?----還真的有3膊簟!我試過(guò)很多次嘗試發(fā)現(xiàn)逞壁,很多小說(shuō)網(wǎng)站就是矯情猎莲。第一次死活打不開(kāi)绍弟。當(dāng)然啦,循環(huán)也是為了更加穩(wěn)健而已著洼,一次就獲取成功也會(huì)馬上跳出循環(huán)的樟遣。
休眠代碼如下
function sleep(time = 0) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
})
}
使用方法是(注意只能在異步函數(shù)中使用)
(async ()=> {
//休眠3秒
await sleep(3000);
})()
????????休眠的作用就不說(shuō)了,反正盡量裝的像個(gè)人唄9年碘!
啟動(dòng)多個(gè)瀏覽器實(shí)例來(lái)加快獲取內(nèi)容
????????人多力量大!1個(gè)人的力量總是有限的展鸡。同理1個(gè)瀏覽器抓取大量的內(nèi)容速度也是有限的屿衅,而且風(fēng)險(xiǎn)也大。這里我同時(shí)啟動(dòng)了20個(gè)帶代理服務(wù)器的瀏覽器實(shí)例莹弊,相當(dāng)于20個(gè)IP不同的主機(jī)在同時(shí)抓取我們所需要的內(nèi)容涤久。前面我們獲取到了代理服務(wù)器列表,這里可以這樣用:
/**
* 獲取代理瀏覽器
*/
function getProxyBrowser(proxyList) {
return new Promise(async (resolve, reject) => {
var browserList = [];
for (var i = 0; i < proxyList.length; i++) {
var proxyserver = proxyList[i];
var proxyOption = proxyserver.version.toLowerCase() + '://' + proxyserver.ip + ':' + proxyserver.port;
var browser = await puppeteer.launch({
headless:true,
args: [
//'--window-size="800,600"',
//'--start-fullscreen'
'--proxy-server='+proxyOption
]
}).catch();
if (browser) {
browserList.push(browser);
}
}
if (browserList.length == 0) {
reject()
return;
}
resolve(browserList);
})
}
????????調(diào)用這個(gè)方法可以獲取到proxyList大小的代理瀏覽器實(shí)例的列表忍弛。這樣我們就相當(dāng)于有20個(gè)人同時(shí)幫我們干活了响迂。之前我們獲取到了小說(shuō)的所有章節(jié)的url,這里就交給這20個(gè)代理瀏覽器去獲取吧细疚!
????????幸運(yùn)的是nodejs是單進(jìn)程的蔗彤。這樣我們也不用考慮同步的問(wèn)題。這樣步驟就簡(jiǎn)單了疯兼。
單個(gè)瀏覽器的任務(wù)是:
????????似乎到這里就已經(jīng)結(jié)束了然遏?不是的。這里還是有一些問(wèn)題吧彪。
內(nèi)容優(yōu)化
????????很多小說(shuō)網(wǎng)站的正文內(nèi)容中都有一些令人很不爽的廣告啊待侵、推薦啊之類的東西,更有甚者正文中間都有奇怪的東西姨裸。這能忍嗎秧倾?肯定不能霸乖汀!這里我們對(duì)獲取到的內(nèi)容進(jìn)行一定的處理:舉個(gè)栗子
/**
* 處理獲取到的內(nèi)容
* @unHandleContent 未處理的內(nèi)容(可能會(huì)包含某些奇怪的東西那先,統(tǒng)統(tǒng)去掉农猬,這里寫(xiě)去除邏輯∥搁牛可以先調(diào)試一條數(shù)據(jù)試下效果)
*/
function handleContent(unHandleContent) {
var result = unHandleContent;
// result = result.replace(/ /g,' ');
// result = result.replace(/\n|\r/g, '<br>');
// result = result.replace(/<[a-z]{1,6}\s.*\/[a-z]{1,6}>/g, '');
// result = result.replace(/<br>/g, '\n');
return result;
}
????????ok盛险,到這里就結(jié)束了。有什么問(wèn)題的可以獲取源碼來(lái)看看勋又。