全自动爬虫,你爱了么

目标

知道什么是Selenium
会使用Selenium
了解反爬机制
会使用Selenium抵制反爬
Selenium反爬案例

1. 什么是Selenium

Selenium是一个用于Web应用程序测试的工具
它可以真实的模拟浏览器,为爬虫开辟了良好的环境

2. Selenium的基本使用

  1. 根据平台下载需要的webdirver(此处以chrome为例,注意两者版本号最好一致)

全自动爬虫,你爱了么_第1张图片

  1. 项目根目录安装selenium-webdirver包
    将下载的包进行解压,把.exe文件放入项目根目录
  2. 依据官方文档写一个demo
    官方文档地址:https://www.npmjs.com/package/selenium-webdriver
2.1 Selenium的小demo

此案例为自动打开百度,搜索“前端”

  1. 预览
    全自动爬虫,你爱了么_第2张图片
  2. 代码(注释清晰)
const { Builder, By, Key, until } = require('selenium-webdriver');
// 来自于官网测试demo
(async function example() {
    let driver = await new Builder().forBrowser('chrome').build();
    try {
        // 自动打开百度并搜索传智
        await driver.get('https://www.baidu.com/');
        // 找到元素(搜索框),写入关键字按下回车
        await driver.findElement(By.id('kw')).sendKeys('前端', Key.RETURN);
        // 验证是否搜索成功(时间为1s)
        // await driver.wait(until.titleIs('webdriver - Google Search'), 1000);
    } finally {
        // 执行完毕后退出
        // await driver.quit();
    }
})();

注意:全程自动,无需控制,我们可以用代码操控它来注册,登录,等等。

3. 反爬

3.1 反爬机制
  1. 有些网页会在服务器端做反爬机制,来抵制爬虫的骚扰
  2. 例如一些Ajax请求,在手动请求时,直接返回“操作频繁”
    全自动爬虫,你爱了么_第3张图片
  3. 在伪造请求头后,前几次可以拿到数据,多次请求后又提示:“操作频繁”(简单爬虫无法爬取)
  4. 服务器发觉IP恶意请求,把IP纳入黑名单
3.2 抵制反爬机制
  1. 使用代理IP,让服务器无法封杀全部(通过代理IP不断爬取数据)
  2. 使用selenium制造的爬虫

4. Selenium抵制反爬

首先selenium如何抵制反爬:反爬机制只是针对爬虫的,例如:检查请求头、检查IP的请求频率(过高则封杀)等手段,而selenium打开的就是一个自动化的浏览器,和用户正常使用并无差别,所以再厉害的反扒技术,也无法将它干掉。

4.1 SeleniumAPI的学习
  • getText() 获取文本内容
  • sendKeys() 发送一些按键指令
  • click() 点击元素
  • 对于SeleniumAPI介绍的肯定没有官方文档详尽,so,直接奉上文档:http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/,记住常用操作,就ok啦

5. Selenium反爬案例

此案例为爬取拉勾网某地区所有的职位信息(此信息有服务器反爬保护)

5.1 案例步骤
  • 第一阶段:
  1. 使用driver打开拉勾网主页
  2. 找到杭州站点击一下
  3. 输入前端,按回车
  4. 使用dirver.findElement()找到所有条目项,根据需求分析页面元素,获取其文本内容
  • 第二阶段(自动翻页):
  1. 定义初始页码
  2. 获取数据中,最大页码
  3. 开始获取数据时,打印当前页码数
  4. 获取完一页后,当前页码自增,然后判断是否达到最大页码
  5. 查找下一页按钮并点击,实现自动翻页
  6. 翻页后递归调用获取数据的函数
5.2 效果预览

5.3 代码
const { Builder, By, Key, until } = require('selenium-webdriver');
// 定义当前页码
let currentPage = 1;
// 定义总页码(后面加分号,避免立即执行函数报错)
let maxPage;

