前言

大家好,今天给大家带来了一个博客文章分析系统,支持目前各大主流博客(CSDN,博客园,知乎,思否,掘金), 系统可以分析文章的内容, 评论,点赞,访问。具体内容请见下面视频。

解决了登录,环境异常等问题。这是一个完整的商用系统,不是demo,是定制开发的线上版本。 

视频演示

https://githubs.xyz/show/71f17896-1112-4246-b66c-453a4c309960... 

图片演示

图片

图片

图片
  
图片

系统功能描述

1. 博客配置管理

  • 支持多个博客平台的数据爬取(博客园、思否、CSDN、知乎、掘金)
  • 爬取文章标题,内容,评论,点赞量,访问量

2. 爬虫任务管理

  • 支持创建和管理博客爬取任务
  • 实时显示任务执行状态和进度
  • 提供任务日志查看功能

3. 数据分析功能

  • 展示文章访问量、点赞量、评论量等数据
  • 支持分析被封博客预警
  • 支持和上一次分析对比评论上升量,点赞上升量,访问上升量

4. 系统配置

  • 支持WebDriver配置管理
  • 提供元素选择器字典配置

使用说明

  • 首先在【博客设置】中配置需要爬取的博客信息
  • 在【驱动配置】中确保WebDriver配置正确
  • 通过【任务列表】创建新的爬取任务
  • 在【任务结果】中查看数据分析结果

部署搭建

将文件解压到D盘的根目录:

文件我已经整理清楚了,移步获取:

gitee( 典 ) C 〇 M/qiqi915/java01.git

图片

 按顺序启动1,2,3,4  bat  文件就可以了, 启动 “0.rundb.bat” , 如果启动失败,可能是3306端口被占用了,自行解决。

图片

 启动 “1.run_server.bat” , 启动会启动一个浏览器,不要动它。

图片

 启动 “2.run_font.bat” , 这个启动会等待一段时间,然后会自动关闭。启动 “3.run_font2.bat” :

图片

 访问:

http://localhost:4000/ 

技术实现(带源码)

源码我已经整理清楚了,移步获取:

gitee( 典 ) C 〇 M/qiqi915/java01.git

1. 动态配置元素定位

后端使用的是java版本的selenium  ,元素的定位信息都是存储到t_blog_element_settings表里面的:

图片

  这样的好处就是,页面更新后,只需要在后台修改下元素定位就可以了,不用改代码。 2. 优秀的隐藏机制selenium 运行时, 很容易被识别出来,我使用了如下代码来隐藏,下面是构造驱动的部分代码:

// 添加更多的浏览器指纹
Map<String, Object> prefs = new HashMap<>();
prefs.put("profile.default_content_settings.images", 2);
prefs.put("profile.managed_default_content_settings.javascript", 1);
prefs.put("credentials_enable_service", false);
prefs.put("profile.password_manager_enabled", false);
options.setExperimentalOption("prefs", prefs);

// 用来解决 登录智能 验证
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("source", "Object.defineProperty(navigator, \"webdriver\", {get: () => undefined})");
        driver.executeCdpCommand("Page.addScriptToEvaluateOnNewDocument",
                parameters);

 尤其是遇到知乎时,一打开就是环境异常!!!!我也是搞了好久,java还是解决不了这个问题,最终用一个脚本解决,脚本使用python写的,然后用java调用exe执行。下面是部分代码:

Process process = null;
        try {
            List<String> command = new ArrayList<>();
            command.add(exePath.toFile().getAbsolutePath());
            TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "开始执行脚本:" + exePath.toFile().getAbsolutePath());
            command.add(env.get("in"));

            TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "脚本参数:" + env.get("in"));

            ProcessBuilder processBuilder = new ProcessBuilder(command);
            processBuilder.redirectErrorStream(true); // 合并错误流到标准输出流
            process = processBuilder.start();
            // 创建单独的线程来读取进程输出
            final Process process1 = process;
            Thread outputThread = new Thread(() -> {

                System.out.println("知乎脚本输出线程开始: ");
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process1.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        System.out.println("知乎脚本输出: " + line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //
                System.out.println("==================输出流线程退出========================");
            });
            outputThread.start();
            TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "脚本执行中,请耐心等待...");

            // 等待进程完成,设置超时时间
            if (!process.waitFor(5, TimeUnit.HOURS)) {
                throw new InterruptedException("进程执行超时(" + 5 + "小时)");
            }
            // 检查进程退出码
            int exitCode = process.exitValue();
            if (exitCode != 0) {
                System.out.println("脚本执行退出码错误," + exitCode);
                setError("脚本执行退出码错误," + exitCode);

                TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "脚本内部异常,exitCode:" + exitCode);

                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
            setError("脚本执行异常");
            TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "脚本执行异常, msg:" + e.getMessage());

            return;
        } finally {
            if (process != null && process.isAlive()) {
                process.destroy(); // 确保进程被终止
                try {
                    // 等待一段时间让进程正常结束
                    if (!process.waitFor(5, TimeUnit.SECONDS)) {
                        process.destroyForcibly(); // 强制终止
                    }
                } catch (InterruptedException e) {
                    process.destroyForcibly(); // 强制终止
                }
            }
        }

