nodejs + puppeteer 模拟淘宝登录并采集数据

熟悉puppeteer模块时做的小demo,代码十分简单易懂,分享给大家交流学习,请勿恶意抓取或做违反国家政策的行为。

一、前期准备工作

首先需要安装nodejs,并初始化一个项目,安装puppeteer模板以及log4js日志模板并保存。

   npm install puppeteer --save-dev

   npm install log4js --save-dev

二、先引用模块并定义相关变量以及日志配置

 1 var puppeteer = require(\'puppeteer\');
 2 var log4js = require(\'log4js\');
 3 var logger = log4js.getLogger();
 4 // 结果集合
 5 var resultList = [];
 6 // 采集最大页码
 7 var maxPage = 10;
 8 // 当前采集页码
 9 var nowPage = 1;
10 
11 
12 log4js.configure({
13     appenders: {
14         xcLogFile: {
15             type: "dateFile",
16             filename: __dirname + \'/logs/log4js.log\',//您要写入日志文件的路径
17             //compress: true,//(默认为false) - 在滚动期间压缩备份文件(备份文件将具有.gz扩展名)
18             pattern: "-yyyy-MM-dd-hh",//(可选,默认为.yyyy-MM-dd) - 用于确定何时滚动日志的模式。格式:.yyyy-MM-dd-hh:mm:ss.log
19             encoding: \'utf-8\',//default "utf-8",文件的编码
20             maxLogSize: 2048000 //文件最大存储空间,当文件内容超过文件存储空间会自动生成一个文件xxx.log.1的序列自增长的文件
21         },
22         xcLogConsole: {
23             type: \'console\'
24         }
25     },
26     categories: {
27         default: {
28             appenders: [\'xcLogFile\'],
29             level: \'all\'
30         },
31         xcLogFile: {
32             appenders: [\'xcLogFile\'],
33             level: \'all\'
34         },
35         xcLogConsole: {
36             appenders: [\'xcLogConsole\'],
37             level: log4js.levels.ALL
38         }
39     }
40 })
41 logger.level = \'debug\';

三、实现淘宝模拟登录

需要特别说明的是其中有一步跳过 selenium检测机制的代码,是十分重要的,如果没加将会无限循环验证码。

await page.evaluate(async () => {
  Object.defineProperty(navigator, \'webdriver\', { get: () => false })
})
const action = (async () => {
    // 定义浏览器无头模式、分辨率以及关闭沙盒模式等等。
    const browser = await puppeteer.launch({
        headless: false,
        defaultViewport: {
            width: 1300,
            height: 900
        },
        args: [\'--no-sandbox\', \'--disable-setuid-sandbox\']
    });
    // 新建页面
    var page = await browser.newPage();
    
    // 设定浏览器UserAgent
    await page.setUserAgent(\'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36\');
    // 跳转淘宝登录页
    await page.goto(\'https://login.taobao.com/member/login.jhtml?redirectURL=https%3A%2F%2Fwww.taobao.com%2F\');
    // 这一步十分重要,因为大部分大型网站都会对selenium机制进行检测,例如navigator.webdriver,navigator.languages等等。
    // 这一步就是把navigator的一些属性方法等等注入到浏览器中,绕过这些检测机制。
    await page.evaluate(async () => {
        Object.defineProperty(navigator, \'webdriver\', { get: () => false })
    })
    // 等待登录按钮加载完毕
    await page.$(\'.password-login\');
    // 自动输入账号,间隔频率随机数300毫秒内
    await page.type(\'input[, \'你的账号\', { delay: (parseInt(Math.random() * 300)) });
    // 自动输入密码,间隔频率随机数300毫秒内
    await page.type(\'input[, \'你的密码)\', { delay: (parseInt(Math.random() * 300)) })
    // 点击登录按钮
    await page.click(\'.password-login\');

    console.log(\'登录成功!\')

    // 获取cookies
    // cookies = await page.evaluate(() => document.cookie);
    // console.log(cookies);

    // 等待页面加载完毕
    await page.waitForNavigation()

    // 搜索关键字
    var keyName = \'连衣裙\';
    // 等待输入框加载完毕
    await page.$(\'#q\');
    // 输入关键字
    await page.type(\'input[, keyName, { delay: 300 });
    // 点击搜索按钮
    await page.click(\'.btn-search\');

    // 等待5秒后进入采集方法
    await page.waitFor(5000);

    console.log(\'采集开始...\');
    gather(page);
})();

四、实现淘宝数据采集

const gather = async (page) => {
    await page.waitFor(5000);

    // 等待下一页的按钮
    var nextBtn = await page.waitForSelector(\'#mainsrp-pager .next:not(.next-disabled)\');

    // 数据对象
    console.log(`进行第${nowPage}页数据采集...`)
    // 执行JS方法,将获取到的结果返回
    var content = await page.evaluate(() => {
        // 这里的:not(.item-ad)是指不需要推广的商品,如果需要则直接去掉即可。
        var itemList = document.querySelectorAll(\'#mainsrp-itemlist .J_MouserOnverReq:not(.item-ad)\');
        var dataList = [];
        for (var i = 0; i < itemList.length; i++) {
            var data = {};
            var item = itemList[i];
            // 商品名称
            data.name = item.querySelector(\'.title .J_ClickStat\').innerText;
            // 价格
            data.price = item.querySelector(\'.price strong\').innerText;
            // 地区
            data.area = item.querySelector(\'.location\').innerText;
            // 店铺名称
            data.shopName = item.querySelector(\'.shopname\').innerText;
            // 月销量
            data.sellerMonth = item.querySelector(\'.deal-cnt\').innerText || 0;
            if (data.sellerMonth) {
                data.sellerMonth = data.sellerMonth.replace(\'人付款\', \'\');
            }
            dataList.push(data);
        }
        return dataList;
    });
    console.log(`采集到${content.length}条数据!`);
    content.map(item => {
        // 这里本应是数据库操作,但是篇幅有限就直接写在日志里。
        logger.debug(JSON.stringify(item));
        resultList.push(item);
    })
    // 判断页码是否小于定义的采集最大页码,并且存在下一页按钮。
    if (nowPage < maxPage && nextBtn) {
        nowPage++;
        page.click(\'#mainsrp-pager .next\');
        gather(page);
    }
    else {
        console.log(`采集完毕,一共采集${maxPage}页,${resultList.length}条数据!`);
    }
}

代码都是实测没问题的,这里的maxPage也本应是动态获取的,但都比较简单,有问题的小伙伴可以留言问我,转载请注明出处,谢谢!