模拟登录+cookie保持+数据爬取——中国铁塔的爬虫之旅

2023-11-01 20:51

本文主要是介绍模拟登录+cookie保持+数据爬取——中国铁塔的爬虫之旅,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        先简单交代一下背景:我表哥在铁塔公司做维护工人,一旦铁塔出现故障就会有专人派单然后被派到单的人就需要在一个小时之内接单,否则就算工作失误,会扣钱,但是有一点让人非常不理解,铁塔公司的电脑端虽然有一个单子提醒的功能,但是目前看来就是一个摆设,设置了也不会起到任何作用,换句话说,如果你想知道自己有没有单子可以接,就必须自己手动定时刷新,坐在电脑旁边天天点鼠标玩,这是一件非常痛苦的事情,而且有很多单子会在晚上出来,明摆着就是想让你接不到单子扣工资嘛。所以这就找上我了。。。

        那么我所需要做的就是模拟登录进入系统,然后查询有没有单子可以接,有的话提醒表哥,需求听着很简单,但是毕竟中国铁塔,爬这个网站还是费了一番功夫。

        话不多说,进入正题,首先我要到了网站的登陆地址:中国铁塔维护系统 ,用户名以及密码

        然后进入登录页面,开启f12,如下

        

        比较不幸,有验证码,不过问题不大,先看看验证码的验证机制是怎么样的,边输验证码边查看network

        

        额,很明显了,验证码的输入框绑定了onchange事件,每次发生变化都会请求后台,后来我查看了response响应体,发现验证码输入正确会返回1,然后出现 中国铁塔,欢迎您 的字样,错误会返回0。至于验证码获取就是一个固定的地址,返回不同的图片,查看页面元素可以看的很清楚,在这里就不多说了。

        到了这一步,已经明了了自己第一步需要完成的工作 获取验证码图片-》解析验证码-》检查验证码-》获取响应结果

        话不多说,上代码,代码写的比较赶,没怎么注意规范。。。在这里是直接把验证码下载到桌面肉眼识别然后手输的,也可以使用tess4j,识别率还可以

        HttpGet getCheckCode = new HttpGet(getCheckCodeUrl);CloseableHttpResponse responseGet = null;String desktopDir =               FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath();File imageFile = new File(desktopDir, "checkCode.jpg");try {responseGet = httpClient.execute(getCheckCode);FileOutputStream outputStream = new FileOutputStream(imageFile);HttpEntity entity = responseGet.getEntity();InputStream inputStream = entity.getContent();byte[] b = new byte[1024];int i = 0;while ((i = inputStream.read(b)) != -1) {outputStream.write(b, 0, i);}outputStream.flush();outputStream.close();EntityUtils.consume(entity);System.out.println("验证码获取成功,已经下载到桌面,请查看并输入验证码:");} catch (Exception e) {System.out.println("获取验证码失败!请重新运行程序!");return;} finally {try {responseGet.close();} catch (IOException e) {e.printStackTrace();}}Scanner scanner = new Scanner(System.in);String checkCode = scanner.next();HttpGet checkCodeGet = new HttpGet(checkCodeUrl + checkCode);checkCodeGet.addHeader("Accept", "text/plain, */*; q=0.01");checkCodeGet.addHeader("Referer", loginUrl);checkCodeGet.addHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36");checkCodeGet.addHeader("X-Requested-With", "XMLHttpRequest");CloseableHttpResponse responseCheckCode = null;try {responseCheckCode = httpClient.execute(checkCodeGet);HttpEntity entity = responseCheckCode.getEntity();String codeStatus = EntityUtils.toString(entity);// 如果为1 则说明验证码正确,否则错误if (!"1".equals(codeStatus)) {System.out.println("验证码解析失败!请重新运行");return;}System.out.println("验证码解析成功!");EntityUtils.consume(entity);} catch (Exception e) {e.printStackTrace();} finally {try {responseCheckCode.close();} catch (IOException e) {e.printStackTrace();}}