(async function start() {
    let driver = await new Builder().forBrowser('chrome').build();
    try {
        // 访问拉勾网
        await driver.get('https://www.lagou.com/');
        // 点击选择拉勾网弹窗内容(此处选择全国)
        await driver.findElement(By.css('#changeCityBox>ul.clearfix li:nth-child(3)')).click();
        // 在搜索框中输入前端,进行信息展示
        await driver.findElement(By.id('search_input')).sendKeys('前端', Key.RETURN);
        // 在搜索完页面抓取信息总页面
        maxPage = await driver.findElement(By.css('.totalNum')).getText()
        getData(driver)
    } catch (e) {
        console.log(e.message)
    }
})();

async function getData(driver) {
    // 打印当前页
    console.log(`当前页码为${currentPage}---总页码为${maxPage}`);
    // 定义while循环(类死循环)
    while (true) {
        let noError = true;
        try {
            // 获取岗位信息模块
            let items = await driver.findElements(By.css('.item_con_list .con_list_item'));
            // 通过for循环对信息逐条获取(原生for循环,避免开辟新函数使async冲突)
            // 定义空数组,接收循环得到的数据
            let infors = [];
            for (let i = 0; i < items.length; i++) {
                let item = items[i]
                let post = await item.findElement(By.css('.list_item_top .p_top h3')).getText()
                let place = await item.findElement(By.css('.list_item_top .p_top .add em')).getText()
                let time = await item.findElement(By.css('.list_item_top .p_top .format-time')).getText()
                let tags = await item.findElement(By.css('.list_item_bot .li_b_l')).getText()
                let postLink = await item.findElement(By.css('.list_item_top .p_top .position_link')).getAttribute('href')
                let money = await item.findElement(By.css('.list_item_top .p_bot .money')).getText()
                let experience = await item.findElement(By.css('.list_item_top .p_bot .li_b_l')).getText();
                // 把工作经验模块获得的文字中,多余的money信息替换为空
                experience = experience.replace(money, '')
                let companyName = await item.findElement(By.css('.list_item_top .company .company_name')).getText()
                let companyLink = await item.findElement(By.css('.list_item_top .company .company_name>a')).getAttribute('href')
                let otherInfo = await item.findElement(By.css('.list_item_top .company .industry')).getText()
                let welfare = await item.findElement(By.css('.list_item_bot .li_b_r')).getText();
                // 把获取的数据追加到数组(此处分享vscode快捷键(Shift+Alt+左键下移,选中多行),(shift+Ctrl+右箭头,选中所有属性),(粘贴完后,选中多行+end+逗号,ok))
                infors.push({
                    post,
                    place,
                    time,
                    tags,
                    postLink,
                    money,
                    experience,
                    companyName,
                    companyLink,
                    otherInfo,
                    welfare,
                })
            }
            console.log(infors);
            if (currentPage < maxPage) {
                // 页码自加
                currentPage++;
                // 点击下一页
                await driver.findElement(By.css('.pager_next')).click();
                // 调用自身继续爬取
                await getData(driver)
            }
        } catch (e) {
            // 此处抛出异常,让noError为false,继续循环爬取
            // 此处异常为页面元素请求在art-templete模板渲染之前(无法获取元素)
            // console.log(e.message)
            if (e) noError = false
        } finally {
            // 爬到最后一页,无异常抛出,noError为true,跳出循环
            if (noError) break
        }
    }
}

注意:在翻页时会碰到,页面元素请求在art-templete模板渲染之前(无法获取元素),导致报错(抛异常)。

解决方案:

  1. 休眠,让程序等待页面渲染,本身selenium效率就一般,此方法pass
  2. 无限询问法,类似死循环,利用try{}catch{}使程序跑起来,用循环无限询问,直到不符合条件退出循环

总结:利用selenium可以做好多自动化的事情,解放咱的双手

6. 最后

写在最后:爬虫神通广大,用途也非常广泛,主要目的是为了实现自动化程序,解放咱的双手。帮助我们自动获取一些数据,测试软件,甚至自动操作浏览器做很多事情,但使用爬虫时我们需要注意,把它用在得当的地方。同时,在爬取目标网站之前,建议大家浏览该网站的robots.txt文件(url后输入/robots.txt),以确保自己爬取的数据在对方允许的范围之内。

你可能感兴趣的:(Node,爬虫,node.js)