Java爬虫学习(安居客)

Java爬虫学习(安居客)Java爬虫学习最近看着搭档使用python爬虫,觉得手痒。然后感觉自己学习java,应该也可以爬虫。就去百度学习了一下java的爬虫框架。国内有几种开源爬虫框架:gecco、WebMagic等。gecco学习文档:http://www.geccocrawler.com/tag/sysc/WebMagic:http://webmagic.io/docs/zh/因为我学习的是gecc…

Java爬虫学习

最近看着搭档使用python爬虫,觉得手痒。然后感觉自己学习java,应该也可以爬虫。就去百度学习了一下java的爬虫框架。国内有几种开源爬虫框架:gecco、WebMagic等。
gecco学习文档:

http://www.geccocrawler.com/tag/sysc/

WebMagic:

http://webmagic.io/docs/zh/

因为我学习的是gecco,所以个人感觉这框架入门爬虫还是挺简单的。后面也会对WebMagic进行学习。

爬虫案例

我使用gecco框架去爬取了安居客的房屋信息。

https://shanghai.anjuke.com/sale/a8-p1/#filtersort

爬取的内容主要是每一页的房屋列表和单个房屋的信息。
在这里插入图片描述
爬取思路:先获取到当前页面的所有房屋列表,然后爬取单个房屋信息,爬取完成再爬取下一页房屋列表,获取房屋信息。
我在爬取的时候,因为要解析数据,一直请求网页,怕出事哈哈,我就先把网页(ctrl+s)保存到本地。然后用tomcat跑起来。再去请求爬取本地的跑起来的网址

http://localhost:8081/a1.html

爬取的效果和爬取下面的一致:

https://shanghai.anjuke.com/sale/a8-p1/#filtersort

编写爬虫启动入口
我新建的是maven项目,所以要使用Gecco,第一步是添加maven依赖


<dependency>
    <groupId>com.geccocrawler</groupId>
    <artifactId>gecco</artifactId>
    <version>1.0.8</version>
</dependency>

然后编写一个启动类作为爬虫的入口

package cn.demo.gecco.starter;

import com.geccocrawler.gecco.GeccoEngine;
import com.geccocrawler.gecco.request.HttpGetRequest;


import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName:
 * @PackageName: cn.demo.gecco.starter
 * @author: youjp
 * @create: 2019-11-04 08:39
 * @description: 爬虫启动类
 * @Version: 1.0
 */
public class StartClass {

    public static void main(String[] args) {

        System.out.println("=========start===============");
        HttpGetRequest startUrl = new HttpGetRequest("http://localhost:8081/a1.html");
        //HttpGetRequest startUrl = new HttpGetRequest("https://shanghai.anjuke.com/sale/a8-p1/#filtersort");
        //HttpGetRequest startUrl= new HttpGetRequest("https://zhaotong.anjuke.com/sale/rd1/?kw=&from=sugg");
        startUrl.setCharset("utf-8");
        startUrl.setHeaders(setHeaders());
        GeccoEngine.create()    //Gecco搜索包路径
                .classpath("cn.demo.gecco.geccobean")
                .start(startUrl)
                .thread(1)    //开启几个爬虫线程
                .interval(5000)     //单个爬虫每次抓取完一个请求后的间隔时间
                .run();
    }

    /**
     * @return java.util.Map<java.lang.String               ,               java.lang.String>
     * @Param []
     * @Author youjp
     * @Description //TODO 设置请求头
     * @throw
     **/
    public static Map<String, String> setHeaders() {
        Map<String, String> setHeaders = new HashMap<>();
        setHeaders.put("Accept", "text/html,application/xhtml+xml," +
                "application/xml;q=0.9,image/webp,*/*;q=0.8");

        setHeaders.put("Accept-Encoding", "gzip, deflate, sdch, br");
        setHeaders.put("Accept-Language", "zh-CN,zh;q=0.8");
        setHeaders.put("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" +
                " (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36");
        return setHeaders;
    }
}


  • HttpGetRequest用于包裹种子网站,同时可以设置编码,这里设置的是“utf-8”(一开始当时没有设置该参数时,爬出的文本都是乱码的)
  • classpath是一个扫描路径,类似于Spring中的component-scan,用于扫描注解的类。这里主要用于扫描注解“@Gecco”所在的类。

获取所有房屋列表

package cn.demo.gecco.geccobean;

