内容简介:基于webmagic框架的爬虫小Demo
爬虫,往往都会想到用 python 来做,因为语言简洁,上手也方便。
但有时候就是会要求用java, 这里就推荐一个 java 爬虫框架,webmagic。
WebMagic是一个简单灵活的Java爬虫框架, 便于二次开发,提供简单灵活的API,只需少量代码即可实现一个爬虫。
详情可见: http://webmagic.io/
接下来,我们来动手实践一下吧。
目标解析
我们这个demo的目标是爬取天善最热博文列表( https://blog.hellobi.com/hot/weekly )对应的博文信息存入 mysql 数据库中。
我们需要从博文列表页获取每篇博文的url,并进入对应的url爬取博文的相关信息。
暂定的博文相关信息有:
- 博文url::url
- 博文标题::title
- 博文作者::author
- 作者博客地址::blogHomeUrl
- 博文阅读数:readNum
- 博文推荐数:recommandNum
- 博文评论数:commentNum
- 博文内容:content
- 博文发表时间:publishTime
因此,根据相应的信息建表
CREATE TABLE `hot_weekly_blogs` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `url` VARCHAR(100) DEFAULT NULL, `title` VARCHAR(100) DEFAULT NULL, `author` VARCHAR(50) DEFAULT NULL, `readNum` INT(11) DEFAULT NULL, `recommendNum` INT(11) DEFAULT NULL, `blogHomeUrl` VARCHAR(100) DEFAULT NULL, `commentNum` INT(11) DEFAULT NULL, `publishTime` VARCHAR(20) DEFAULT NULL, `content` MEDIUMTEXT, PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=69 DEFAULT CHARSET=utf8
爬虫的思路一般离不开以下三点:
1.获取目标链接
2.抽取需要的信息
3.处理数据
获取目标链接
我们需要一个入口页,从入口页获取目标链接,如上图。我们需要将目标链接加入待爬取的集合里
page.addTargetRequests(page.getHtml().xpath("//h2[@class='title']/a").links().all());
抽取需要的信息
如上图是一篇博文的结构,标出了9个所需要抽取的信息。
确定好目标之后,我们就开始敲代码了。
步骤一:新建maven工程,添加依赖
<dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-core</artifactId> <version>0.6.1</version> </dependency> <dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-extension</artifactId> <version>0.6.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.42</version> </dependency>
步骤二:创建对应的实体对象
public class BlogInfo { private String url; private String title; private String author; private String readNum; private String recommendNum; private String blogHomeUrl; private String commentNum; private String publishTime; private String content; // 篇幅太长,省略getter、setter和toString方法,可自己动手生成 }
步骤三:编写 PageProcessor
PageProcessor中的process方法是webmagic的核心,负责抽取目标url的逻辑。
package test.repo; import test.dao.BlogDao; import test.dao.impl.BlogDaoImpl; import test.entity.BlogInfo; import us.codecraft.webmagic.Page; import us.codecraft.webmagic.Site; import us.codecraft.webmagic.Spider; import us.codecraft.webmagic.processor.PageProcessor; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by Administrator on 2017/6/11. */ public class BlogPageProcessor implements PageProcessor { //抓取网站的相关配置,包括:编码、抓取间隔、重试次数等 private Site site = Site.me().setRetryTimes(10).setSleepTime(1000); //博文数量 private static int num = 0; //数据库持久化对象,用于将博文信息存入数据库 private BlogDao blogDao = new BlogDaoImpl(); public static void main(String[] args) throws Exception { long startTime ,endTime; System.out.println("========天善最热博客小爬虫【启动】喽!========="); startTime = new Date().getTime(); Spider.create(new BlogPageProcessor()).addUrl("https://blog.hellobi.com/hot/weekly?page=1").thread(5).run(); endTime = new Date().getTime(); System.out.println("========天善最热博客小爬虫【结束】喽!========="); System.out.println("一共爬到"+num+"篇博客!用时为:"+(endTime-startTime)/1000+"s"); } @Override public void process(Page page) { //1. 如果是博文列表页面 【入口页面】,将所有博文的详细页面的url放入target集合中。 // 并且添加下一页的url放入target集合中。 if(page.getUrl().regex("https://blog\\.hellobi\\.com/hot/weekly\\?page=\\d+").match()){ //目标链接 page.addTargetRequests(page.getHtml().xpath("//h2[@class='title']/a").links().all()); //下一页博文列表页链接 page.addTargetRequests(page.getHtml().xpath("//a[@rel='next']").links().all()); } //2. 如果是博文详细页面 else { // String content1 = page.getHtml().get(); try { /*实例化BlogInfo,方便持久化存储。*/ BlogInfo blog = new BlogInfo(); //博文标题 String title = page.getHtml().xpath("//h1[@class='clearfix']/a/text()").get(); //博文url String url = page.getHtml().xpath("//h1[@class='clearfix']/a/@href").get(); //博文作者 String author = page.getHtml().xpath("//section[@class='sidebar']/div/div/a[@class='aw-user-name']/text()").get(); //作者博客地址 String blogHomeUrl = page.getHtml().xpath("//section[@class='sidebar']/div/div/a[@class='aw-user-name']/@href").get(); //博文内容,这里只获取带html标签的内容,后续可再进一步处理 String content = page.getHtml().xpath("//div[@class='message-content editor-style']").get(); //推荐数(点赞数) String recommendNum = page.getHtml().xpath("//a[@class='agree']/b/text()").get(); //评论数 String commentNum = page.getHtml().xpath("//div[@class='aw-mod']/div/h2/text()").get().split("个")[0].trim(); //阅读数(浏览数) String readNum = page.getHtml().xpath("//div[@class='row']/div/div/div/div/span/text()").get().split(":")[1].trim(); //发布时间,发布时间需要处理,这一步获取原始信息 String time = page.getHtml().xpath("//time[@class='time']/text()").get().split(":")[1].trim(); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Calendar cal = Calendar.getInstance();// 取当前日期。 cal = Calendar.getInstance(); String publishTime = null; Pattern p = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}$"); Matcher m = p.matcher(time); //如果time是“YYYY-mm-dd”这种格式的,则不需要处理 if (m.matches()) { publishTime = time; } else if (time.contains("天")) { //如果time包含“天”,如1天前, int days = Integer.parseInt(time.split("天")[0].trim());//则获取对应的天数 cal.add(Calendar.DAY_OF_MONTH, -days);// 取当前日期的前days天. publishTime = df.format(cal.getTime()); //并将时间转换为“YYYY-mm-dd”这个格式 } else {//time是其他格式,如几分钟前,几小时前,都为当日日期 publishTime = df.format(cal.getTime()); } //对象赋值 blog.setUrl(url); blog.setTitle(title); blog.setAuthor(author); blog.setBlogHomeUrl(blogHomeUrl); blog.setCommentNum(commentNum); blog.setRecommendNum(recommendNum); blog.setReadNum(readNum); blog.setContent(content); blog.setPublishTime(publishTime); num++;//博文数++ System.out.println("num:" + num + " " + blog.toString());//输出对象 blogDao.saveBlog(blog);//保存博文信息到数据库 }catch (Exception e){ e.printStackTrace(); } } } @Override public Site getSite() { return this.site; } }
步骤四:处理数据
即将数据存入mysql数据库
Dao接口层
/** * 博文 数据持久化 接口 * @author Jasmine */ public interface BlogDao { /** * 保存博文信息 * @param blog * @return */ public int saveBlog(BlogInfo blog); }
Dao实现类
/** * 博客 数据库持久化接口 实现 * @author Jasmine */ public class BlogDaoImpl implements BlogDao{ @Override public int saveBlog(BlogInfo blog) { DBHelper dbhelper = new DBHelper(); StringBuffer sql = new StringBuffer(); sql.append("INSERT INTO hot_weekly_blogs(url,title,author,readNum,recommendNum,blogHomeUrl,commentNum,publishTime,content)") .append("VALUES (? , ? , ? , ? , ? , ? , ? , ? , ? ) "); //设置 sql values 的值 List<String> sqlValues = new ArrayList<>(); sqlValues.add(blog.getUrl()); sqlValues.add(blog.getTitle()); sqlValues.add(blog.getAuthor()); sqlValues.add(""+blog.getReadNum()); sqlValues.add(""+blog.getRecommendNum()); sqlValues.add(blog.getBlogHomeUrl()); sqlValues.add(""+blog.getCommentNum()); sqlValues.add(blog.getPublishTime()); sqlValues.add(blog.getContent()); int result = dbhelper.executeUpdate(sql.toString(), sqlValues); return result; } }
步骤五:编写数据库 工具 类
这个工具类是采自网络,后续会改用mybaits来持久化。
public class DBHelper { public static final String driver_class = "com.mysql.jdbc.Driver"; public static final String driver_url = "jdbc:mysql://localhost/spider?useunicode=true&characterEncoding=utf8"; public static final String user = "root"; public static final String password = "admin123"; private static Connection conn = null; private PreparedStatement pst = null; private ResultSet rst = null; /** * Connection */ public DBHelper() { try { conn = DBHelper.getConnInstance(); } catch (Exception e) { e.printStackTrace(); } } /** * 单例模式 * 线程同步 * @return */ private static synchronized Connection getConnInstance() { if(conn == null){ try { Class.forName(driver_class); conn = DriverManager.getConnection(driver_url, user, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } System.out.println("连接数据库成功"); } return conn; } /** * close */ public void close() { try { if (conn != null) { DBHelper.conn.close(); } if (pst != null) { this.pst.close(); } if (rst != null) { this.rst.close(); } System.out.println("关闭数据库成功"); } catch (SQLException e) { e.printStackTrace(); } } /** * query * * @param sql * @param sqlValues * @return ResultSet */ public ResultSet executeQuery(String sql, List<String> sqlValues) { try { pst = conn.prepareStatement(sql); if (sqlValues != null && sqlValues.size() > 0) { setSqlValues(pst, sqlValues); } rst = pst.executeQuery(); } catch (SQLException e) { e.printStackTrace(); } return rst; } /** * update * * @param sql * @param sqlValues * @return result */ public int executeUpdate(String sql, List<String> sqlValues) { int result = -1; try { pst = conn.prepareStatement(sql); if (sqlValues != null && sqlValues.size() > 0) { setSqlValues(pst, sqlValues); } result = pst.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } return result; } /** * sql set value * * @param pst * @param sqlValues */ private void setSqlValues(PreparedStatement pst, List<String> sqlValues) { for (int i = 0; i < sqlValues.size(); i++) { try { pst.setObject(i + 1, sqlValues.get(i)); } catch (SQLException e) { e.printStackTrace(); } } } }
本文由简单的happy 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 超级易懂爬虫系列之爬虫框架scrapy
- python网络爬虫(14)使用Scrapy搭建爬虫框架
- 一个咸鱼的python爬虫之路(五):scrapy 爬虫框架
- 11、web爬虫讲解2—Scrapy框架爬虫—Scrapy使用
- Scrapy框架-----爬虫
- 网络爬虫框架开发笔记
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入React技术栈
陈屹 / 人民邮电出版社 / 2016-11-1 / CNY 79.00
全面讲述React技术栈的第一本原创图书,pure render专栏主创倾力打造 覆盖React、Flux、Redux及可视化,帮助开发者在实践中深入理解技术和源码 前端组件化主流解决方案,一本书玩转React“全家桶” 本书讲解了非常多的内容,不仅介绍了面向普通用户的API、应用架构和周边工具,还深入介绍了底层实现。此外,本书非常重视实战,每一节都有实际的例子,细节丰富。我从这......一起来看看 《深入React技术栈》 这本书的介绍吧!