前言:有时候可能需要从网上或者某个网站收集一些数据,这时候就可以用爬虫来实现,不需要手动去收集费时费力。本文使用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代码
谷歌浏览器为例,可以按ctrl+shift+c。鼠标移动到网页任意地方,右边的代码框会显示当前鼠标停留处的代码。
网站看起来结构并不复杂,甚至还有点整齐。英文字母A-Z一字排开整齐放好了,意味着写爬虫代码时可以不用自己手动输入英文字母A-Z,后面会提到如何自动获取英文字母。
-
-
第二步
-
这是网页分页部分的html代码,会发现多页时代码中会有5个a标签。那么要计算一共有多少也可以用a标签的个数减去2就是真实页数了。
-
这个网页还存在另一种情况,比如字母o的成语中a标签只有一个那这种就不用减2了。
-
-
-
开始写代码
-
项目结构
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!---------------
-
数据库
-
-
总结
-
拓展--发现每个成语的a标签都带一个href,那么和头部拼起来的话就是成语的详情页面了!
详情页链接=https://chengyu.911cha.com/ZzB5.html
- 有一个问题,如果是爬成语详情的话就要访问2W多个页面,时间会很长的有什么办法可以优化一下?让时间缩到最短?
-
相关
java线程和线程锁
mysql数据库语句
jdbc
jsoup
-
项目地址:点击查看