运行效果

j经过测试,没有问题,现在可以进行模拟登录了,首先在网站进行登录,查看network的请求

好吧,居然是302重定向,猜想应该是请求带了问号后面的东西导致的重定向,去掉之后再次登录,发现状态码变成了200,ok,就用它了,如下

 

查看一下请求体,发现是form-data,此外发现除了用户名和密码两个参数之外,还有其他三个参数

最后经过调试,发现这几个参数在访问登录页面的时候在页面上会动态生成到隐藏域中一起传到后台,那我们也要首先访问一下获取这几个动态参数,否则登录一直是失败的。

上代码

        // 全局请求设置RequestConfig globalConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).setSocketTimeout(50000).setConnectTimeout(50000).setConnectionRequestTimeout(50000).build();// 创建cookie store的本地实例CookieStore cookieStore = new BasicCookieStore();// 创建HttpClient上下文HttpClientContext context = HttpClientContext.create();context.setCookieStore(cookieStore);// 创建一个HttpClientCloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(globalConfig).setDefaultCookieStore(cookieStore).build();CloseableHttpResponse response = null;String lt = "";String execution = "";String _eventId = "";// 先访问一下登录页面HttpGet getLoginPage = new HttpGet(loginUrl);try {response = httpClient.execute(getLoginPage);HttpEntity entity = response.getEntity();System.out.println("获取登录所需参数中...");String str = EntityUtils.toString(entity);lt = regex("\"lt\" value=\"([^\"]*)\"", str)[0];execution = regex("\"execution\" value=\"([^\"]*)\"", str)[0];_eventId = regex("\"_eventId\" value=\"([^\"]*)\"", str)[0];EntityUtils.consume(entity);} catch (Exception e1) {e1.printStackTrace();} finally {try {response.close();} catch (IOException e) {e.printStackTrace();}}/*** 通过正则表达式获取内容* * @param regex 正则表达式* @param from  原字符串* @return*/public static String[] regex(String regex, String from) {Pattern pattern = Pattern.compile(regex);Matcher matcher = pattern.matcher(from);List<String> results = new ArrayList<String>();while (matcher.find()) {for (int i = 0; i < matcher.groupCount(); i++) {results.add(matcher.group(i + 1));}}return results.toArray(new String[] {});}

regex方法使用正则表达式可以帮助解析出页面对应参数的值,然后进行模拟登录,上代码

		CloseableHttpResponse responseLogin = null;HttpPost httppost = new HttpPost(loginUrl); // 登录地址List<NameValuePair> nvps = new ArrayList<NameValuePair>();nvps.add(new BasicNameValuePair("username", "用户名"));nvps.add(new BasicNameValuePair("password", "密码"));nvps.add(new BasicNameValuePair("lt", lt));nvps.add(new BasicNameValuePair("execution", execution));nvps.add(new BasicNameValuePair("_eventId", _eventId));nvps.add(new BasicNameValuePair("submit", "登录"));httppost.addHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");httppost.addHeader("Accept-Encoding", "gzip, deflate");httppost.addHeader("Connection", "keep-alive");httppost.addHeader("Host", "180.153.49.81:18989");httppost.addHeader("Origin", "http://180.153.49.81:18989");httppost.addHeader("Content-Type", "application/x-www-form-urlencoded");httppost.addHeader("Referer", loginUrl);httppost.addHeader("Upgrade-Insecure-Requests", "1");httppost.addHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36");HttpEntity reqEntity = new UrlEncodedFormEntity(nvps, Consts.UTF_8);httppost.setEntity(reqEntity);try {responseLogin = httpClient.execute(httppost);// 设置响应码,后面用int statusCode = responseLogin.getStatusLine().getStatusCode();if (statusCode != 200) {System.out.println("登录失败!请重新运行程序,如多次失败,请联系作者!");return;}System.out.println("登录成功,即将自动监控工单...");System.out.println(EntityUtils.toString(responseLogin.getEntity()));} catch (Exception e) {e.printStackTrace();} finally {try {responseLogin.close();} catch (IOException e) {e.printStackTrace();}}

 

