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