总耗时4秒爬取2.7W+数据并存入数据库!简单的java爬虫!jsoup

前言:有时候可能需要从网上或者某个网站收集一些数据,这时候就可以用爬虫来实现,不需要手动去收集费时费力。本文使用java的jsoup来实现。

  • 前置条件:

    • JAVA基础:https://www.runoob.com/java/java-tutorial.html

    • 有安装可运行java的编译器(idea等)

    • 有安装mysql(可百度如何安装mysql)

    • 有安装数据库管理工具(Navicat Premium 12等)

    • 用到的jar包

      • jsoup-1.11.3.jar

      • mysql-connector-java-8.0.16.jar

      • jar下载(搜索下载):https://mvnrepository.com/

  • 准备工作

    • 寻找目标网站

      • 因为要简单粗暴,所以找了一个爬取逻辑不复杂的网站

      • 本文所爬目标网站:https://chengyu.911cha.com/pinyin_a_p1.html

    • 在数据库中新建一个表

      • 表名任意、字段任意(例:id、首字母、成语、个数)

      • 具体百度“如何用Navicat新建表和添加字段”

  • 分析目标网站

    • 第一步

      • 在浏览器中按F12观察该网站HTML代码

        F12界面

      • 谷歌浏览器为例,可以按ctrl+shift+c。鼠标移动到网页任意地方,右边的代码框会显示当前鼠标停留处的代码。

      • 网站看起来结构并不复杂,甚至还有点整齐。英文字母A-Z一字排开整齐放好了,意味着写爬虫代码时可以不用自己手动输入英文字母A-Z,后面会提到如何自动获取英文字母。

    • 第二步

      • 这是网页分页部分的html代码,会发现多页时代码中会有5个a标签。那么要计算一共有多少也可以用a标签的个数减去2就是真实页数了。

          
        首页 末页 | 1 2 3 | 上一页 下一页
      • 这个网页还存在另一种情况,比如字母o的成语中a标签只有一个那这种就不用减2了。

          
        首页 末页 | 1 | 上一页 下一页
  • 开始写代码

    • 项目结构

      项目结构
    • Idioms.class

    public class Idioms {
        private String letter;
        private String content;
        private int num;
    
        public String getLetter() {
            return letter;
        }
    
        public void setLetter(String letter) {
            this.letter = letter;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
        public int getNum() {
            return num;
        }
    
        public void setNum(int num) {
            this.num = num;
        }
    }
    
    • IdiomsThread.class
    public class IdiomsThread implements Runnable {
        CallBack callBack;
        long startTime = System.currentTimeMillis();
        long endTime;
        String letter;
        int letterCount = 0;
    
        private synchronized void insertData(List idiomsList) {
            String sql = "insert into 表名 values";
            for (int i = 0; i < idiomsList.size(); i++) {
                if (i == 0) {
                    sql = sql + "(id,\'" + idiomsList.get(i).getLetter()
                            + "\',\'" + idiomsList.get(i).getContent()
                            + "\'," + idiomsList.get(i).getNum() + ")";
                } else if (i == idiomsList.size() - 1) {
                    sql = sql + ",(id,\'" + idiomsList.get(i).getLetter()
                            + "\',\'" + idiomsList.get(i).getContent()
                            + "\'," + idiomsList.get(i).getNum() + ");";
                } else {
                    sql = sql + ",(id,\'" + idiomsList.get(i).getLetter()
                            + "\',\'" + idiomsList.get(i).getContent()
                            + "\'," + idiomsList.get(i).getNum() + ")";
                }
            }
            DBManager dbManager = new DBManager(sql);
            try {
                dbManager.preparedStatement.executeUpdate();
                dbManager.close();
                endTime = System.currentTimeMillis();
                callBack.callBack(Thread.currentThread().getName(), (endTime - startTime) + "ms");
            } catch (SQLException e) {
                System.out.println("》》》》》》报错信息:" + "SQL语句:" + sql + "List大小" + idiomsList.size() + "当前字母:" + letter);
                e.printStackTrace();
            }
        }
    
        public IdiomsThread(String lertter, CallBack callBack) {
            this.letter = lertter;
            this.callBack = callBack;
        }
    
        @Override
        public void run() {
            insertData(getAllIdiomList(letter));
        }
    
        public interface CallBack {
            void callBack(String threadName, String runTime);
        }
    
        /**
         * 获取当前字母所有成语
         *
         * @param letter 字母
         * @return
         */
        private List getAllIdiomList(String letter) {
            List idiomBeanList = new ArrayList<>();
            for (int i = 1; i <= getTotalPages(letter); i++) {
                idiomBeanList.addAll(getIdiomList(letter, i));
            }
            return idiomBeanList;
        }
    
        /**
         * 获取单页成语
         *
         * @param letter 字母
         * @param page   当前页
         * @return
         */
        private List getIdiomList(String letter, int page) {
            List idiomBeanList = new ArrayList<>();
            Elements IdiomListElements;
            Document document = DocumentUtil.newInstance().getDocument(getUrl(letter, page));
            if (document != null) {
                IdiomListElements = document.select("body > div.mainbox > div.leftbox > div:nth-child(2) > div:nth-child(5) > ul > li");
                for (int i = 0; i < IdiomListElements.size(); i++) {
                    bean.Idioms idioms = new bean.Idioms();
                    idioms.setLetter(letter);
                    idioms.setContent(IdiomListElements.get(i).text());
                    idioms.setNum(letterCount);
                    idiomBeanList.add(idioms);
                    letterCount++;
                }
            }
            return idiomBeanList;
        }
    
        /**
         * 获取总页数
         *
         * @param letter 字母
         * @return 总页数
         */
        private int getTotalPages(String letter) {
            int count = 0;
            Elements pageElements;
            Document document = DocumentUtil.newInstance().getDocument(getUrl(letter, 1));
            if (document != null) {
                pageElements = document.select("body > div.mainbox > div.leftbox > div:nth-child(2) > div.gclear.pp.bt.center.f14 > a");
                count = pageElements.size();
            }
            if (count != 1) {
                return count - 2;
            } else {
                return count;
            }
        }
    
        /**
         * 获取链接
         *
         * @param letter 字母
         * @param page   当前页
         * @return url
         */
        public String getUrl(String letter, int page) {
            return "https://chengyu.911cha.com/pinyin_" + letter + "_p" + page + ".html";
        }
    }
    
    • GetIdioms.class
    public class GetIdioms {
        private static GetIdioms getIdioms;
        private long startTime;
        private long endTime;
        private List letterList = new ArrayList<>();
        private static int allThreadCount = 0;
    
        public static GetIdioms newInstance() {
            if (getIdioms == null) {
                getIdioms = new GetIdioms();
            }
            return getIdioms;
        }
    
        public GetIdioms() {
            startTime = System.currentTimeMillis();
            letterList = getLetterList();
            allThreadCount = letterList.size();
            System.out.println("-------------------------线程启动!-------------------------");
            System.out.println("------------------------共" + letterList.size() + "条线程!------------------------");
            for (int i = 1; i <= letterList.size(); i++) {
                Thread thread = new Thread(new IdiomsThread(letterList.get(i - 1), new IdiomsThread.CallBack() {
                    @Override
                    public void callBack(String threadName, String runTime) {
                        if (allThreadCount == 1) {
                            endTime = System.currentTimeMillis();
                            System.out.println("-------------------" + "所有线程执行完毕!总耗时:" + (endTime - startTime) + "ms!------------------");
                        } else {
                            increase();
                            System.out.println("-------------------" + threadName + "执行完毕!耗时" + runTime + "-------------------");
                        }
                    }
                }));
                thread.setName("字母"+letterList.get(i - 1)+"-线程" + String.format("%02d", i));
                thread.start();
                System.out.println("------------------------" + thread.getName() + "启动!------------------------");
            }
            System.out.println("------------------------所有线程启动完毕!-------------------------");
        }
    
        /**
         * 有一条线程完成时减一
         */
        public synchronized void increase() {
            allThreadCount--;
        }
    
        /**
         * 获取所有字母
         *
         * @return
         */
        private List getLetterList() {
            List letterList = new ArrayList<>();
            Elements letterElements;
            Document document = DocumentUtil.newInstance().getDocument(getUrl("a", 1));
            if (document != null) {
                letterElements = document.select("body > div.mainbox > div.leftbox > div:nth-child(2) > div.otitle > span > a");
                for (int i = 0; i < letterElements.size(); i++) {
                    letterList.add(letterElements.get(i).text().toLowerCase());
                }
            }
            return letterList;
        }
    
        /**
         * 获取链接
         *
         * @param letter 字母
         * @param page   当前页
         * @return url
         */
        public String getUrl(String letter, int page) {
            return "https://chengyu.911cha.com/pinyin_" + letter + "_p" + page + ".html";
        }
    }
    
    • DBManager.class(数据库工具)
    public class DBManager {
        private static final String url = "jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
        private static final String name = "com.mysql.cj.jdbc.Driver";
        private static final String username = "数据库账号";
        private static final String password = "数据库密码";
        public Connection connection = null;
        public PreparedStatement preparedStatement = null;
    
        public DBManager(String sql) {
            try {
                Class.forName(name);
                connection = DriverManager.getConnection(url, username, password);
                preparedStatement = connection.prepareStatement(sql);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public void close() {
            try {
                this.connection.close();
                this.preparedStatement.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • DocumentUtil.class(传入网页链接返回HTML代码)
    public class DocumentUtil {
        private static DocumentUtil documentUtil;
    
        public static DocumentUtil newInstance() {
            if (documentUtil == null) {
                documentUtil = new DocumentUtil();
            }
            return documentUtil;
        }
    
        public Document getDocument(String url) {
            Document doc = null;
            try {
                doc = Jsoup.connect(url)
                        .userAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36")
                        .timeout(50000).get();
                return doc;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
    • jsoupTest.class
    public class jsoupTest {
        public static void main(String[] args) {
            GetIdioms.newInstance();
        }
    }
    
    • 梳理

      • GetIdioms.class--先获取到网页内所有字母,每个字母分配一条线执行任务。这个网站有23个字母所以分配了23条线程,其实23条线程应该是多了。感觉效率没有提高还有所降低(欢迎指教)。

      • IdiomsThread.class-- 获取到分配的字母后,先获取该字母页面有多少页数。再根据页数逐页爬取成语,最后合并在一起然后存入数据库。

        数据库一次性插入多条数据语句示例:
        INSERT INTO table VALUES(id,'data1','data2',0),(id,'data1','data2',0),...,(id,'data1','data2',0);
        
  • 运行结果

    • 夜间网络较好的情况下4秒左右就能完成(凌晨的时候)

      -------------------------线程启动!-------------------------
       ------------------------共23条线程!------------------------
       ------------------------字母a-线程01启动!------------------------
       ------------------------字母b-线程02启动!------------------------
       ------------------------字母c-线程03启动!------------------------
       ------------------------字母d-线程04启动!------------------------
       ------------------------字母e-线程05启动!------------------------
       ------------------------字母f-线程06启动!------------------------
       ------------------------字母g-线程07启动!------------------------
       ------------------------字母h-线程08启动!------------------------
       ------------------------字母j-线程09启动!------------------------
       ------------------------字母k-线程10启动!------------------------
       ------------------------字母l-线程11启动!------------------------
       ------------------------字母m-线程12启动!------------------------
       ------------------------字母n-线程13启动!------------------------
       ------------------------字母o-线程14启动!------------------------
       ------------------------字母p-线程15启动!------------------------
       ------------------------字母q-线程16启动!------------------------
       ------------------------字母r-线程17启动!------------------------
       ------------------------字母s-线程18启动!------------------------
       ------------------------字母t-线程19启动!------------------------
       ------------------------字母w-线程20启动!------------------------
       ------------------------字母x-线程21启动!------------------------
       ------------------------字母y-线程22启动!------------------------
       ------------------------字母z-线程23启动!------------------------
       ------------------------所有线程启动完毕!-------------------------
       -------------------字母o-线程14执行完毕!耗时1377ms----------------
       -------------------字母e-线程05执行完毕!耗时1898ms----------------
       -------------------字母a-线程01执行完毕!耗时1937ms----------------
       -------------------字母r-线程17执行完毕!耗时6771ms----------------
       -------------------字母c-线程03执行完毕!耗时6853ms----------------
       -------------------字母b-线程02执行完毕!耗时6880ms----------------
       -------------------字母n-线程13执行完毕!耗时6966ms----------------
       -------------------字母k-线程10执行完毕!耗时7464ms----------------
       -------------------字母s-线程18执行完毕!耗时7769ms----------------
       -------------------字母q-线程16执行完毕!耗时8209ms----------------
       -------------------字母p-线程15执行完毕!耗时9102ms----------------
       -------------------字母l-线程11执行完毕!耗时9369ms----------------
       -------------------字母t-线程19执行完毕!耗时9480ms----------------
       -------------------字母j-线程09执行完毕!耗时9736ms----------------
       -------------------字母g-线程07执行完毕!耗时9750ms----------------
       -------------------字母m-线程12执行完毕!耗时10477ms---------------
       -------------------字母x-线程21执行完毕!耗时10665ms---------------
       -------------------字母y-线程22执行完毕!耗时10971ms---------------
       -------------------字母z-线程23执行完毕!耗时11395ms---------------
       -------------------字母d-线程04执行完毕!耗时11426ms---------------
       -------------------字母w-线程20执行完毕!耗时11972ms---------------
       -------------------字母h-线程08执行完毕!耗时12695ms---------------
       -------------------所有线程执行完毕!总耗时:14195ms!---------------
      
      
    • 数据库

      查询结果 27976个成语

  • 总结

    • 拓展--发现每个成语的a标签都带一个href,那么和头部拼起来的话就是成语的详情页面了!

      a标签内容

      详情页链接=https://chengyu.911cha.com/ZzB5.html

      • 有一个问题,如果是爬成语详情的话就要访问2W多个页面,时间会很长的有什么办法可以优化一下?让时间缩到最短?
    • 相关

      • java线程和线程锁

      • mysql数据库语句

      • jdbc

      • jsoup

  • 项目地址:点击查看

你可能感兴趣的:(总耗时4秒爬取2.7W+数据并存入数据库!简单的java爬虫!jsoup)