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

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

  • 前置条件:

  • 准备工作

    • 寻找目标网站

    • 在数据库中新建一个表

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

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

  • 分析目标网站

    • 第一步

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

        F12界面

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

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

    • 第二步

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

          <div class="gclear pp bt center f14">
            <span class="gray">首页</span>
            <a href="pinyin_a_p3.html">末页</a>
            <span class="gray">|</span>
            <a href="pinyin_a.html" class="red noline">1</a>
            <a href="pinyin_a_p2.html">2</a>
            <a href="pinyin_a_p3.html">3</a>
            <span class="gray">|</span>
            <span class="gray">上一页</span>
            <a href="pinyin_a_p2.html">下一页</a>
          </div>
        
      • 这个网页还存在另一种情况,比如字母o的成语中a标签只有一个那这种就不用减2了。

          <div class="gclear pp bt center f14">
            <span class="gray">首页</span>
            <span class="gray">末页</span>
            <span class="gray">|</span>
            <a href="pinyin_o.html" class="red noline">1</a>
            <span class="gray">|</span>
            <span class="gray">上一页</span>
            <span class="gray">下一页</span>
          </div>
        
  • 开始写代码

    • 项目结构

      项目结构
    • 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<bean.Idioms> 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<bean.Idioms> getAllIdiomList(String letter) {
            List<bean.Idioms> 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<bean.Idioms> getIdiomList(String letter, int page) {
            List<bean.Idioms> 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<String> 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<String> getLetterList() {
            List<String> 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

  • 项目地址:点击查看

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容