这里我们继续上篇的内容,完善如何将爬取的数据插入到数据库中,并对前边所提到的推荐量问题做一下视觉上的更改。
先写一个对推荐量的类型转换类:
package com.qiku.util;
public class SpiderUtil {
public static String DataFormat(String txt){ //类名一般要大写
if(txt.contains("万")){ //判断是否包含 “万”
txt = "" + Double.parseDouble(txt.substring(0,txt.length()-1)) * 10000 ;
}
else
txt = txt + ".0";
return txt;
}
public static int StrToInt(String novelAdviceNumber){
String temp = DataFormat(novelAdviceNumber);
String str = temp.substring(0,temp.indexOf("."));
//返回一个将 str 字符类型数据转换为 Integer 整型数据。
return Integer.parseInt(str);
}
}
对上面代码中一些方法的简要说明:contains("万") 方法判断该属性上的值中是否包含字符串 “万” ;Double 类中一个方法 parseDouble() ,作用是将字符串转换为 double 类型。subString(a,b) 方法表示将截取下标从 a 开始到 b 结束的字符;indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置 ;stringObject.indexOf(searchvalue,fromindex),该方法将从头到尾地检索字符串 stringObject,看它是否含有子串 searchvalue(必须参数,规定需检索的字符串值), 开始检索的位置在字符串的 fromindex(可选的整数参数,合法取值是 0 到 stringObject.length -1)处或字符串的开头(没有指定 fromindex 时),如果找到一个 searchvalue ,则返回 searchvalue 的第一次出现的位置。
接下来创建一个小说类,用于生成对象,体现面向对象思想:
package com.qiku.entity;
public class NovelInfo {
public String NovelName ; //小说名称
public int NovelWordCount ; //小说总字数
public int NovelAdviceNumber ; //小说推荐量
public String getNovelName() {
return NovelName;
}
public void setNovelName(String novelName) {
NovelName = novelName;
}
public int getNovelWordCount() {
return NovelWordCount;
}
public void setNovelWordCount(int novelWordCount) {
NovelWordCount = novelWordCount;
}
public int getNovelAdviceNumber() {
return NovelAdviceNumber;
}
public void setNovelAdviceNumber(int novelAdviceNumber) {
NovelAdviceNumber = novelAdviceNumber;
}
}
建议:这里定义的小说元素名称尽量和你本地数据库表中设置的字段保持一样,因为这里边代码稍微有些多,起名不同的话容易乱,当然这只是建议。
这几步都完成之后,开始获取本地数据库的连接,在此之前先把数据库表建好,如:
我这里表名为 novelinfo ,里面有三个字段,分别表示小说名称的 NovelName ,小说总字数的 NovelWordCount ,小说推荐量的 NovelAdviceNumber 。接下来开始创建获取数据库连接的类:
package com.qiku.DBUtils;
import java.sql.DriverManager;
import java.sql.SQLException;
import com.mysql.jdbc.Connection;
public class DBUtil {
private final String URL = "jdbc:mysql://localhost:3306/henuspider" ;
// ?useUnicode=true&characterEncoding=utf8
private final String USER_NAME = "root" ;
private final String PASSWD = "123456" ;
private final String DRIVER = "com.mysql.jdbc.Driver" ;
public static DBUtil db;
//定义的私有构造函数,用来生成对象
private DBUtil(){
}
public static synchronized DBUtil getIntance(){
if(db==null){
db=new DBUtil();
}
return db;
}
public Connection getConnection() throws Exception {
try{
Class.forName(DRIVER);//加载驱动
Connection connection = (Connection) DriverManager.getConnection(URL,USER_NAME,PASSWD);
return connection;
}catch(ClassNotFoundException e){
e.printStackTrace();
}
return null;
}
}
刚开始定义的 URL 值不能乱设,是根据前面所设置的主机名称、数据库端口、数据库名称等来填写,下面的同样按照前面设置来,还需要注意大小写,这个也很重要,至于那个 jdbc:mysql 不是很清楚,貌似我们是通过 jdbc 来获取数据库连接 mysql 的,所以...(如果有大神知道的话也可以解惑一下)
我们上面定义的方法是静态方法,仅用来生成 DBUtil 对象,又由于该方法是静态的,调用方式和一般的不一样,是通过 getIntance() 方法来调用,synchronized 是为多线程数据安全同步使用,简单理解 synchronized 的作用,假如在多个类中同时调用 DButil.getInstance() 方法,因为是同时并发,所以就不保证整个系统中只有一个,所以需要加入“同步”机制:synchronized 。 DButil 类对象:db ,也就是说,通过在 方法 getIntance() 前加上 synchronized 。就类似门上的一把锁,同时只能有一个人开门,开门后,进入里面办事,办完后,再快速的关门,把锁的钥匙给另一个想开锁进门办事的人;若不加同步机制,就类似一个房间(getIntance)有多个门可以进入,这样同时多个人进来,就会有同一个数据被多个人同时处理的情况。
关于 Class.forname() 我是第一次接触,我们讲师大致意思就是说是为了给数据库加载驱动什么的,我后来查了查,大致意思差不多,详解见 Class.forname() 的使用 。
获取过数据库连接之后,下面就是对数据库进行相关操作:
package com.qiku.dao;
import java.sql.PreparedStatement;
import com.mysql.jdbc.Connection;
import com.qiku.DBUtils.DBUtil;
import com.qiku.entity.NovelInfo;
public class NovelDao {
public int insertNovelInfo(NovelInfo novel) throws Exception{
String sql="insert into novelinfo(NovelName,NovelWordCount,NovelAdviceNumber) values (?,?,?)";
DBUtil dbutil = DBUtil.getIntance();
Connection connection = dbutil.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//往数据库表中添加数据
ps.setString(1, novel.getNovelName());
ps.setInt(2, novel.getNovelWordCount());
ps.setInt(3, novel.getNovelAdviceNumber());
int result = ps.executeUpdate();
//按顺序关闭对应资源(释放资源):
ps.close();
connection.close();
return result;
}
}
同样,字符串中是 sql 语句,不能乱写。现在基本上需要的功能类都写好了,开始实现我们写的成功不成功,在带有主函数的类中(即最初实现数据爬取的类)添加后续的功能,完整的代码为:
package com.qiku.seivice;
import java.io.IOException;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import com.qiku.dao.NovelDao;
import com.qiku.entity.NovelInfo;
import com.qiku.util.SpiderUtil;
public class WebpageSpider {
public static void main(String[] args) throws Exception {
// TODO 自动生成的方法存根
//定义小说所在网页位置
String URL="https://www.qidian.com/search?kw=完美世界";
//获取 Jsoup 连接
Connection connect = Jsoup.connect(URL);
//触发 URL 对应的 html 信息
Document document = connect.get();
//定位所有小说所在的路径
Elements NovelList = document.select(".book-img-text ul li");
//遍历输出
for(Element ele:NovelList) {
Elements NovelName = ele.select(".book-mid-info h4");
System.out.println("小说名称: "+NovelName.text());
Elements NovelWordCount = ele.select(".total p span");
System.out.println("小说总字数: "+NovelWordCount.first().text());
//或者这样定义
//在网页源代码中可以看到总字数和推荐量位于同一个 div 下,故能一起用。
String NovelAdviceNumber = NovelWordCount.last().text();
System.out.println("小说推荐量: "+NovelAdviceNumber);
NovelDao novelDao = new NovelDao();
NovelInfo novelInfo = new NovelInfo();
novelInfo.setNovelName(NovelName.text());
novelInfo.setNovelWordCount(SpiderUtil.StrToInt(NovelWordCount.first().text()));
novelInfo.setNovelAdviceNumber(SpiderUtil.StrToInt(NovelAdviceNumber));
int result = novelDao.insertNovelInfo(novelInfo);
System.out.println("结果: "+result);
System.out.println();
}
}
}
运行结果为:
显示的结果表明已经成功了,那么我们去看下数据库中是否真的有数据:
我们发现,小说的总字数。总推荐量都转换为相应的数字并入库了,但小说名称却是 一堆问号,这是什么情况呢?这表示在数据入库时发生了乱码。我们回到获取数据库连接的类中,把下面注释的部分加上:
package com.qiku.DBUtils;
import java.sql.DriverManager;
import java.sql.SQLException;
import com.mysql.jdbc.Connection;
public class DBUtil {
private final String URL = "jdbc:mysql://localhost:3306/henuspider?useUnicode=true&characterEncoding=utf8" ;
// ?useUnicode=true&characterEncoding=utf8
private final String USER_NAME = "root" ;
private final String PASSWD = "123456" ;
private final String DRIVER = "com.mysql.jdbc.Driver" ;
public static DBUtil db;
//定义的私有构造函数,用来生成对象
private DBUtil(){
}
public static synchronized DBUtil getIntance(){
if(db==null){
db=new DBUtil();
}
return db;
}
public Connection getConnection() throws Exception {
try{
Class.forName(DRIVER);//加载驱动
Connection connection = (Connection) DriverManager.getConnection(URL,USER_NAME,PASSWD);
return connection;
}catch(ClassNotFoundException e){
e.printStackTrace();
}
return null;
}
}
再次运行结果后,查看数据库:
诶,这样就成了(嘻嘻嘻),这就是数据采集的全部过程。先从网页爬取数据,然后将其封装到一个一个对象中,通过 jdbc 获取数据库连接,将爬取的数据保存至数据库中,使其长久化。下面再说一下这做好的一个项目如何打包给客户(这是我们讲师课外提的),见 数据采集及其可视化(三),如果不感兴趣,那就直接进入后续的可视化操作吧,见 数据采集及其可视化(四)。