前言
大家好,今天给大家带来了一个博客文章分析系统,支持目前各大主流博客(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” :
访问:
技术实现(带源码)
源码我已经整理清楚了,移步获取:
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;
}
结尾语
有问题联系小编。或者注意文章内容,能看到 响应的 你想要的 源代码信 息。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。