基于webmagic框架的爬虫小Demo

栏目: 编程工具 · 发布时间: 7年前

内容简介:基于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.处理数据

获取目标链接

基于webmagic框架的爬虫小Demo

我们需要一个入口页,从入口页获取目标链接,如上图。我们需要将目标链接加入待爬取的集合里

page.addTargetRequests(page.getHtml().xpath("//h2[@class='title']/a").links().all());

抽取需要的信息

基于webmagic框架的爬虫小Demo

如上图是一篇博文的结构,标出了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();

            }

        }

    }

}

基于webmagic框架的爬虫小Demo

基于webmagic框架的爬虫小Demo

基于webmagic框架的爬虫小Demo 基于webmagic框架的爬虫小Demo

本文由简单的happy 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。

转载、引用前需联系作者,并署名作者且注明文章出处。

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入React技术栈

深入React技术栈

陈屹 / 人民邮电出版社 / 2016-11-1 / CNY 79.00

全面讲述React技术栈的第一本原创图书,pure render专栏主创倾力打造 覆盖React、Flux、Redux及可视化,帮助开发者在实践中深入理解技术和源码 前端组件化主流解决方案,一本书玩转React“全家桶” 本书讲解了非常多的内容,不仅介绍了面向普通用户的API、应用架构和周边工具,还深入介绍了底层实现。此外,本书非常重视实战,每一节都有实际的例子,细节丰富。我从这......一起来看看 《深入React技术栈》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具