• 关于工作中爬取网站的一些思路记录


    声明:只是因为工作中需要,且基本不会对别人的网站构成什么不好的影响,做个思路记录!!!
    尊重网站所有者、控制请求频率、遵守网站规则、尊重个人隐私

    平常工作中难免会遇到需要爬取别人网站的需求,例如爬取兑换之类的流程,把接口爬取下来封装到项目中,这种一般不会对别人的网站有什么的影响,因为也是正常流程下单,例如下面的活动截图:
    在这里插入图片描述

    • 1、先理解需求,从需求和界面上看,很容易知道我们基本只需要获取两个接口,一个是发送验证码接口,一个是兑换接口。当然也可能存在前置接口,具体情况具体分析
    • 2、先不要急着分析网站源码,可以先尝试找个手机号发送验证码,看看请求了哪些接口,接口的参数是什么,返回的数据是什么,例如:
      在这里插入图片描述
      在这里插入图片描述
    • 3、从请求接口的参数的返回分析,这个网站的数据部分接口需要加密,部分不需要,如果不需要加密的接口,基本很容易就获取请求参数,这里可以大胆猜测accessId、reqId可能为uuid,reqMethod为请求的方法,encrypted表示接口请求参数是否需要加密,但是光这样看有点难以看出data和dataKey是什么加密算法加密而成,dataKey大概率是解密data的关键密钥。这里建议一般情况下看一下加密后的数据的长度,是不是一些比较特殊的长度,比如36位、512位之类的长度,可以根据这个长度大胆猜测是否是平常常用的算法。
    • 4、对网站有个大概的数据交互认识之后,我们就可以知道难点基本在数据加密这一块,这时候就可以来看源码了,这里的思路我一般是直接定位到“发送验证码”的按钮上,然后找到F12控制台元素的事件监听器的click事件上,直接进入相关的代码中,就可以看到这个按钮的点击事件的代码了。
    • 5、网站的代码都是经过编译的,且一般线上是不会直接开放源代码的,这种时候如何光看编译过后的代码,有时候是比较难以理解,这种时候就需要打断点,一步步调试了,看函数的参数和返回值,以及大概的处理过程。
    • 6、前面已经分析过了,难点就在加密算法上,经过代码分析找到加密部分的代码,如下图:
      在这里插入图片描述
      从这段的代码中,存在RSA和AES的字眼,且打断点都走了这些函数,可以看出这个网站是AES+RSA结合使用进行加密的。

    AES+RSA:使用RSA秘钥生成工具生成一对公钥(A)和私钥(B),前端保留A,后端保留B。
    前端发送数据时,先生成一串随机16位字符串作为AES的秘钥(C),然后使用A使用RSA算法对C进行加密,得到加密后的AES秘钥(D)。将要发送的数据(E)用C使用AES加密,得到密文(F)。将D和F一同发给后端进行处理。
    后端处理数据时,先用B对D使用RSA进行解密得到C,用C对F使用AES进行解密得到E,处理后得到结果G,再用C对G进行AES加密得到H,将H返回给前端。
    前端接收到H后用C进行解密,得到处理的结果G。

    根据AES和RSA的配合流程来看,这里没有直接返回RSA公钥,而是根据摸和指数来生成RSA公钥然后加密AES私钥,用AES私钥去加密数据进行传输,但是从前面请求参数来看,跟我们平常看到的AES和RSA字符串不一样,显然还做了相关的转换,dataKey字段只有数字和小写字母,平常看到的加密数据还有大写字母、+、/等。

    • 7、这里有一个比较难的地方就是“new no.RSAKeyPair(e,“”,t,2048)”这个对象,这个对象去加密随机生成的AES密钥生成dataKey字段,这里我也是看了半天,打断点半天,也难以理解,因为编译后很多变量是单个字母,且你需要换算成你自己项目的语言,如JAVA、Python等,有时候是比较难的,这时候你可能会想我能不能反编译一下js文件,那你可以尝试一下,比如找一些在线的网站上传文件看看能不能反编译,希望你能成功😂
    • 8、遇到难点无法继续下的时候怎么办呢?给大家提供一种思路,就是尝试直接拷贝它的js文件下来,然后在项目去执行它的函数,获取结果,我这里是使用JAVA、jdk1.8版本,由JavaScript引擎去执行js文件中的函数,根据上面的截图,我只需要执行Fp()函数即可拿到dataKey,和随机生成的AES私钥。
      当然你如果直接执行拷贝下来的js文件大概率是失败,会报各种错误,例如jdk1.8不支持ES6语法,你需要先转化成ES5语法格式(es6转es5在线网站:https://babeljs.io/repl,需要勾选左侧下面的"FORCE ALL TRANSFORMS"),其次你需要把不需要用到的代码都删掉,只留加密部分的代码,可以根据入口函数一步步找到所有需要的变量和函数等,可以根据报错一步步处理,这也是个小挑战~
    public static void main(String[] args) {
            // 创建JavaScript引擎
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("js");
    
            try {
                // 加载JavaScript文件
                engine.eval(new FileReader("C:\\Users\\win\\Desktop\\index2.js"));
                // 执行JavaScript函数
                Object result = engine.eval("Fp()");
                
                // 这里是我习惯用serr打印测试日志,因为标红比较显眼
                System.err.println("Function Result: " + JSONObject.toJSONString(result));
                // 拿到Fp()函数返回的dataKey等数据
                JSONObject object = JSONObject.parseObject(JSONObject.toJSONString(result));
                // 要加密的数据
                String content = "{\"appId\":\"xxxx\"}";
                System.err.println("加密前的数据:" + content);
                // 加密后的数据
                String encryptAES = encryptAES(content, object.getString("key"), object.getString("iv"));
                System.err.println("加密后的数据:" + encryptAES);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * AES加密
         * @param plaintext 要加密的数据
         * @param key
         * @param iv
         */
        public static String encryptAES(String plaintext, String key, String iv) {
            try {
                SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
                IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
    
                byte[] encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
                return bytesToHex(encryptedBytes).toUpperCase();
            } catch (Exception e) {
                log.error("AES加密异常,{}", e.getMessage());
                return "";
            }
        }
    
        private static String bytesToHex(byte[] bytes) {
            StringBuilder result = new StringBuilder();
            for (byte b : bytes) {
                result.append(String.format("%02X", b));
            }
            return result.toString();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 9、终极方案:无头浏览器!!!如果上面还不能解决你的问题,你可以尝试使用无头浏览器,模拟人工点击操作,当然这比接口慢很多,但你不需要考虑网站的加密和接口调用等等,按照人工操作的流程执行即可。注意,无头浏览器你可能需要考虑代理ip和请求头问题,尽可能模拟真实的操作,一直使用同一个ip和userAgent有可能会被检测到。
    /**
         * 获取浏览器驱动
         */
        private WebDriver getDriver(Param pm) {
            // 设置随机的设备 user-agent 和屏幕大小
            Map<String, Object> mobileEmulation = new HashMap<>();
            Map<String, Object> map = new HashMap<>();
            Dimension randomScreenSize = getRandomScreenSize();
            map.put("width", randomScreenSize.getWidth());
            map.put("height", randomScreenSize.getHeight());
            mobileEmulation.put("deviceMetrics", map);
            if (pm != null && Func.isNotBlank(pm.getUserAgent())) {
            	// 设置UA
                mobileEmulation.put("userAgent", pm.getUserAgent());
            }
    
            //谷歌浏览器设置
            ChromeOptions options = new ChromeOptions();
            // 隐藏浏览器、无头浏览器
            options.addArguments("-headless");
            options.setExperimentalOption("mobileEmulation", mobileEmulation);
            // 打开F12
    //        options.addArguments("--auto-open-devtools-for-tabs");
            // 启动无沙盒模式运行,以最高权限运行
            options.addArguments("--no-sandbox");
            // 不加载图片, 提升速度
            options.addArguments("blink-settings=imagesEnabled=false");
            
            options.addArguments("--disable-dev-shm-usage");
            // 禁用gpu渲染
            options.addArguments("--disable-gpu");
            // 禁用阻止弹出窗口
            options.addArguments("--disable-popup-blocking");
            // 禁用扩展
            options.addArguments("disable-extensions");
            // 禁用默认浏览器检查
            options.addArguments("no-default-browser-check");
            // 忽略不信任证书
            options.addArguments("–ignore-certificate-errors");
            // 禁用自动化控制特性
            options.addArguments("--disable-blink-features=AutomationControlled");
            // 禁用通知
            options.addArguments("--disable-notifications");
            // 禁用信息栏
            options.addArguments("--disable-infobars");
            // 禁用日志记录
            options.addArguments("--disable-logging");
    
            LoggingPreferences logPrefs = new LoggingPreferences();
            logPrefs.enable(LogType.PERFORMANCE, Level.ALL);
            options.setCapability("goog:loggingPrefs", logPrefs);
    
            // 禁用默认应用程序
            options.addArguments("--disable-default-apps");
            // 静音
            options.addArguments("--mute-audio");
            // 禁用自动化标志
            options.setExperimentalOption("excludeSwitches", new String[]{"enable-automation"});
    
            // 设置代理ip
            if (pm != null && Func.isNotBlank(pm.getProxyIp())) {
                String proxyAddress = pm.getProxyIp() + ":" + pm.getProxyPort();
    
                Proxy proxy = new Proxy();
                proxy.setHttpProxy(proxyAddress);
                proxy.setSslProxy(proxyAddress);
                options.setProxy(proxy);
            }
            return new ChromeDriver(options);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    谷歌浏览器驱动路径:https://googlechromelabs.github.io/chrome-for-testing/#stable

    public void handle(Param pm) {
            WebDriver driver = null;
            try {
                if (Func.isBlank(pm.getUserAgent())) {
                	// 这个工具类只是随机生成userAgent的工具类
                    pm.setUserAgent(UserAgentUtil.getUserAgent());
                }
    			// 谷歌浏览器驱动路径,注意驱动版本问题
                System.setProperty("webdriver.chrome.driver", "F:/chromedriver-win64/chromedriver.exe");
                driver = getDriver(pm);
    //            driver.manage().window().maximize();
                // 进入页面
                driver.get("https://xxx");
    
                JavascriptExecutor js = (JavascriptExecutor) driver;
                //添加cookie
                if (pm.getCookies() != null && pm.getCookies().size() > 0) {
                    setCookie(js, pm.getCookies());
                }
                // 隐式等待
                driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
    
              	// 其他相关处理代码
              	...
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (driver != null) {
                    driver.quit();
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
  • 相关阅读:
    72岁奶奶在抖音教物理火了,百万粉丝追更,网友:小时候要有这种老师就好了...
    Win11环境Mecab日语分词和词性分析以及动态库DLL not found问题(Python3.10)
    实测_GPT_Assistant
    java基础之Map集合
    财务RPA机器人真的能提高效率吗?
    用递归算法得到Java的树形结构
    shell第一个命令结果传入第二个命令删除
    免费使用ChatGPT 4.0 和 文心一言 4.0
    电子警察联网解决方案,让违法无处遁逃
    科技风杂志科技风杂志社科技风编辑部2022年第31期目录
  • 原文地址:https://blog.csdn.net/weixin_43810415/article/details/133375561