唐诗分析项目设计文档

一. 唐诗项目介绍

1.1项目背景

爬虫技术一直处于风口浪尖,爬了不该爬的会引来一身官司,爬想爬取的数据又不会轻而易举获得,但是又想来玩玩爬虫技术,怎么办呢?首选古诗文网,因为据了解它是没有设置任何反爬虫机制的,数据都是公开合法的,这里我就选择了唐诗三百首来进行爬虫,你们不好奇哪位唐代诗人写的诗最多吗?你们不好奇诗人们最喜欢用什么词去作诗吗?如果你对唐诗也感兴趣,那就跟我一起开启这个奇妙的诗词探险吧。

1.2项目需求

一.用柱状图来展示诗人们的姓名和诗词数量,并按诗词数量降序排序。
二.用词云来展示词的使用频度,使用最频繁的词应该一眼看出。

1.3项目设计

一.通过爬虫机制把唐诗的数据爬取出来,对它进行解析,将每首诗的数据写入到数据库中。
二.通过发起请求来对数据进行分析筛选,最后渲染成可视化效果。
在这里插入图片描述

1.4项目工具选择

这是一个Web项目,Java选择用IDEA这个编译器,选择用maven来引进一些需要用到的第三方库,通过tomcat这个Web应用服务器来执行。

二.唐诗数据爬取模块

2.1技术选型环节

2.1.1爬虫技术

我了解到的爬虫技术栈有 HtmlUnit,HttpClient,这里我选择了HtmlUnit这个框架。

  • 原因:HttpClient是用来模拟HTTP请求的,用的是socket通信,通过get方法来提交请求,只能获取html静态页面的源码,如果页面中有js部分,则不能获取到js执行后的源码。
    HtmlUnit是一款无界面的浏览器程序库,它模拟用户去操作浏览器,允许调用页面,填写表单,点击链接等,还可以执行js,有很多的API用起来也非常方便。

2.1.2分词技术

使用ansj_seg库对古诗的标题和正文进行分词,为词云做准备。这个中文分词器正确率高,不容易出做,分词速度也快,效果也比较高。

2.2项目依赖环节

解析请求html页面

<dependency>
            <groupId>net.sourceforge.htmlunit</groupId>
            <artifactId>htmlunit</artifactId>
            <version>2.36.0</version>
        </dependency>

分词

<dependency>
            <groupId>org.ansj</groupId>
            <artifactId>ansj_seg</artifactId>
            <version>5.1.6</version>
        </dependency>

2.3预研环节

通过编写一些Demo来熟悉这些技术的使用,看看它展示的效果是否满意。

2.3.1解析列表页Demo

WebClient client = new WebClient(BrowserVersion.CHROME);
        client.getOptions().setJavaScriptEnabled(false);
        client.getOptions().setCssEnabled(false);

        String baseurl = "https://so.gushiwen.org";
        String pathurl = "/gushi/tangshi.aspx";
        List<String> detailUrlList = new ArrayList<>();//所有古诗的详情页的url
        //列表页的解析
        {
            String url = baseurl + pathurl;
            HtmlPage page = client.getPage(url);//获取诗词页面
            List<HtmlElement> divs = page.getBody().getElementsByAttribute("div", "class", "typecont");
            for (HtmlElement div : divs) {
                List<HtmlElement> as = div.getElementsByTagName("a");
                for (HtmlElement a : as) {
                    String detailUrl = a.getAttribute("href");
                    detailUrlList.add(baseurl + detailUrl);
                }
            }
        }

2.3.2分词Demo

public static void main(String[] args) {
        String sentence="中华人民共和国成立了!中国人民从此站起来了";
       List<Term> termlist=NlpAnalysis.parse(sentence).getTerms();
       for(Term term:termlist){
           System.out.println(term.getNatureStr()+":"+term.getRealName());
       }
    }

2.4具体开发环节

2.4.1数据库的设计

数据库要存取:自增主键id,标题,朝代,作者,正文,分词,sha-256。

  • SHA-256是为了防止下载重复诗词,所以用每首诗的标题和正文去计算SHA-256值。
