模拟登录+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多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

解决pandas无法读取csv文件数据的问题

《解决pandas无法读取csv文件数据的问题》本文讲述作者用Pandas读取CSV文件时因参数设置不当导致数据错位,通过调整delimiter和on_bad_lines参数最终解决问题,并强调正确参... 目录一、前言二、问题复现1. 问题2. 通过 on_bad_lines=‘warn’ 跳过异常数据3

python运用requests模拟浏览器发送请求过程

《python运用requests模拟浏览器发送请求过程》模拟浏览器请求可选用requests处理静态内容,selenium应对动态页面,playwright支持高级自动化,设置代理和超时参数,根据需... 目录使用requests库模拟浏览器请求使用selenium自动化浏览器操作使用playwright

Spring Security 单点登录与自动登录机制的实现原理

《SpringSecurity单点登录与自动登录机制的实现原理》本文探讨SpringSecurity实现单点登录(SSO)与自动登录机制,涵盖JWT跨系统认证、RememberMe持久化Token... 目录一、核心概念解析1.1 单点登录(SSO)1.2 自动登录(Remember Me)二、代码分析三、

C#监听txt文档获取新数据方式

《C#监听txt文档获取新数据方式》文章介绍通过监听txt文件获取最新数据,并实现开机自启动、禁用窗口关闭按钮、阻止Ctrl+C中断及防止程序退出等功能,代码整合于主函数中,供参考学习... 目录前言一、监听txt文档增加数据二、其他功能1. 设置开机自启动2. 禁止控制台窗口关闭按钮3. 阻止Ctrl +

java如何实现高并发场景下三级缓存的数据一致性

《java如何实现高并发场景下三级缓存的数据一致性》这篇文章主要为大家详细介绍了java如何实现高并发场景下三级缓存的数据一致性,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 下面代码是一个使用Java和Redisson实现的三级缓存服务,主要功能包括:1.缓存结构:本地缓存:使

在MySQL中实现冷热数据分离的方法及使用场景底层原理解析

《在MySQL中实现冷热数据分离的方法及使用场景底层原理解析》MySQL冷热数据分离通过分表/分区策略、数据归档和索引优化,将频繁访问的热数据与冷数据分开存储,提升查询效率并降低存储成本,适用于高并发... 目录实现冷热数据分离1. 分表策略2. 使用分区表3. 数据归档与迁移在mysql中实现冷热数据分

C#解析JSON数据全攻略指南

《C#解析JSON数据全攻略指南》这篇文章主要为大家详细介绍了使用C#解析JSON数据全攻略指南,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、为什么jsON是C#开发必修课?二、四步搞定网络JSON数据1. 获取数据 - HttpClient最佳实践2. 动态解析 - 快速

Ubuntu 24.04启用root图形登录的操作流程

《Ubuntu24.04启用root图形登录的操作流程》Ubuntu默认禁用root账户的图形与SSH登录,这是为了安全,但在某些场景你可能需要直接用root登录GNOME桌面,本文以Ubuntu2... 目录一、前言二、准备工作三、设置 root 密码四、启用图形界面 root 登录1. 修改 GDM 配

MyBatis-Plus通用中等、大量数据分批查询和处理方法

《MyBatis-Plus通用中等、大量数据分批查询和处理方法》文章介绍MyBatis-Plus分页查询处理,通过函数式接口与Lambda表达式实现通用逻辑,方法抽象但功能强大,建议扩展分批处理及流式... 目录函数式接口获取分页数据接口数据处理接口通用逻辑工具类使用方法简单查询自定义查询方法总结函数式接口