点击上方Java资料社区,选择“置顶公众号”
优质文章,第一时间送达
最近迷上了爬虫技术,经过学习了解爬虫基本流程:
1.分析接口、页面,及请求参数、数据渲染逻辑
2.通过发起Http工具发起接口、页面请求
3.得到响应之后对响应的数据进行筛选
4.筛选出的数据进行入库
随着业务慢慢的深入,后来发现通过HttpClient做爬虫并不方便,例如碰到某一个需要登录才能进入的网站,使用HttpClient则不太方便,想要模拟登录必须先让用户登录之后获取到真实登录用户的cookie,将cookie设置到请求Cookie当中才能解决,显然这并不是很好的解决方案.
在这段爬虫学习过程中发现一款很有意思的框架,这款框架叫作selenium,做自动化测试的同学应该比较了解,下面可以先观看以下我用selenium写的一个小案例:
看完以上视频,下面了解如何实现的
Selenium介绍该框架是一个用于web端测试工具,直接运行在浏览器中,使用过程中与真人操作类似,支持兼容市场各大主流浏览器,通过编写程序可以简化web端上的重复操作,从而让程序去替你工作,当然!selenium主要并不是做爬虫,本文只是利用该框架做一个简单的爬虫应用
1 Selenium环境安装因为selenium是运行在浏览器中,所以首先必须安装浏览器驱动,这里我使用的谷歌浏览器,下载驱动需要根据浏览器版本安装不同的驱动版本,否则会出现异常
谷歌下载地址:
http://npm.taobao.org/mirrors/chromedriver/
火狐下载地址:
https://github.com/mozilla/geckodriver/releases
IE下载地址:
http://selenium-release.storage.googleapis.com/index.html
我的谷歌是80.0.3987.149版本,这里我选择的80.0.3987.16的谷歌驱动,最后可以根据自己的系统环境下载不同的版本.
2 案例需求1.实现拉勾网自动登录(验证码需要人工识别)
2.自动筛选并搜索指定职位
3.抓取筛选搜索后的职位信息及分页处理
3 搭建Java项目项目环境:maven3.3.1+jdk1.8+IDEA2019.1
maven依赖:
<?xml version="1.0" encoding="UTF-8"?> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 com.java134 selenium_Lagou 1.0-SNAPSHOT org.seleniumhq.selenium selenium-server 3.141.59
1)将下载下来的谷歌驱动放入项目根路径下(也可以自定义,用于项目启动加载驱动)
2)实现需求1
首先分析拉勾网登录页面为https://passport.lagou.com/login/login.html
再分析拉勾网用户名、密码、登录按钮结构
代码实现:
/** * @ClassName LagouJob * @Description * @Author 公众号:Java资料社区-码农小龙 * @Date 2020/3/27 16:51 **/public class LagouJob { public static void run() throws InterruptedException { //设置谷歌驱动系统变量 System.setProperty("webdriver.chrome.driver",System.getProperty("user.dir")+"\\chromedriver.exe"); //创建浏览器驱动实例 WebDriver driver = new ChromeDriver(); //打开指定网址(拉勾网登录网址) driver.get("https://passport.lagou.com/login/login.html"); //定位用户名框 WebElement userName=driver.findElement(By.xpath("//div[@data-propertyname='username']/input")); //输入手机号 userName.sendKeys("1767xxxxxxx"); //定位密码框 WebElement userPwd=driver.findElement(By.xpath("//div[@data-propertyname='password']/input")); //输入密码 userPwd.sendKeys("xxxxxxxx"); //定位提交按钮 WebElement btnSubmit=driver.findElement(By.xpath("//div[@data-propertyname='submit']/input")); //点击提交按钮 btnSubmit.click(); }}
Api介绍:
1.WebDriver 创建一个浏览器实例,根据不同的驱动创建不同的实现
WebDriver driver = new ChromeDriver(); //Chrome浏览器WebDriver driver = new FirefoxDriver(); //Firefox浏览器WebDriver driver = new EdgeDriver(); //Edge浏览器WebDriver driver = new InternetExplorerDriver(); // Internet Explorer浏览器WebDriver driver = new OperaDriver(); //Opera浏览器WebDriver driver = new PhantomJSDriver(); //PhantomJS
2.get:属于WebDriver下的方法,用于第一次打开浏览器访问的指定网址
3.findElement:属于WebDriver下的方法,用于查找页面中的元素,需要配合By使用
4.By:用于定位页面中的元素,类似于Jquery,提供了8种定位方式
id
name
class name
tag name
link text
partial link text
xpath
css selector
对应Api方法:
//这里只简单介绍几个常用的,如果感兴趣可以自行了解findElement(By.id("id"))//通过元素的id名进行定位,类似于$("#id")findElement(By.name("name"))//通过元素name名定位findElement(By.className("class"))//通过元素的id名进行定位,类似于$(".id")//分为两种 1.绝对定位 2.相对定位//绝对定位,这里的下标是从1开始的,用于区分子元素的层级findElement(By.xpath("/html/body/div[1]/a[2]/"))//相对定位,这里的@后面可以用于区分元素的属性值,也可以设置成class name或者自定义属性等等findElement(By.xpath("//a[@href='www.baidu.com']"))findElement(By.tagName())findElement(By.linkText())findElement(By.partialLinkText())findElement(By.cssSelector())
5.WebElement:用于接收findElement查找的元素
6.sendKeys:属于WebElement下的方法,用于向文本框中输入指定字符
7.click:属于WebElement下的方法,用于触发元素的click事件
运行结果
3)实现需求2
登录成功之后会跳转到拉勾首页,由于我们需求是要根据条件搜索职位,所以先从首页搜索入口入手分析,与上述登录流程一致,定位文本框-->输入搜索条件-->点击搜索按钮;
问题1:由于上述登录有可能会出现人机验证,selenium对人机验证暂时没法很好的识别,所以需要设置线程睡眠10秒提供人工识别验证码及登录之后跳转处理时间
问题2:搜索跳转之后有可能会出现广告模态框,如果不关闭模态框会影响程序的下一步运行,所以需要分析广告模态框是否存在存在则需要关闭
需求2搜索实现代码
//省略需求1实现代码...//接着需求1代码块继续//解决问题1:停止运行10秒,提供人工选择验证码及登录跳转时间Thread.sleep(10000);//输入搜索职位名称driver.findElement(By.id("search_input")).sendKeys("Java");//点击搜索按钮driver.findElement(By.id("search_button")).click(); //解决问题2:判断广告框是否存在,取消模态框广告if(driver.findElement(By.className("body-container")).isDisplayed()){ driver.findElement(By.className("body-btn")).click(); }
搜索跳转之后开始分析筛选条件模块,查看源码之后分析到如果要选择条件筛选,必须点击相应的筛选条件文字;
需求2筛选条件实现代码:
//省略上述代码...//筛选条件String gzdd="上海";String gzjy="3年及以下";String xl="大专";String rzjd="不限";String gsgm="不限";String hyly="不限";//点击指定地区条件driver.findElement(By.xpath("//div[@class='other-hot-city']/div/a[contains(text(),'" + gzdd + "')]")).click();//点击指定工作经验条件driver.findElement(By.xpath("//li[@class='multi-chosen']/span[contains(text(),'工作经验')]")). findElement(By.xpath("../a[contains(text(),'" + gzjy + "')]")).click();//点击指定学历条件driver.findElement(By.xpath("//li[@class='multi-chosen']/span[contains(text(),'学历要求')]")). findElement(By.xpath("../a[contains(text(),'" + xl + "')]")).click();//点击指定融资阶段条件driver.findElement(By.xpath("//li[@class='multi-chosen']/span[contains(text(),'融资阶段')]")). findElement(By.xpath("../a[contains(text(),'" + rzjd + "')]")).click();//点击指定公司规模条件driver.findElement(By.xpath("//li[@class='multi-chosen']/span[contains(text(),'公司规模')]")). findElement(By.xpath("../a[contains(text(),'" + gsgm + "')]")).click();点击指定行业领域条件driver.findElement(By.xpath("//li[@class='multi-chosen']/span[contains(text(),'行业领域')]")). findElement(By.xpath("../a[contains(text(),'" + hyly + "')]")).click();
xpath定位中contains解释:对于以上对筛选模块代码的分析,由于多个a标签的class、name名都一致,无法定位到单个元素,所以用到contains方法直接定位元素中的text内容,根据指定的文字内容匹配相关元素;
运行结果
4)实现需求3
条件筛选之后会加载所有与条件匹配的职位数据,接下来开始分析职位列表渲染的html结构,从而爬取关键数据,下图分析出每一条职位数据,都是由一个li标签组成;
需求3抓取职位信息实现代码:
//省略上述代码...boolean flag=true;//是否有下一页Double sum=0.0;//总工资int count=0;//职位总数//循环分页爬取数据,直到最后一页while (flag){//设置时间等待网页渲染完毕 Thread.sleep(1500); WebElement nextBtn=null; try { //定位底部分页栏 nextBtn=driver.findElement(By.className("pager_container")).findElement(By.className("pager_next")); //判断是否包含pager_next_disabled样式,包含则没有下一页 flag=!nextBtn.getAttribute("class").contains("pager_next_disabled"); }catch (Exception e){ //异常则代表只有一页,不存在上面的class名,则无法定位到 flag=false; } try { //定位职位数据列表 List li = driver.findElements(By.xpath("//ul[@class='item_con_list']/li")); if (li.size() > 0) { for (WebElement item : li) { //标题地址 String address = item.findElement(By.tagName("em")).getText(); //公司行业 String industry = item.findElement(By.className("industry")).getText(); //企业福利 String welfare = item.findElement(By.className("li_b_r")).getText(); //详情网址 String url = item.findElement(By.tagName("a")).getAttribute("href"); //职位标题 String name = item.getAttribute("data-positionname"); //工资 String money = item.getAttribute("data-salary"); //公司名 String company = item.getAttribute("data-company"); //切割最低工资与最高工资,用于计算中等工资 例:10k-20k String arr[] = money.replaceAll("k", "").split("-"); //职位平均工资=职位最低工资+职位最高工资/2 Double avg = (Double.valueOf(arr[0]) + Double.valueOf(arr[1])) / 2; sum += avg;//加入总工资中 count++;//加入职位总数中 System.out.println(company + "--" + name + "[" + address + "](" + money + ")"); System.out.println("公司行业:" + industry + "---福利:" + welfare); System.out.println("URL:" + url); System.out.println("----------------------------------------------------"); } //下一页按钮存在并且非禁用的情况下则点击下一页 if (flag) { nextBtn.click(); } } else { System.out.println("暂时没有符合该搜索条件的职位!"); } }catch (Exception e){ System.out.println("------------出现异常----------------"); System.out.println("本次根据指定条件爬取岗位"+count+"个,平均工资为"+sum/count+"k."); }}System.out.println("本次根据指定条件爬取岗位"+count+"个,平均工资为"+sum/count+"k.");
上述代码解决分页逻辑
经过分析:
1.如果下一页按钮class中存在pager_next_disabled样式则代表没有下一页数据,则停止while循环翻页;
2.通过try-catch包裹下一页按钮代码块,如果首次定位翻页按钮抛出异常则代表Html中没有该按钮,即代表没有下一页数据,停止while循环
selenium远远不止文中所提到仅有的功能,比如浏览器控制、模拟鼠标操作、模拟键盘操作、获取断言信息、多窗口切换、调用JS代码、Cookie操作、截图等待,感兴趣在该网址学习[http://www.testclass.net/selenium_java/]
4 写在最后无论selenium是做自动化测试还是做爬虫,能够根据自身需要解决痛点则是优秀的框架,对于selenium而言我个人认为可以大胆的发挥想象,其实能够替代很多web端人工操作,例如后台信息审核很多简单的操作完全不需要人工审核,还可以结合我上述代码整合多个招聘平台进行简历一键投递!
最全面试题:入口链接
源码:关注公众号回复【拉钩】
如果对你有帮助,请转发给更多人,顺便下方点个在看!
点一下“ 在看”,好文和朋友一起看~