import com.geccocrawler.gecco.annotation.Gecco;
import com.geccocrawler.gecco.annotation.Href;
import com.geccocrawler.gecco.annotation.HtmlField;
import com.geccocrawler.gecco.annotation.Request;
import com.geccocrawler.gecco.request.HttpRequest;
import com.geccocrawler.gecco.spider.HtmlBean;

import java.util.List;

/**
 * @ClassName:
 * @PackageName: cn.demo.gecco.geccobean
 * @author: youjp
 * @create: 2019-11-04 08:54
 * @description: 	接口HtmlBean:说明该类是一个解析html页面的爬虫
 *      			注解@Gecco:告知该爬虫匹配的url格式(matchUrl)和
 *      			内容抽取后的bean处理类(pipelines处理类采用管道过滤器模式,可以定义多个处理类)。
 *      
 * @Version: 1.0
 */
@Gecco(matchUrl = "http://localhost:8081/a1.html", pipelines = {"consolePipeline", "allSortPipeline"})
public class AllSort implements HtmlBean {


    @Request
    private HttpRequest request;


 /**
     * 解析到的所有含房屋信息的li标签
     */
    @HtmlField(cssPath = "#houselist-mod-new li")
    private List<HouseInfo> houseInfoList;


    /**
     * 下一页按钮的url地址(a标签的href)
     */
    @Href
    @HtmlField(cssPath = "#content > div.sale-left > div.multi-page > a.aNxt")
    private String nextPage;


    public HttpRequest getRequest() {
        return request;
    }

    public void setRequest(HttpRequest request) {
        this.request = request;
    }

    public List<HouseInfo> getHouseInfoList() {
        return houseInfoList;
    }

    public void setHouseInfoList(List<HouseInfo> houseInfoList) {
        this.houseInfoList = houseInfoList;
    }

    public String getNextPage() {
        return nextPage;
    }