3. 驱动支持配置

我设计了一个驱动设置表,可以支持用户自定义浏览器版本,不过一般用默认的就可以了。

CREATE TABLE `t_driver_settings` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `chrome_driver_path` varchar(2000) COLLATE utf8mb4_bin NOT NULL COMMENT '驱动路径',
  `chrome_exe_path` varchar(2000) COLLATE utf8mb4_bin NOT NULL COMMENT '浏览器EXE路径',
  `headless` int(11) DEFAULT '0' COMMENT '是否显示浏览器,0-显示 1-不显示',
  `driver_count` int(11) DEFAULT '0' COMMENT '驱动数量',
  `user_id` int(11) DEFAULT NULL COMMENT '用户id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='驱动设置表';

 
图片

4. 文章分析系统

 文章不仅仅可以显示文章数据列表,还可以自动进行分析,将被封的文章和评论上升量等分析出来:

图片

 实现就是通过对比上一次的任务结果数据进行计算。 5. 优秀的异常处理总所周知,页面元素会出现偶尔的查找错误,比如登录,我这里进行了重试和 每一段代码都是异常try,保证了流程的正常进行,和数据提取。比如下面部分代码:

@Override
    public boolean crawlLogin() {
        boolean success = false;
        final String loginUrl = settings.getLoginUrl();
        String userName = settings.getUserName();
        String pwd = settings.getPassword();
        int retryCount = 0;
        while (retryCount < 10) {
            try {
                retryCount++;
                TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "开始第" + retryCount + "次登录");

                if (!driver.get(loginUrl, (msg) -> setProgress("博客园登录页访问错误,url:" + loginUrl + ",进行重试"))) {
                    TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "页面访问错误,url:" + loginUrl);

                    continue;
                }
                setProgress("博客园访问登录页面成功,url:" + loginUrl);

                // 判断是否已经登录
                driver.sleep(1);

                WebElement loginOut = driver.findElement(By.id("loginOut"), null);
                if(loginOut != null){
                    //已经登录过 ,直接返回
                    success = true ;
                    TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "已经登录");
                    return success ;
                }

                // 输入用户名和密码
                WebElement usernameInput = driver.findElement(finder.findBy(1), msg -> setProgress("错误:" + finder.findAlias(1)));
                if (usernameInput == null) {
                    TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "元素查找错误,名称:" + finder.findAlias(1));

                    continue;
                }
                WebElement passwordInput = driver.findElement(finder.findBy(2), msg -> setProgress("错误:" + finder.findAlias(2)));
                if (passwordInput == null) {
                    TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "元素查找错误,名称:" + finder.findAlias(2));

                    continue;
                }
                usernameInput.clear();
                passwordInput.clear();

                usernameInput.sendKeys(userName);
                passwordInput.sendKeys(pwd);

                TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "输入用户名:" + userName);


                // 点击登录按钮
                WebElement loginButton = driver.findElement(finder.findBy(3), msg -> setProgress("错误:" + finder.findAlias(3)));
                if (loginButton == null) {
                    TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "元素查找错误,名称:" + finder.findAlias(3));
                    continue;
                }
                try{
                    loginButton.click();
                }catch(Exception e){
                    e.printStackTrace();
                    TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "元素点击错误,名称:" + finder.findAlias(3));
                    continue;

                }
                // 等待登录完成,检查是否存在登录成功的标志(比如用户头像)

                // 出现智能验证 //*[@id="rectMask"]
                try{
                    WebElement ver = driver.findElement(finder.findBy(4), msg -> setProgress("错误:" + finder.findAlias(4)));
                    if (passwordInput != null) {
                        ver.click();
                        getCrawlerListener().onProgress("点智能验证");
                        TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "点击智能验证成功");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                    TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "点击智能验证错误, msg:" + e.getMessage());
                }


                // https://www.cnblogs.com/
                WebElement user = driver.findElement(finder.findBy(5), msg -> setProgress("错误:" + finder.findAlias(5)));
                if (user == null) {
                    TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "主页找不到头像,需要重新登录");
                    continue;
                }
                success = true;
                getCrawlerListener().onProgress("登录成功");
                break;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if(!success){
            setError("登录失败");
            TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "登录失败");
        }else{
            TaskLogService.add(task.getTaskId(), "[" + alias() + "] " + "登录成功");
        }
        return success;
    }

结尾语

有问题联系小编。或者注意文章内容,能看到 响应的 你想要的 源代码信 息。


精品源码屋
6 声望17 粉丝

提供海量精品源码,业务范围:游戏,网站,工具等。 也支持招商代理