CREATE TABLE `tangpoetry` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sha256` char(64) NOT NULL,
  `dynasty` varchar(10) NOT NULL,
  `title` varchar(100) NOT NULL,
  `author` varchar(10) NOT NULL,
  `content` text NOT NULL,
  `words` text NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `sha256` (`sha256`)
) ENGINE=InnoDB AUTO_INCREMENT=703 DEFAULT CHARSET=utf8mb4

2.4.2爬取,解析数据

  • 获取列表页解析出每首诗的url
  • 解析详情页得出要往数据库中写入的数据(标题,朝代,作者,正文,计算SHA-256,计算分词(切记分词要去除特殊字符,比如符号,null等)需要将分词拼接成字符串格式,方便存储)

2.4.3数据写入到数据库中

  • 通过JDBC建立数据库连接,将标题,朝代等信息插入到数据库的表中。

2.4.4验证并优化—多线程

*通过sql语句查看320首诗词数据是否成功写入到数据库中。如果信息无误,则插入成功。

  • 单线程版本效率低,每次都是主线程去完成各项任务,在解析详情页的时候每次都是主线程,而总共有320首诗,就要挨个去解析320次,非常耗时。
  • 引入多线程版本:让主线程去获取列表页,解析列表页,得出每首诗的url,开启多个线程,去完成列表页解析工作,并将数据写入到数据库中。
    问题:1.webclient不是线程安全的,每个线程都得自己建一个webclient对象
    2.connection不是线程安全的,需要通过传
    datasource这个参数去建立连接。
    3.MessageDigest这个计算SHA-256的也不是线程安全的。
    多线程每次都要创建320个子线程去执行任务,执行完再销毁,显然有点浪费资源。
  • 线程池版本:这里用的是Executors.newFixedThreadPool(30)这个固定数目的线程池,把多线程要处理的那些任务去交给线程池里面的线程去完成,这里我设置的是30个核心线程。
    BUG: JVM结束是指所有非后台线程执行结束后才关闭,而线程池一直在主线程中,根本不可能自己关闭,它会不断地去执行任务,重复的死循环,永不停止。
    解决办法: 只要等所有子线程都完成任务,就可以手动关闭线程池。
  • 如何判断子线程任务是都都结束了呢?
    1.CAS原子类
private  static AtomicInteger successCount=new AtomicInteger(0);
    private  static AtomicInteger failureCount=new AtomicInteger(0);
    private static class Job implements  Runnable{
        private  void work() throws IOException, InterruptedException {
            Random random=new Random();
            int n=random.nextInt(5);
            if(n<2){
                throw  new IOException();
            }
            Thread.sleep(5);
        }
        @Override
        public void run() {
            try{
                work();
                //successCount++;
                successCount.getAndIncrement();

            }catch (IOException e){
                //failureCount++;
                failureCount.getAndIncrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
     for(int i=0;i<COUNT;i++){
    Thread thread=new Thread(new Job());
    thread.start();
      }
      while(successCount.get()+failureCount.get()!=COUNT){
          Thread.sleep(1000);
          System.out.println("任务还没完成");
      }
        System.out.println("任务全部完成");
    }

2.CountDownLatch:开始会设定要等待的线程数,主线程阻塞,每执行完一个子线程就调用countDown(),此时计数器-1,直到计时器为0时才将主线程唤醒,不为0 就一直await(),为0说明子线程任务都执行完了,就可以关闭线程池了。


       CountDownLatch countDownLatch = new CountDownLatch(detailUrlList.size());//指定countDownLatch要等待的线程数
     for (String url : detailUrlList) {
            pool.execute(new Job(url,dataSource2,countDownLatch));
        }
        countDownLatch.await();//如果没处理结束,就等待
        pool.shutdown();//最后关闭线程池
    }

2.5总结问题

  • 在创建数据库的时候,要合理的设计每个字段的类型,不能盲目浪费资源。
  • 多线程设计,一定要考虑线程安全问题,不然出大错。
  • 学会不断地优化项目。

三.诗词可视化分析模块

3.1技术选型

3.1.1 echarts可视化

echarts是一个开源免费的javascript可视化库,柱状图和词云都是来自于它。而且它是开源的,中文文档,方便上手。

3.1.2 jquery的发起ajax请求

因为echarts的数据是写死的,所以使用jquery来发起ajax请求,用httpservlet来处理请求,返回一个json格式的字符串,等加载成功执行success方法去进行可视化,这样数据就是从请求响应获取的,不再是写死的。

3.1.3 FastJson格式

返回Json格式的第三方库有很多,比如Gson,FastJson等,我选择FastJson主要是因为写起来简单,是阿里巴巴维护的,我这个应用小,也估计碰不到它的BUG。

3.2导入依赖

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
   <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

3.3代码开发

3.3.1 JDBC建立数据库连接

这里用的是饿汉的设计模式,来进行数据库连接。

3.3.2 整理诗的数量

  • 继承HttpServlet,通过doGet方法来处理ajax发起的http请求,用复合查询sql语句就可以搞定,再将数据写入到JSONArray中,返回一个Json格式的字符串。
SELECT author, count(*) AS cnt FROM tangpoetry GROUP BY author HAVING cnt >=? ORDER BY cnt DESC
  • 用@WebServlet(“rank.json”)来配置path路径,就不需要去web.xml去配置servlet了。
  • 发起ajax请求,收到响应后去执行success里面的方法,进行柱状图的渲染。
method: "get",  // 发起 ajax 请求时,使用什么 http 方法
        url: "rank.json?condition=10",   // 请求哪个 url
        dataType: "json",   // 返回的数据当成什么格式解析
        success: function (data) {  // 成功后,执行什么方法

通过html页面的script标签找到js文件去执行。

3.3.3 整理分词

  • 继承HttpServlet,通过doGet方法处理请求,将分词都先放入到list中,再用map来整理每个词出现的次数,最后将Key和Value来放入到JSONArray中,返回一个json格式的字符串。
  • 同样用@WebServlet("/words.json")

3.4验证效果

在这里插入图片描述

在这里插入图片描述


版权声明:本文为weixin_43688527原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。