    public void setNextPage(String nextPage) {
        this.nextPage = nextPage;
    }


}

  • 注解@Gecco告知该爬虫匹配的url格式(matchUrl)和内容抽取后的bean处理类(pipelines处理类采用管道过滤器模式,可以定义多个处理类),这里matchUrl就是http://news.iresearch.cn/,意为从这个网址对应的页面中解析
  • 这里pipelines参数可以添加多个管道处理类,意为下一步该执行哪些管道类,需要说明的是consolePipeline,是专门将过程信息输出到控制台的管道类
  • 注解@HtmlField表示抽取html中的元素,cssPath采用类似jquery的css selector选取元素
  • @HtmlField(cssPath = “#content > div.sale-left > div.multi-page > a.aNxt”)
    代表根据这个cssPath路径获取得到下一页这个按钮元素
  • @Href 表示该字段是一个链接类型的元素,jsoup会默认获取元素的href属性值。属性必须是String类型。
    value:默认获取href属性值,可以多选,按顺序查找
    click:表示是否点击打开,继续让爬虫抓取

举例说明,现在需要解析所有的房屋列表并将列表结果包装为一个list,供后面进一步解析列表的具体内容。

    /**
     * 解析到的所有含房屋信息的li标签
     */
    @HtmlField(cssPath = "#content > div.sale-left  #houselist-mod-new li")
    private List<HouseInfo> houseInfoList;

这里cssPath是用于指定需要解析的目标元素的css位置。
如何获取这个区块的位置,先看页面
在这里插入图片描述

我们要获取的是当前页面下的所有列表,并将其包装为一个list集合。打开Chrome开发者工具(键盘上按F12),可以看到该列表模块被ul标签包裹,只要定位到该模块的位置即可。

如果通过人肉的方式获取cssPath确实有点伤眼,所以我们可以使用Chrome自带的工具获取css路径,在上图箭头所在位置右键,按照如下图所示操作,粘贴即可得到cssPath

#houselist-mod-new > li:nth-child(1)

我们获取到的是ul下的第一个li,因为要获取所有,所以将它更改为:

#houselist-mod-new > li

获取单个房屋信息的标题、价格、地址、跳转详情url等
获取这些信息的原理和上面一样,只要F12打开调试,获取到对应元素。
在这里插入图片描述
然后再选择获取css路径,这里我们获取一下标题的css Selector
在这里插入图片描述
具体获取到的房屋信息类如下:

package cn.demo.gecco.geccobean;

import com.geccocrawler.gecco.annotation.Href;
import com.geccocrawler.gecco.annotation.HtmlField;
import com.geccocrawler.gecco.annotation.Text;
import com.geccocrawler.gecco.spider.HtmlBean;

/**
 * @ClassName:  HouseInfo
 * @PackageName: cn.demo.gecco.geccobean
 * @author: youjp
 * @create: 2019-11-06 14:01
 * @description: TODO 房屋信息
 * @Version: 1.0
 */
public class HouseInfo  implements HtmlBean {


    /**
     * 标题
     */
    @Text
    @HtmlField(cssPath = "div.house-details > div.house-title > a")
    private String housName;

    /**
     * 价格
     */
    @Text
    @HtmlField(cssPath = "div.pro-price > span.price-det > strong")
    private String price;

    /**
     * 每平方
     */
    @Text
    @HtmlField(cssPath = " div.pro-price > span.unit-price")
    private String perSquare;


    /**
     * 住房类型
     */
    @Text
    @HtmlField(cssPath = "div.house-details > div:nth-child(2) > span:nth-child(1)")
    private String type;


    /**
     * 房屋大小
     */
    @Text
    @HtmlField(cssPath = " div.house-details > div:nth-child(2) > span:nth-child(3)")
    private String size;

    /**
     * 地址
     */
    @Text
    @HtmlField(cssPath = "div.house-details > div:nth-child(3) > span")
    private String address;


    @Href
    @HtmlField(cssPath = "div.house-title > a")
    private String url;

    public String getHousName() {
        return housName;
    }

    public void setHousName(String housName) {
        this.housName = housName;
    }

    public String getPrice() {
        return price+"万";
    }

    public void setPrice(String price) {
        this.price = price;
    }

    public String getPerSquare() {
        return perSquare;
    }

    public void setPerSquare(String perSquare) {
        this.perSquare = perSquare;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getSize() {
        return size;
    }

    public void setSize(String size) {
        this.size = size;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    @Override
    public String toString() {
        return "HouseInfo{" +
                "housName='" + housName + '\'' +
                ", price='" + price + '\'' +
                ", perSquare='" + perSquare + '\'' +
                ", type='" + type + '\'' +
                ", size='" + size + '\'' +
                ", address='" + address + '\'' +
                ", url='" + url + '\'' +
                '}';
    }
}

因为houseInfo(房屋信息)是在li标签里查找到的,所有他们查找的真是cssPath应该是在li以下,如标题写的是:

    /**
     * 标题
     */
    @Text
    @HtmlField(cssPath = "div.house-details > div.house-title > a")
    private String housName;

那他的父级是li,而li那我们已经写了一个cssPath路径

 /**
     * 解析到的所有含房屋信息的li标签
     */
    @HtmlField(cssPath = "#houselist-mod-new li")
    private List<HouseInfo> houseInfoList;

组合起来后,所查找的标题全路径是:

#houselist-mod-new li div.house-details > div.house-title > a

这样就便于我们理解。

下面实现AllSortPipeline类,用于进行数据处理。我这里只是输出显示

package cn.demo.gecco.geccobean;

import com.geccocrawler.gecco.annotation.PipelineName;
import com.geccocrawler.gecco.pipeline.Pipeline;
import com.geccocrawler.gecco.request.HttpRequest;
import com.geccocrawler.gecco.scheduler.SchedulerContext;
import org.springframework.util.StringUtils;

/**
 * @ClassName:
 * @PackageName: cn.demo.gecco.geccobean
 * @author: youjp
 * @create: 2019-11-06 11:00
 * @description:  todo 后续业务处理单元
 * @Version: 1.0
 */
@PipelineName("allSortPipeline")
public class AllSortPipeline  implements Pipeline<AllSort> {

    private static Integer num=0;

    @Override
    public void process(AllSort allSort) {
        System.out.println("==========单页房屋信息处理管道==========start"+num++);

        for(int i=0;i<allSort.getHouseInfoList().size();i++){
            HttpRequest currRequest = allSort.getRequest();
            System.out.println(allSort.getHouseInfoList().get(i));    //获取单个房屋信息
            HouseInfo houseInfo=allSort.getHouseInfoList().get(i);
            //
            if(!StringUtils.isEmpty(houseInfo.getUrl())){
                //加入队列
               // SchedulerContext.into(currRequest.subRequest(houseInfo.getUrl()));
            }
        }

        //请求下一页
        HttpRequest currRequest = allSort.getRequest();
        SchedulerContext.into(currRequest.subRequest(allSort.getNextPage()));
        System.out.println("==========单页房屋信息处理管道==========end"+num);
    }
}


  • 通过遍历的方式获取具体的房屋信息
  • 将url信息存储到SchedulerContext上下文中,用于后面爬虫

到此为止,我们获取了所有的分类列表对应的url信息,并将url存储到上下文中,用于后续爬虫匹配。

解析下一页

package cn.demo.gecco.geccobean;

import com.geccocrawler.gecco.annotation.*;
import com.geccocrawler.gecco.request.HttpRequest;
import com.geccocrawler.gecco.spider.HtmlBean;

import java.util.List;

/**
 * @ClassName:
 * @PackageName: cn.demo.gecco.geccobean
 * @author: youjp
 * @create: 2019-11-06 15:12
 * @description: TODO 获取所有房屋列表
 * 接口HtmlBean:说明该类是一个解析html页面的爬虫
 * 注解@Gecco:告知该爬虫匹配的url格式(matchUrl)和
 * 内容抽取后的bean处理类(pipelines处理类采用管道过滤器模式,可以定义多个处理类)。
 *
 * @Version: 1.0
 */
@Gecco(matchUrl = "https://shanghai.anjuke.com/sale/{data}/", pipelines = {"nextPageSortPipeline"})
public class NextPageSort implements HtmlBean {


    @Request
    private HttpRequest request;

    @HtmlField(cssPath = "#content > div.sale-left  #houselist-mod-new li")
    private List<HouseInfo> houseInfoList;

    /**
     * 下一页 url
     */
    @Href
    @HtmlField(cssPath = "#content > div.sale-left > div.multi-page > a.aNxt")
    private String nextPage;

    @RequestParameter
    private String data;

    public HttpRequest getRequest() {
        return request;
    }

    public void setRequest(HttpRequest request) {
        this.request = request;
    }

    public List<HouseInfo> getHouseInfoList() {
        return houseInfoList;
    }

    public void setHouseInfoList(List<HouseInfo> houseInfoList) {
        this.houseInfoList = houseInfoList;
    }

    public String getNextPage() {
        return nextPage;
    }

    public void setNextPage(String nextPage) {
        this.nextPage = nextPage;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

代码和之前解析完成一样,由于我解析的是本地页面的原因。如果请求的是网上的,用 这个解析下一页的处理类就可以了

下一页数据处理

package cn.demo.gecco.geccobean;

import com.geccocrawler.gecco.annotation.PipelineName;
import com.geccocrawler.gecco.pipeline.Pipeline;
import com.geccocrawler.gecco.request.HttpRequest;
import com.geccocrawler.gecco.scheduler.SchedulerContext;
import org.springframework.util.StringUtils;

/**
 * @ClassName:
 * @PackageName: cn.demo.gecco.geccobean
 * @author: youjp
 * @create: 2019-11-06 11:00
 * @description:  todo 后续业务处理单元
 * @Version: 1.0
 */
@PipelineName("nextPageSortPipeline")
public class NextPageSortPipeline implements Pipeline<NextPageSort> {

    private static Integer num=0;

    @Override
    public void process(NextPageSort nextPageSort) {
        System.out.println("==========单页房屋信息处理管道==========start"+num++);

        for(int i=0;i<nextPageSort.getHouseInfoList().size();i++){
            HttpRequest currRequest = nextPageSort.getRequest();
            System.out.println(nextPageSort.getHouseInfoList().get(i));    //获取单个房屋信息
            HouseInfo houseInfo=nextPageSort.getHouseInfoList().get(i);
            //
            if(!StringUtils.isEmpty(houseInfo.getUrl())){
                //加入队列
               // SchedulerContext.into(currRequest.subRequest(houseInfo.getUrl()));
            }
        }

        //请求下一页
        HttpRequest currRequest = nextPageSort.getRequest();
        SchedulerContext.into(currRequest.subRequest(nextPageSort.getNextPage()));
        System.out.println("==========单页房屋信息处理管道==========end"+num);
    }
}

每爬取完成一次页面数据,就去把下一页按钮的url地址加入请求队列,然后开始新一个页面的数据解析。这样就完成一次循环解析页面,直到把所有分页数据解析完成。

解析效果:
在这里插入图片描述
关注【一起收破烂】回复【003】可获取爬虫源码,回复【006】获取2021最新java面试资料以及简历模型120套哦~**
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/38151.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注