运行,查看结果

非常感动,模拟登录成功!接下来访问查询界面,查询界面的url试了好一会,找了大半天,发现访问查询界面cookie会发生变化,所以直接访问不可取,查看其他请求,发现有一个请求是这样的:http://180.153.49.81:18989/SSO/login?service=http%3A%2F%2F180.153.49.216%3A9000%2Flayout%2Findex.xhtml

原来必须在请求路径后面加上查询的url才可以获取最新的cookie

shit,原来跨域了。。。访问之后获取到最新的cookie,然后带着最新的cookie去访问真正的查询界面,最后终于成功了

上代码,两分钟一刷,在这里是判断页面包不包含字符串来判断有没有单子,实际换上表哥的名字就行了,没有对页面进行深层解析,如果解析页面,可以使用jsoup,可以像原生js一样解析html,非常容易上手。值得一提的是,获取到最新的cookie之后,发现原来的cookie还在,需要去除之前的cookie,并且gc回收的时候cookiestore中的cookie有被回收的风险,需要在本地维护一份变量才行。

        List<Cookie> cookies = context.getCookieStore().getCookies();/** for (Cookie c : cookies) {* * cookie += c.getName() + "=" + c.getValue() + "; "; }*/// System.out.println(cookie);CloseableHttpResponse responseQueryWork = null;HttpGet httpGetQuery = new HttpGet(queryWorkUrl); // 查询地址String viewState = "";try {responseQueryWork = httpClient.execute(httpGetQuery);HttpEntity entity = responseQueryWork.getEntity();String str = EntityUtils.toString(entity);// System.out.println(str);viewState = regex("\"javax.faces.ViewState\" value=\"([^\"]*)\"", str)[0];// System.out.println(viewState);//获取现在的cookieList<Cookie> lastcookies = context.getCookieStore().getCookies();//移除之前的cookiefor (Cookie c : cookies) {lastcookies.remove(c);}// System.out.println(lastcookies);//清空cookiecontext.getCookieStore().clear();//设置最新的cookiefor (Cookie c : lastcookies) {context.getCookieStore().addCookie(c);// 保存到当前的cookie 避免gc引起cookie丢失//savedCookies 为全局静态变量savedCookies.add(c);}/** String lastCookie = ""; for (Cookie c :* context.getCookieStore().getCookies()) {* * lastCookie += c.getName() + "=" + c.getValue() + "; "; }*/// System.out.println(lastCookie);EntityUtils.consume(entity);} catch (Exception e) {e.printStackTrace();} finally {try {responseQueryWork.close();} catch (IOException e) {e.printStackTrace();}}String lastViewState = viewState;Timer checkTimer = new Timer();System.out.println("==开始监控工单,请一定不要关闭窗口,如有工单会有音乐提醒!!两分钟一刷,尽量把电脑音量调大,以免听不见!!==");// 两分钟一刷checkTimer.schedule(new TimerTask() {@Overridepublic void run() {// 判断当前的cookie是否被回收 如果被回收,把之前储存的cookie加入if (context.getCookieStore().getCookies().size() == 0) {for (Cookie c : savedCookies) {context.getCookieStore().addCookie(c);}}CloseableHttpResponse lastQueryResponse = null;HttpPost lastPost = new HttpPost(lastQueryUrl); // 查询地址List<NameValuePair> lastnvps = new ArrayList<NameValuePair>();lastnvps.add(new BasicNameValuePair("AJAXREQUEST", "_viewRoot"));lastnvps.add(new BasicNameValuePair("queryForm", "queryForm"));lastnvps.add(new BasicNameValuePair("queryForm:msg", "0"));lastnvps.add(new BasicNameValuePair("queryForm:queryBillId", ""));lastnvps.add(new BasicNameValuePair("queryForm:queryBillSn", ""));lastnvps.add(new BasicNameValuePair("queryForm:isQueryHis", "N"));lastnvps.add(new BasicNameValuePair("queryForm:queryStationId", ""));lastnvps.add(new BasicNameValuePair("queryForm:deviceidText", ""));lastnvps.add(new BasicNameValuePair("queryForm:addOrEditAreaNameId", ""));lastnvps.add(new BasicNameValuePair("queryForm:aid", ""));lastnvps.add(new BasicNameValuePair("queryForm:queryUnitId", ""));lastnvps.add(new BasicNameValuePair("queryForm:j_id48", ""));lastnvps.add(new BasicNameValuePair("queryForm:queryDWCompany", ""));lastnvps.add(new BasicNameValuePair("queryForm:queryDWCompanyName", ""));lastnvps.add(new BasicNameValuePair("queryForm:queryAlarmId", ""));lastnvps.add(new BasicNameValuePair("queryForm:queryAlarmName", ""));lastnvps.add(new BasicNameValuePair("queryForm:j_id58", ""));lastnvps.add(new BasicNameValuePair("queryForm:starttimeInputDate", "2019-06-20 15:00"));lastnvps.add(new BasicNameValuePair("queryForm:starttimeInputCurrentDate", "06/2019"));lastnvps.add(new BasicNameValuePair("queryForm:endtimeInputDate", ""));lastnvps.add(new BasicNameValuePair("queryForm:endtimeInputCurrentDate", "06/2019"));lastnvps.add(new BasicNameValuePair("queryForm:revertstarttimeInputDate", ""));lastnvps.add(new BasicNameValuePair("queryForm:revertstarttimeInputCurrentDate", "06/2019"));lastnvps.add(new BasicNameValuePair("queryForm:revertendtimeInputDate", ""));lastnvps.add(new BasicNameValuePair("queryForm:revertendtimeInputCurrentDate", "06/2019"));lastnvps.add(new BasicNameValuePair("queryForm:dealstarttimeInputDate", ""));lastnvps.add(new BasicNameValuePair("queryForm:dealstarttimeInputCurrentDate", "06/2019"));lastnvps.add(new BasicNameValuePair("queryForm:dealendtimeInputDate", ""));lastnvps.add(new BasicNameValuePair("queryForm:dealendtimeInputCurrentDate", "06/2019"));lastnvps.add(new BasicNameValuePair("queryForm:sitesource_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:querystationstatus_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:billStatus_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:faultSrc_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:isHasten_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:alarmlevel_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:faultDevType_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:isOverTime_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:isReplyOver_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:subOperatorHid_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:operatorLevel_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:turnSend_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:sortSelect_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:faultTypeId_hiddenValue", ""));lastnvps.add(new BasicNameValuePair("queryForm:queryCrewVillageId", ""));lastnvps.add(new BasicNameValuePair("queryForm:hideFlag", ""));lastnvps.add(new BasicNameValuePair("queryForm:queryCrewVillageName", ""));lastnvps.add(new BasicNameValuePair("queryForm:refreshTime", ""));lastnvps.add(new BasicNameValuePair("queryForm:panelOpenedState", ""));lastnvps.add(new BasicNameValuePair("javax.faces.ViewState", lastViewState));lastnvps.add(new BasicNameValuePair("queryForm:j_id133", "queryForm:j_id133"));lastnvps.add(new BasicNameValuePair("AJAX:EVENTS_COUNT", "1"));lastPost.addHeader("Content-Type", "application/x-www-form-urlencoded");lastPost.addHeader("Referer", lastQueryUrl);HttpEntity reqEntityQuery = new UrlEncodedFormEntity(lastnvps, Consts.UTF_8);lastPost.setEntity(reqEntityQuery);try {lastQueryResponse = httpClient.execute(lastPost);// System.out.println(lastQueryResponse.getStatusLine().getStatusCode());HttpEntity lastEntity = lastQueryResponse.getEntity();// System.out.println(EntityUtils.toString(lastEntity));String lastStr = EntityUtils.toString(lastEntity);//如果查询成功,页面必然有包站人这几个字if (lastStr.contains("包站人")) {System.out.println("有单子来了!!快去接单吧!!音乐连续放三次会自动停止。。进入下轮检查!");// 连续放3次/** for (int i = 0; i < 3; i++) { playMusic(); }*/}EntityUtils.consume(lastEntity);} catch (IOException e) {e.printStackTrace();} finally {try {lastQueryResponse.close();} catch (IOException e) {e.printStackTrace();}}}}, 0, 120000);public static void playMusic() {try {String desktopDir = FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath();FileInputStream inputStream = new FileInputStream(desktopDir + "\\tip.mp3");Player player = new Player(new BufferedInputStream(inputStream));player.play();} catch (Exception e) {e.printStackTrace();}}

 

