puppeteer小说爬虫(3)--优化处理

        上一篇我们讲述了爬取小说的基本逻辑,但是这样还是远远不够的。一来没有一些异常的处理,二来爬取速度过快还容易引起小说网站的反爬虫机制导致IP被禁,所以接下来我们一一把这些逻辑优化处理下,让我们更加优雅的爬取到我们想要的内容。

使用浏览器代理

        无论怎样,爬取小说最好都是要使用代理服务器。这样即使触发了反爬虫机制对自身的IP也不会有太大的影响,这对开发阶段尤其重要,甚至对以后再次爬取也有不小的帮助。

原理也很简单
var browser = await puppeteer.launch({
                headless:true,
                args: [
                '--proxy-server=socks5://127.0.0.1:1080'
                ]
            })

        在创建浏览器的时候添加--proxy-server参数即可。当然也有很多其他可选的参数。具体可以参考chrome浏览器运行参数帮助。这里我提供一个chrome浏览器运行参数的参考网站,里面涵盖了几乎所有chrome的运行参数。chrome args helper

        ok,原理知道后我们就去找代理的服务器吧。好在这方面网上一点也不缺,随便搜以下代理服务器,我就找到不少。这里提供一个网站,提供 免费代理服务器

代理服务器地址

        具体怎么去获取呢?这里也不绕弯子了。还是通过puppeteer来获得这里我们所需要的信息。

新建一个文件 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的注释取消运行下可以得到以下结果:
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' },
    ......
这样我们就获取到代理服务器的最关键部分了。当然啦,这个网站上的代理对于国内的IP支持并不好,大家可以找国内的代理服务器网站来抓取,仿照来写一个。

设定重试次数并使用休眠

        不知道大家发现没有,在 proxyserver.js 中,我们获取浏览器实例到打开页面,都使用了一个循环来处理。循环里面才是执行的逻辑。这个循环就是重试机制,他设定了最大重试次数 MAX_RT 。即失败会再次请求当前操作。

        有这个必要吗?----还真的有!!我试过很多次尝试发现,很多小说网站就是矫情。第一次死活打不开。当然啦,循环也是为了更加稳健而已,一次就获取成功也会马上跳出循环的。

休眠代码如下

function sleep(time = 0) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    })
}

使用方法是(注意只能在异步函数中使用)

(async ()=> {
    //休眠3秒
    await sleep(3000);
})()

        休眠的作用就不说了,反正尽量装的像个人呗!!

启动多个浏览器实例来加快获取内容

        人多力量大!1个人的力量总是有限的。同理1个浏览器抓取大量的内容速度也是有限的,而且风险也大。这里我同时启动了20个带代理服务器的浏览器实例,相当于20个IP不同的主机在同时抓取我们所需要的内容。前面我们获取到了代理服务器列表,这里可以这样用:

/**
*   获取代理浏览器
*/
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);
    })
}

        调用这个方法可以获取到proxyList大小的代理浏览器实例的列表。这样我们就相当于有20个人同时帮我们干活了。之前我们获取到了小说的所有章节的url,这里就交给这20个代理浏览器去获取吧!

        幸运的是nodejs是单进程的。这样我们也不用考虑同步的问题。这样步骤就简单了。

单个浏览器的任务是:
流程图

        似乎到这里就已经结束了?不是的。这里还是有一些问题。

内容优化

        很多小说网站的正文内容中都有一些令人很不爽的广告啊、推荐啊之类的东西,更有甚者正文中间都有奇怪的东西。这能忍吗?肯定不能啊!这里我们对获取到的内容进行一定的处理:举个栗子

/**
*   处理获取到的内容
*   @unHandleContent    未处理的内容(可能会包含某些奇怪的东西,统统去掉,这里写去除逻辑。可以先调试一条数据试下效果)
*/
function handleContent(unHandleContent) {

    var result = unHandleContent;

    // result = result.replace(/&nbsp;/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,到这里就结束了。有什么问题的可以获取源码来看看。

源码 github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容