模拟登录+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

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

跨国公司撤出在华研发中心的启示:中国IT产业的挑战与机遇

近日,IBM中国宣布撤出在华的两大研发中心,这一决定在IT行业引发了广泛的讨论和关注。跨国公司在华研发中心的撤出,不仅对众多IT从业者的职业发展带来了直接的冲击,也引发了人们对全球化背景下中国IT产业竞争力和未来发展方向的深思。面对这一突如其来的变化,我们应如何看待跨国公司的决策?中国IT人才又该如何应对?中国IT产业将何去何从?本文将围绕这些问题展开探讨。 跨国公司撤出的背景与

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi