爬虫之爬取易班推文信息

20 篇文章 0 订阅
6 篇文章 0 订阅

简介

趁着周末有空,最近又在做爬虫相关的功能,想将易班的推文信息放到自己的小程序里,奈何网上没有找到合适的文章,于是就自己研究了一下易班推文的爬取,使用Springboot设置定时任务去爬取,在爬取信息成功之后打印爬虫的信息。

该教程仅用于学习用途,如有侵犯您的权益,请联系我删除。

效果图:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

广告:
广告:
个人博客,网页版:palewl.cn

小程序端: 培正Lite

源码:码云直达

使用技术:java为前提,使用HttpClient和Jsoup。
使用工具:结合psotMan进行测试

如果你对爬虫登录有兴趣,可以看我上一篇模拟登录的案例:
爬虫之模拟强智系统登录

分析

首先找到需要爬取的页面地址:

http://www.yiban.cn/school/index/puserid/13712362
这里以广东培正学院的易班首页为例,爬取文章信息

在这里插入图片描述

随便打开一篇文章,如打开:植树节,为地球绘上一点绿色这篇文章,打开f12调试,找到如图所示的请求

在这里插入图片描述
在这里插入图片描述

请求地址:http://www.yiban.cn/forum/article/show/article_id/155610598/channel_id/301700/puid/1371236

链接参数分析:

三个参数都有特定的意义,这里先记录一下。

article_id:155610598 //文章的id
    channel_id:301700 //频道的id
    puid:1371236 //作者的id

这个地址是点击文章后访问的地址,但是打开response发现文章的内容并不在这里。

其实也很容易想到,一般这种情况就可以认为该页面是通过异步的方式来请求数据的。

继续查找:

在这里插入图片描述

发现连接:
http://www.yiban.cn/forum/article/showAjax
提交方式:POST
表单内容:

在这里插入图片描述

试着去postMan尝试一下:

在这里插入图片描述

结果是正确的。

因此这里爬取推文的关键就是得到这三个参数,然后通过这三个参数发起异步请求,从而得到数据。

	article_id:155610598 //文章的id
    channel_id:301700 //频道的id
    puid:1371236 //作者的id

还有个小问题

再次请求这个页面:http://www.yiban.cn/school/index/puserid/13712362

这里的放假通知的地址:
http://www.yiban.cn/forum/schoolAd/articleShow/id/3244805

                                        </a>

在这里插入图片描述

跟植树这个地址:
http://www.yiban.cn/forum/article/show/article_id/155610598/channel_id/301700/puid/13712362

在这里插入图片描述

链接格式完全不一样,第一个只有简单的id/3244805结尾,但是第二个地址却包含了需要发起异步请求的所有参数。

但是当点击的时候却发现地址:

在这里插入图片描述

显然发生了重定向,重定向后的地址才是我们需要的。

思路实现

1.搞清楚两个地址,一个是需要爬取的地址:
添加链接描述
通过这个地址需要获得发起异步请求的三个参数。

另一个是需要发起异步请求的地址:
请求地址: http://www.yiban.cn/forum/article/showAjax
请求方式: POST

2.根据发起的异步请求,需要准备几个实体对象用于接收异步请求返回的结果。

在这里插入图片描述

3.准备工具类,使用池的方式管理HttpClient

在这里插入图片描述

4.使用SpringBoot的定时任务,定时爬取数据,将爬取到的结果打印到控制台。
如你有存入到数据库的需求,这个也是一样的。

在这里插入图片描述

代码实现

相关的代码已经放到码云,你也可以clone下来查看

创建一个maven工程,导入依赖。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
</parent>
<dependencies>
	
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--阿里巴巴处理json工具-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.51</version>
    </dependency>
    
    <!--httpclient-->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
    </dependency>
    
    <!--jsoup 处理爬虫文档-->
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.10.3</version>
    </dependency>
    <!--lang3工具类-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>


</dependencies>

准备实体对象用于接收结果

Arti

/**
 * 临时存储第一部分爬虫的基础信息,非数据库实体表
 */
public class Arti implements Serializable {
    private String title; //标题
    private String src; //推文图片地址
    private String href; //推文访问地址
    private String article_id;//文章id
    //get set ...
    }
/**
 * 文章实体,重要的实体部分,如有需要可以作为数据库部分
 */
public class Article implements Serializable {

    private String id;
    private String Channel_id;
    private String User_id;//作者id
    private String title;
    private String firstPicture;//首图地址
    private String isNotice;
    private String isLocked;
    private String isWeb;
    private String replyCount; //回复数
    private String replyTime;//回复时间
    private String createTime;
    private String updateTime;
    private String clicks;//点击数
    private String upCount;
    private String status;
    private String Sections_id;
    private String UserGroup_id;
    private String oldArtId;
    private String oldAreaId;
    private String content; //推文内容
    //get set ...
    }
