文章较长,想看哪儿请直接点目录
先奉上源码链接项目源码
先来看一下项目最终的效果什么样子的,从项目名称可以看出来这个项目是对唐诗的数据进行分析,实现两个功能,也就是我们的需求
功能一:图一这样,以作者创作数量的大小进行排序
功能二:将作者用的高频词汇统计出来,看看古人爱用哪些词
最终用户看到的就是一个分析好的结果。
①、第三方库的使用,比如HtmlUtil、Servlet、ansj_seg等
②、JavaEE,JAVA和数据库的综合英勇
③、多线程和线程池的理解使用
④、SHA256去重算法
⑤、HTTP协议
⑥、echarts、jQuery、ajax前端展示的使用
⑦、GSON数据格式
⑧、软件测试的基本思想应用
⑨maven的使用
为什么选择maven开发?
1、maven一个命令就可以完成项目构建过程
2、他可以进行强大的依赖管理
3、可以分阶段进行构建
4、提供web项目的模式
想一下现在需求明确了,就该着手去考虑怎么下手做,我要分析数据,那数据从哪儿来?往哪儿存?怎么存?这是第一个大方面;第二个方面,我有数据了,那用户会去看你找到的数据吗?用户是最懒得,他只想看到上图统计好的结果,那问题来了,怎样展示?所以就可以分为两大模块去做,数据获取和数据展示。
数据的来源应该是公开的,就比如我们不能去获取教务系统存的学生信息,所以选择唐诗三百首,这里的诗词信息都公开
我是要把数据存的数据库,所以表怎么建,要是存进来的数据重复了怎么处理?要统计高频词的前提是知道怎样分词?存进来了怎么让前端获取数据去可视化。
请求列表页->解析列表页->下载详情页->解析详情页->想办法去重->计算分词->存入数据库
引入HtmlUnit这个第三方库来帮我们解析,它是一个无界面的浏览器,它自带http客户端,给他一个url,它就会去请求页面。用它里面的webclient类,就可以完成请求和解析,从列表页里筛选我们需要的标签进入详情页,它的一些api使用案例里面有
net.sourceforge.htmlunit
htmlunit
2.36.0
HtmlUnit使用案例
public class HtmlUnitDemo {
public static void main(String[] args) throws IOException {
//无界面的浏览器(HTTP客户端),假装自己是谷歌浏览器
WebClient webClient=new WebClient(BrowserVersion.CHROME);
//可能第三方库里写的css和js不标准,运行会报警告,所以先关了。关闭浏览器css执行引擎
webClient.getOptions().setCssEnabled(false);
webClient.getOptions().setJavaScriptEnabled(false);
//把url放进去,它会返回HtmlPage类型的数据
HtmlPage page = webClient.getPage("https://so.gushiwen.org/gushi/tangshi.aspx");
//保存列表页html文档,放到文件里
page.save(new File("唐诗三百首\\列表页.html"));
//html的body
HtmlElement body=page.getBody();
//筛选包含我们所需内容的标签,保存标签的内容,3个参数:元素名称,标签名称。标签值,
List elements = body.getElementsByAttribute("div",
"class",
"typecont");
HtmlElement divElement = elements.get(0);
List aElement = divElement.getElementsByAttribute("a",
"target",
"_blank");
//一个详情页的URL
String str=aElement.get(0).getAttribute("href");
}
}
跟上面的道理是一样的,用HtmlPage/HtmlElement,这次用Xpath来获取
标题xpath="//div[@class='cont']/h1/text()";
朝代 (第一个 a 标签,下标从一开始)
xpath="//div[@class='cont']/p[@class='source']/a[1]/text()";
作者 (第二个 a 标签)
xpath="//div[@class='cont']/p[@class='source']/a[2]/text()";```
正文
xpath="//div[@class='cont']/div[@class='contson']";
public class SHA256Demo {
public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest messageDigest=MessageDigest.getInstance("SHA-256");
String s="你好世界";
//转成字节数组,通过update传进去,调用digest
byte[] bytes = s.getBytes("UTF-8");
messageDigest.update(bytes);
byte[] result = messageDigest.digest();
System.out.println(result.length);
for (byte b : result) {
System.out.printf("%02x",b);
}
System.out.println();
}
}
引入第三方库,给它一句话,可以帮你分成词,而且使用很方便,AnsjSeg提供了四种分词调用的方式:基本分词(BaseAnalysis)、精准分词(ToAnalysis)、NLP分词(NlpAnalysis)、面向索引分词(IndexAnalysis) NLP分词方式是未登录词,但速度较慢:
org.ansj
ansj_seg
5.1.6
public class DetailPartDemo {
public static void main(String[] args) throws IOException {
try (WebClient webClient = new WebClient(BrowserVersion.CHROME)) {
webClient.getOptions().setCssEnabled(false);
webClient.getOptions().setJavaScriptEnabled(false);
String url = "https://so.gushiwen.org/shiwenv_45c396367f59.aspx";
HtmlPage page = webClient.getPage(url);//url传进去得到body部分
HtmlElement body = page.getBody();
/*
List elements = body.getElementsByAttribute(
"div",//输出正文对应的id
"class",
"contson"
);
for (HtmlElement element : elements) {
System.out.println(element);
}
System.out.println(elements.get(0).getTextContent().trim());//输出正文内容
*/
// 标题 朝代 作者 正文
{
String xpath = "//div[@class='cont']/h1/text()";
Object o = body.getByXPath(xpath).get(0);//取第一个就行
DomText domText = (DomText) o;//文档对象树
System.out.println(domText.asText());
}
//1就代表第一个 a[1]
{
String xpath = "//div[@class='cont']/p[@class='source']/a[1]/text()";
Object o = body.getByXPath(xpath).get(0);
DomText domText = (DomText) o;
System.out.println(domText.asText());
}
{
String xpath = "//div[@class='cont']/p[@class='source']/a[2]/text()";
Object o = body.getByXPath(xpath).get(0);
DomText domText = (DomText) o;
System.out.println(domText.asText());
}
{
String xpath = "//div[@class='cont']/div[@class='contson']";
Object o = body.getByXPath(xpath).get(0);
HtmlElement element = (HtmlElement) o;
System.out.println(element.getTextContent().trim());
}
}
}
}
分为建库建表在连接,要完整的描述一首诗,肯定要有作者、朝代、标题、正文、分词这些信息,连接数据库我们引入第三方库来连接
mysql
mysql-connector-java
5.1.47
一个免费的js可视化库,上面的项目效果就是它做出来的。写起来简单,这个项目比较简单,基本碰不上它的bug,支持国产
提供一个接口给前端处理作者作诗数的统计
com.alibaba
fastjson
1.2.62
public class RankServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset=utf-8");
//用的get方法
String condition = req.getParameter("condition");
if (condition == null) {
condition = "5";
}
JSONArray jsonArray = new JSONArray();
try (Connection connection = DBConfig.getConnection()) {
String sql = "SELECT author, count(*) AS cnt FROM tangshi GROUP BY author HAVING cnt >= ? ORDER BY cnt DESC";
//把作者全部统计出来
try (PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, condition);
try (ResultSet rs = statement.executeQuery()) {
while (rs.next()) {
String author = rs.getString("author");
int count = rs.getInt("cnt");
JSONArray item = new JSONArray();
item.add(author);
item.add(count);
jsonArray.add(item);
System.out.println(author+":"+count);
}
resp.getWriter().println(jsonArray.toJSONString());
}
}
} catch (SQLException e) {
e.printStackTrace();
JSONObject object = new JSONObject();
object.put("error", e.getMessage());
resp.getWriter().println(object.toJSONString());
}
}
}在这里插入代码片
public class WordServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String condition=req.getParameter("condition");
if(condition==null){
condition="5";
}
HashMapmap=new HashMap<>();
Connection connection = DBConfig.getConnection();
String sql="SELECT word FROM tangshi";
PreparedStatement statement=null;
try {
statement = connection.prepareStatement(sql);
ResultSet rs = statement.executeQuery();
while (rs.next()){
String word=rs.getString("word");
String[] split = word.split(",");
for (String s : split) {
int count=map.getOrDefault(s,0);
map.put(s,++count);
}
}
// System.out.println(map.toString());
} catch (SQLException e) {
e.printStackTrace();
JSONObject object = new JSONObject();
object.put("error", e.getMessage());
resp.getWriter().println(object.toJSONString());
}finally {
DBConfig.close(connection,statement);
}
JSONArray jsonArray=new JSONArray();
for (String s : map.keySet()) {
JSONArray item=new JSONArray();
item.add(s);
item.add(map.get(s));
jsonArray.add(item);
}
resp.setContentType("application/json ; charset=utf-8");
resp.getWriter().println(jsonArray.toJSONString());
System.out.println(jsonArray.toJSONString());
}
}
利用$.ajax()发起一个HTTP请求,后台收到请求时返回给前端一个index.html文件,这个文件里面的Script标签又会主动向后台发送http请求,得到json格式的数据,js中的代码把数据写到echart中,页面就展示出来了也引入第三方库,js请求交给相对应的servlet去处理(创作数量排行榜用RankServlet()去处理,诗词用词云图用wordServlet()去处理)
这里也引入servlet第三方库
javax.servlet
javax.servlet-api
3.1.0
provided
在这里插入代码片
所有工作只有主线程一个人在做,线程安全但速度太慢了
增加速度,列表页的请求和解析整个项目只执行一次,所以放在主线程中去做而;详情页的请求和解析需要执行320次,在多线程中去做,同理提取诗词信息(标题,作者,内容)、计算 sha256 的值、计算分词、信息插入数据库等步骤放到多线程中去做
引入的问题:线程不安全,webclient messagedigest ,preparedstament、datasource都线程不安全,加锁的话达不到高效率的目的,所以让每个线程建立自己的对象,
线程池可以减少线程创建和销毁的次数,但线程池中的线程不会结束,因为jvm在所有非后台线程都结束后才会结束,我们用CountDownLatch,在主线程中调用countDownLatch,作为参数传给线程,调用pool.shutDown();让线程池结束
1、点击创作数量排行榜,页面正常显示,如上图
2、二次点击创作数量排行榜,同名次的人会被替换
3、点击诗词用词云图,页面正常显示
4、多次点击诗词用词云图可将所有字展示出来
5、没有网络时,功能不可用
6、运行过程中关闭tomcat,显示失败
7、获取数据部分写完运行可以存进数据库
8、数据的过滤清洗得到了预期的结果
9、数据库异常时访问失败
10、统计到得到诗人作诗数量经测试一致
11、在进行结果分析时断网
12、超过数据库字段设置的最大长度会报错
13、多次启动爬取数据会报错
14、爬的过程中执行truncate table
1、响应时间都、在三秒以内,符合358原则
2、单线程版本数据从爬取到插入数据库需要38秒
3、多线程版本数据从爬取到插入数据库需要5秒
4、线程池版本数据从爬取到插入数据库需要4秒
5、多个用户同时点击时,系统会不会崩溃
1、页面排版符合预期效果
2、页面字体大小是否合适
3、页面的整个布局是否合理
4、界面按钮在预期位置
5、页面文字显示正确
1、在谷歌浏览器可正常显示
2、在搜狗浏览器可正常显示
3、在IE浏览器可正常显示
4、在火狐浏览器可正常显示
5、在linux环境下可运行
6、在win7环境下可运行
7、在winxp环境下可正常显示
8、手机可正常显示
9、每一个浏览器的不同版本也要测试
10、在苹果系统运行正常
1、数据库方面的安全
2、反爬虫方面
1、不能用上下左右键操作功能切换,对没有鼠标的人不方便
2、页面功能按钮设计直观,用户很容易理解
3、输入一个简单的url就可以展示结果