最近在学Kettle,今天自己实践用Kettle获取html网页数据的时候,写java的时候遇到一下困难,整理一下。
目录
2.在“网络”中,任意点击一个元素,在“标头”中,下拉找到“Request Headers”
1.在“核心对象”-“查询”中找到“HTTP client”控件
2.在“类与代码片段”中点击“Code Snippits”,“Common use”,“Main”插入main方法
一、版本信息
Kettle(Pentaho Data Integration) | 8.2 |
Mysql | 8.0 |
JDK | 1.8 |
数据库管理系统 | Navicat |
浏览器 | Edge |
目标网页 | 豆瓣网 |
注意:mysql中的数据库和数据表需要提前创建
use kettle;
create table html(
contents VARCHAR(255));
二、获取浏览器的User-Agent和Accept
学过爬虫的应该都知道想要爬取网页数据,单靠一个URL只能完成一些简单网站的爬取任务,由于网页的反爬机制,需要User-Agent和Accept将程序伪装成浏览器进行网页的访问,Kettle也是这样。(User-Agent取决于电脑系统、浏览器类别等等因素,所以每个人的User-Agent是不同的)
以豆瓣网为例:
1.进入豆瓣网,右键选择检查或者F12进入开发者工具
2.在“网络”中,任意点击一个元素,在“标头”中,下拉找到“Request Headers”
将User-Agent和Accept的内容复制下来
三、配置“自定义常量数据”控件
1.回到Kettle中,新建转换
2.在“核心对象”-“输入”中找到“自定义常量数据”控件
3.“元数据”对话框中,定义字段常量
4.“数据”对话框中,为字段常量赋值
- filename: 要抽取网页的URL
- User-Agent: 浏览器中的User-Agent
- Accept: 浏览器中的Accept
以豆瓣网为例:
- filename: https://movie.douban.com/chart
- Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
- User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0
注意:在此处指定User-Agent和Accept之后可以直接使用,否则要在Java代码中定义
四、配置“HTTP client”控件
1.在“核心对象”-“查询”中找到“HTTP client”控件
2.“General”对话框
- 勾选“从字段中获取URL”
- “URL字段名”: “filename”
- “Encoding(empty means standard)”: “UTF-8”
- “结果字段名”: “result”
3.“Fields”对话框
五、配置“Java代码”控件
1.在“核心对象”-“脚本”中找到“Java代码”控件
2.在“类与代码片段”中点击“Code Snippits”,“Common use”,“Main”插入main方法
3.将代码中的提示代码删除,替换为下方的代码
注意:
代码中的变量“url”、“userName”、“userPwd”、Class.forName()的值这四个位置,需要替换成自己的
这段代码可能只适合mysql8.0之前的版本,具体原因在代码下方,不想看原因的可以直接跳到第4小节
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.mysql.jdbc.Connection;
import com.mysql.jdbc.PreparedStatement;
private String result;
private String contents;
private Connection connection = null;
public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException,SQLException{
if (first) {
result=getParameter("result");
first = false;
}
Object[] r = getRow();
if (r == null) {
setOutputDone();
return false;
}
Object[] outputRow = createOutputRow(r, data.outputRowMeta.size());
String foobar = get(Fields.In, result).getString(r);
String pattern ="<a[^>]*href(\\\"([^\\\"]*)\\\"|\\'([^\\']*)\\'|([^\\\\s>]*))[^>]*>(.*?)</a>";
Pattern patterns = Pattern.compile(pattern);
Matcher m = patterns.matcher(foobar);
while(m.find()){
get(Fields.Out, "contents").setValue(outputRow, m.group().replaceAll("<[^>]*>",""));
String url = "jdbc:mysql://localhost:3306/kettle?useUnicode=true&
characterEncoding=UTF-8&useSSL=false";
String userName = "root";
String userPwd = "147369";
try{
// 加载驱动程序
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接对象
connection= (Connection) DriverManager.getConnection(url, userName, userPwd);
} catch (Exception e) {
e.printStackTrace();
}
//要执行的SQL语句
String sql="insert into html (contents) values (?);";
PreparedStatement stat = (PreparedStatement) connection.prepareStatement(sql);
contents=m.group().replaceAll("<[^>]*>","");
stat.setString(1, contents);
//3.ResultSet类,用来存放获取的结果集!!
stat.executeUpdate();
putRow(data.outputRowMeta, outputRow);
}
return true;
}
这一段代码是书上的代码,鄙人跑这个代码会报错,报错日志如下
2024/03/24 14:31:05 - Java 代码.0 - ERROR (version 8.2.0.0-342, build 8.2.0.0-342 from 2018-11-14 10.30.55 by buildguy) : Error initializing UserDefinedJavaClass:
2024/03/24 14:31:05 - Java 代码.0 - ERROR (version 8.2.0.0-342, build 8.2.0.0-342 from 2018-11-14 10.30.55 by buildguy) : org.codehaus.commons.compiler.CompileException: Line 10, Column 8: Imported class "com.mysql.jdbc.Connection" could not be loaded
2024/03/24 14:31:05 - Java 代码.0 - ERROR (version 8.2.0.0-342, build 8.2.0.0-342 from 2018-11-14 10.30.55 by buildguy) : 错误初始化步骤[Java 代码]
2024/03/24 14:31:05 - 4-2-1 - ERROR (version 8.2.0.0-342, build 8.2.0.0-342 from 2018-11-14 10.30.55 by buildguy) : 步骤 [Java 代码.0] 初始化失败!
2024/03/24 14:31:05 - Spoon - ERROR (version 8.2.0.0-342, build 8.2.0.0-342 from 2018-11-14 10.30.55 by buildguy) : 4-2-1: preparing transformation execution failed
2024/03/24 14:31:05 - Spoon - ERROR (version 8.2.0.0-342, build 8.2.0.0-342 from 2018-11-14 10.30.55 by buildguy) : org.pentaho.di.core.exception.KettleException:
2024/03/24 14:31:05 - Spoon - 无法初始化至少一个步骤. 执行无法开始!
2024/03/24 14:31:05 - Spoon -
2024/03/24 14:31:05 - Spoon -
2024/03/24 14:31:05 - Spoon - at org.pentaho.di.trans.Trans.prepareExecution(Trans.java:1272)
2024/03/24 14:31:05 - Spoon - at org.pentaho.di.ui.spoon.trans.TransGraph$30.run(TransGraph.java:4287)
2024/03/24 14:31:05 - Spoon - at java.lang.Thread.run(Thread.java:750)
2024/03/24 14:31:05 - 4-2-1 - ERROR (version 8.2.0.0-342, build 8.2.0.0-342 from 2018-11-14 10.30.55 by buildguy) : 错误被检测到!
2024/03/24 14:31:05 - 4-2-1 - ERROR (version 8.2.0.0-342, build 8.2.0.0-342 from 2018-11-14 10.30.55 by buildguy) : 错误被检测到!
反过来找原因,原因明显是第二行的
Imported class "com.mysql.jdbc.Connection" could not be loaded
仔细一想确实是这样,因为鄙人的mysq是8.0版本的,其中jdbc的大部分类已经换地方了 ,换到了“com.mysql.cj.jdbc”下边
然后就去改代码,把之前导包的代码变成了
import com.mysql.cj.jdbc.Connection;
import com.mysql.cj.jdbc.PreparedStatement;
还是报错,我就在想会不会是jar包的原因,随后把连接mysql 的驱动jar包放在java 的jar/lib路径下。
结果还是找不到,就反过来看mysql 的驱动,在mysql的驱动jar包中确实没有com.mysql.cj.jdbc.Connection,随后看到一个和这个长的比较像的类
叫做“ConnectWapper.class”,我用这个类试了一下还是报错,但是这次报错信息是找不到类里的方法,说明Kettle已经读取到这个驱动jar包了,鄙人把jar解压放到IDEA里边看源码
误打误撞发现java.sql中就有Connect和PreparedStatement类,就把导包的代码改成从java.sql中导入了,结果居然运行成功了,这次是运气好才运行成功的,如果以后想要解决其他问题还是要多多学习,也希望有大佬可以指点一下具体是什么问题,谢谢。
4.下面是修改后的代码
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Connection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//声明输入的结果字段
private String result;
//声明输出的结果字段
private String contents;
//声明数据库连接
private Connection connection = null;
public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException,SQLException{
if (first) {
//获取传入的结果参数
result=getParameter("result");
first = false;
}
//获取一行记录,即抽取到的每行超链接数据
Object[] r = getRow();
if (r == null) {
setOutputDone();
return false;
}
Object[] outputRow = createOutputRow(r, data.outputRowMeta.size());
String foobar = get(Fields.In, result).getString(r);
//编写抽取超链接<a><a>标签中数据的正则表达式规则
String pattern ="<a[^>]*href(\\\"([^\\\"]*)\\\"|\\'([^\\']*)\\'|([^\\\\s>]*))[^>]*>(.*?)</a>";
//将匹配规则进行编译,生成Pattern对象,Pattern对象表示编译后的正则表达式
Pattern patterns = Pattern.compile(pattern);
//将匹配规则与输入界面的foobar字段进行匹配,生成Matcher对象
Matcher m = patterns.matcher(foobar);
//查找与该匹配规则匹配的数据,并输出到数据库的html数据表中
while(m.find()){
//获取输出的字段,并进行赋值
get(Fields.Out, "contents").setValue(outputRow, m.group().replaceAll("<[^>]*>",""));
//配置数据库连接,用于保存抽取到的数据
String url = "jdbc:mysql://localhost:3306/kettle?useUnicode=true&characterEncoding=UTF-8&useSSL=false";
String userName = "root";
String userPwd = "147369";
try{
// 加载驱动程序
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接对象
connection= (Connection) DriverManager.getConnection(url, userName, userPwd);
} catch (Exception e) {
e.printStackTrace();
}
//要执行的SQL语句
String sql="insert into html (contents) values (?);";
PreparedStatement stat = (PreparedStatement) connection.prepareStatement(sql);
contents=m.group().replaceAll("<[^>]*>","");
stat.setString(1, contents);
//ResultSet类,用来存放获取的结果集!!
stat.executeUpdate();
putRow(data.outputRowMeta, outputRow);
}
return true;
}