/**
 * 作者实体对象,重要的实体部分,如有需要可以作为数据库部分
 */
public class Author implements Serializable {

    private String id;
    private String isPublic;
    private String isOrganization;
    private String isDeveloper;
    private String isSchoolVerify;
    private String regTime;
    private String kind;
    private String nick;//作者昵称
    private String name;//作者姓名
    private String sex;
    private String birth;
    private String source;
    private String major;
    private String img;
    private String userName;
    private String area;
    private String addr;
    private String authority;
    private String group_id;
    private String qr_add;
    private String qr_index;
    private String token;
    private String homeUrl;
    private String iosHomeUrl;
    private String androidHomeUrl;
    private String authorname;
    private String avatar;//头像地址
    //get set ...
    }
/**
 * 异步返回的返回的Data数据
 */
public class Data  implements Serializable {

    private String id;

    private String baseUri;
    private String jsBaseUrl;
    private String cssBaseUrl;
    private String imgBaseUrl;
    private String channel_id;
    private String puid;
    private String isManager;
    private String jsVersion;
    private String cssVersion;
    private String isOrganization;
    private String SectionFlag;
    private Boolean editPermission;
    private Boolean delPermission;
    private String  hotScore;
    private String  Sections_name;

    //外键
    private String articleId;
    //非数据库字段

    private Article article;

   //非数据库字段
    private UrlParams urlParams;
    //get set ...
    }
/**
 * 异步请求返回的结果集
 */
public class Result  implements Serializable {

    private String id;
    private String code; //返回的状态码
    private String message;//提示消息
    private Data data;
    //get set ...
    }
/**
 * 文章的id信息
 */

public class UrlParams implements Serializable {


    private String id; //id
    private String channel_id;//频道id
    private String puid;//作者id
    //get set ...
    }

准备工具类:

import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

/**
 * HttpClient的工具类,使用连接池的方式管理
 */
@Component
public class ClientUtils {
    private PoolingHttpClientConnectionManager cm;

    public ClientUtils() {
        this.cm = new PoolingHttpClientConnectionManager();

        //    设置最大连接数
        cm.setMaxTotal(200);

        //    设置每个主机的并发数
        cm.setDefaultMaxPerRoute(20);
    }

    //获取内容
    public String getHtml(String url) {
        // 获取HttpClient对象
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();

        // 声明httpGet请求对象
        HttpGet httpGet = new HttpGet(url);
        // 设置请求参数RequestConfig
        httpGet.setConfig(this.getConfig());
        //httpGet.addHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36");
        CloseableHttpResponse response = null;
        try {
            // 使用HttpClient发起请求,返回response
            response = httpClient.execute(httpGet);
            // 解析response返回数据
            if (response.getStatusLine().getStatusCode() == 200) {
                String html = "";

                // 如果response。getEntity获取的结果是空,在执行EntityUtils.toString会报错
                // 需要对Entity进行非空的判断
                if (response.getEntity() != null) {
                    html = EntityUtils.toString(response.getEntity(), "UTF-8");
                }

                return html;
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    // 关闭连接
                    response.close();
                }
                // 不能关闭,现在使用的是连接管理器
                // httpClient.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return null;
    }


    //获取请求参数对象
    private RequestConfig getConfig() {
        RequestConfig config = RequestConfig.custom().setConnectTimeout(2000)// 设置创建连接的超时时间
                .setConnectionRequestTimeout(500) // 设置获取连接的超时时间
                .setSocketTimeout(10000) // 设置连接的超时时间
                .build();

        return config;
    }
}

设置定时任务:

/**
 * 设置定时任务去爬取易班的内容
 */
@Component
@Configuration
@EnableScheduling
public class TaskArticle {
    //工具类
    @Autowired
    private ClientUtils clientUtils;


    /**
     * 设置定时任务去爬虫
     * 第一次执行前延时5秒启动
     *  24*60*60*1000  每隔半天天去爬取数据
     */

    @Scheduled(initialDelay = 1000, fixedDelay = 12*60*60*1000)
    public void taskArticle0(){
        System.out.println("执行了定时任务.");
        String uri = "http://www.yiban.cn/school/index/puserid/13712362";
        //开始爬虫0,爬取首页数据
       getMap(clientUtils, uri);
    }