总体运行效果:

大功告成,其实可以做成图形界面,但是有点麻烦,这样已经足够了,哈哈,打个jar包写个bat双击运行即可。

这篇关于模拟登录+cookie保持+数据爬取——中国铁塔的爬虫之旅的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/325668

相关文章

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

MyBatis-plus处理存储json数据过程

《MyBatis-plus处理存储json数据过程》文章介绍MyBatis-Plus3.4.21处理对象与集合的差异:对象可用内置Handler配合autoResultMap,集合需自定义处理器继承F... 目录1、如果是对象2、如果需要转换的是List集合总结对象和集合分两种情况处理,目前我用的MP的版本

JWT + 拦截器实现无状态登录系统

《JWT+拦截器实现无状态登录系统》JWT(JSONWebToken)提供了一种无状态的解决方案:用户登录后,服务器返回一个Token,后续请求携带该Token即可完成身份验证,无需服务器存储会话... 目录✅ 引言 一、JWT 是什么? 二、技术选型 三、项目结构 四、核心代码实现4.1 添加依赖(pom

GSON框架下将百度天气JSON数据转JavaBean

《GSON框架下将百度天气JSON数据转JavaBean》这篇文章主要为大家详细介绍了如何在GSON框架下实现将百度天气JSON数据转JavaBean,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录前言一、百度天气jsON1、请求参数2、返回参数3、属性映射二、GSON属性映射实战1、类对象映

C# LiteDB处理时间序列数据的高性能解决方案

《C#LiteDB处理时间序列数据的高性能解决方案》LiteDB作为.NET生态下的轻量级嵌入式NoSQL数据库,一直是时间序列处理的优选方案,本文将为大家大家简单介绍一下LiteDB处理时间序列数... 目录为什么选择LiteDB处理时间序列数据第一章:LiteDB时间序列数据模型设计1.1 核心设计原则

Java+AI驱动实现PDF文件数据提取与解析

《Java+AI驱动实现PDF文件数据提取与解析》本文将和大家分享一套基于AI的体检报告智能评估方案,详细介绍从PDF上传、内容提取到AI分析、数据存储的全流程自动化实现方法,感兴趣的可以了解下... 目录一、核心流程:从上传到评估的完整链路二、第一步:解析 PDF,提取体检报告内容1. 引入依赖2. 封装

MySQL中查询和展示LONGBLOB类型数据的技巧总结

《MySQL中查询和展示LONGBLOB类型数据的技巧总结》在MySQL中LONGBLOB是一种二进制大对象(BLOB)数据类型,用于存储大量的二进制数据,:本文主要介绍MySQL中查询和展示LO... 目录前言1. 查询 LONGBLOB 数据的大小2. 查询并展示 LONGBLOB 数据2.1 转换为十

使用SpringBoot+InfluxDB实现高效数据存储与查询

《使用SpringBoot+InfluxDB实现高效数据存储与查询》InfluxDB是一个开源的时间序列数据库,特别适合处理带有时间戳的监控数据、指标数据等,下面详细介绍如何在SpringBoot项目... 目录1、项目介绍2、 InfluxDB 介绍3、Spring Boot 配置 InfluxDB4、I