    /**
     * 根据地址去获取http://www.yiban.cn/school/index/puserid/13712362
     * 的文章,得到初步的信息
     * System.out.println(src);//图片资源
     * System.out.println(href);//a标签地址
     * System.out.println(title);//标题
     * System.out.println(article_id);//文章id
     * @param clientUtils
     * @param uri
     * @return
     */
    private  void getMap(ClientUtils clientUtils, String uri){

        //得到响应内容
        String html = clientUtils.getHtml(uri);
        //使用jsoup解析
        Document parse = Jsoup.parse(html);
        //解析div.section-body > ul > li路径下的内容
        Elements clearfix = parse.select("div.section-body > ul > li");
        //循环遍历
        for (Element element : clearfix) {
            //获取a标签下的内容
            Elements a = element.select("a");
            //获取图片
            String src = a.select("img").attr("src");
            //获取a标签下的跳转href
            String href = a.attr("href");
            //获取文章尾数编号
            String article_id = "";
            try {
                article_id = href.substring(href.indexOf("article_id/")+11,href.indexOf("/channel_id"));
            } catch (Exception e) {
            //    System.out.println("防止报错");
            }
            //获取标题
            String title = a.select("span").select("b").text();

            //封装结果
            Arti arti = new Arti();
            arti.setTitle(title);
            arti.setHref(href);
            arti.setArticle_id(article_id);
            arti.setSrc(src);
            //将结果加入集合

            //调用方法继续执行
            getElement(arti);

        }

    }

    /**
     * 根据爬取的内容来去进一步的加载
     */
    private  String getElement(Arti arti){
       // System.out.println("arti:"+arti);
        if(arti != null){
            //如果文章id不为空,则说明无需重定向处理,否则就需要重定向处理
            if (arti.getArticle_id() != null && !"".equals(arti.getArticle_id())){
                //调用方法继续执行
                redict(arti);
            }else{
                //调用方法继续执行,获取重定向后得到的链接
             //   System.out.println("执行了重定向方法");
                redirect(arti);
            }


        }
        return "";
    }

    /**
     * 需要进一步重定向处理地址
     * 获取重定向后得到的链接,然后继续调用处理
     * @param arti
     */
    private  void redirect(Arti arti) {
        //创建连接对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        List<URI> redirectLocations = null; //返回的重定向的地址集合
        CloseableHttpResponse response = null;
        try {
            HttpClientContext context = HttpClientContext.create();
            HttpGet httpGet = new HttpGet(arti.getHref()); //使用href去获取重定向地址
            httpGet.addHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36");
            response = httpClient.execute(httpGet, context);
            // 获取所有的重定向位置
            redirectLocations = context.getRedirectLocations();
          //  System.out.println("redirectLocations:"+redirectLocations);
           // System.out.println("getHref():"+arti.getHref());
            String uri = redirectLocations.get(0).toString();//返回的重定向地址
            String article_id = uri.substring(uri.lastIndexOf("/") + 1);//截取正确的文章id
         //   System.out.println("重定向后的文章id"+article_id);

            //重新封装结果集
            arti.setArticle_id(article_id);
            arti.setHref(uri);
            //调用方法继续执行
            System.out.println(arti);
            redict(arti);
        } catch (IOException e) {
        //    System.out.println("redirect方法报错");
        }

    }

    /**
     * 无需重定向直接处理爬虫,获取响应id文章的内容
     * @param arti
     */
    private  void redict(Arti arti) {
     //   System.out.println("非重定向:"+arti);
        //封装结果
        Map<String,String> map = new HashMap<>();
        map.put("channel_id", "301700");
        map.put("puid", "13712362");
        map.put("article_id", arti.getArticle_id());
        //发起HTTP请求
        String s = HttpClientUtil.doPost("http://www.yiban.cn/forum/article/showAjax",map);

        //将结果转换为JavaBean
        JSONObject jsonObject = JSONObject.parseObject(s);
        Result result = JSON.toJavaObject(jsonObject, Result.class);



        //将结果存入数据库 Data
        Data data = result.getData();


        Article article = null;
        Author author = null;
        UrlParams urlParams = null;
        String articleId = "";

        try {
            //文章
            article = data.getArticle();
            articleId = article.getId();
            article.setId(arti.getArticle_id());

            //修改时间格式
            try {
                if(article.getCreateTime().startsWith("2020") ||
                        article.getCreateTime().startsWith("2019")||
                        article.getCreateTime().startsWith("2018")||
                        article.getCreateTime().startsWith("2017")||
                        article.getCreateTime().startsWith("2016")||
                        article.getCreateTime().startsWith("2015")||
                        article.getCreateTime().startsWith("2021")){
                  //如果是以 以上前缀执行的,则不进行处理
                }else{
                    //否则需要处理时间
                    //获取当前的年份
                    Date date = new Date();
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy");
                    String year = simpleDateFormat.format(date);
                    String createTime = article.getCreateTime();
                    //赋值时间
                    article.setCreateTime(year+"-"+createTime);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            //作者信息
            author = article.getAuthor();

            //url信息
            urlParams = data.getUrlParams();


            /**
             * 打印结果
             */
            System.out.println("urlParams:"+urlParams);
            System.out.println("作者信息:"+author);
            System.out.println("文章信息:"+article);

        } catch (Exception e) {
            System.out.println("错误,跳过。");
        }


    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值