WebMagic——多线程,多深度数据爬取整合
注:
此文章主要解决以下应用场景,在使用webmagic框架时,会存在多线程爬取数据,这就导致了在不同页面中爬取到的数据无法整合为一条,因为你在爬取A页面的时候,你其实也在爬取通过A页面点进去的B页面,但是这其实是一条数据,应该在存储时进行整合。
应用场景:
这是A页面,也就是第一层页面,通多A页面点击 “北京岭秀” 进入B页面
进入如下B页面后,需要抓取,楼盘的别名,以及标题,而且需要通过查看更多楼盘详情,进入C页面
进入C页面后,我需要抓取他的基本信息,那么如何将C页面的数据与B整合呢,解决思路如下:
思路:
创建一个Map,以抓取的跳转c页面的链接作为Key,以B页面的数据作为value进行绑定,让后存入Request 中,在匹配到是C页面时,在通过自己本身的url进行获取,尽可以拿到指定的value,从而保证了数据的一致性
代码:
package com.jyft.wf.test;
import com.jyft.wf.webmagic.MyPipeline;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Request;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.scheduler.BloomFilterDuplicateRemover;
import us.codecraft.webmagic.scheduler.QueueScheduler;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author:wf
* @Date 2021/6/16 16:56
* @Describe:抓取贝壳数据并整合Demo
**/
public class Test01 implements PageProcessor {
//楼盘列表页
private static String startUrl="https://bj.fang.ke.com/loupan/pg1";
//详情页url
private static String pageUrl="https://bj\\.fang\\.ke\\.com/loupan/p_\\w+/";
//最里面的详细信息url
private static String detailedPageUrl="https://bj\\.fang\\.ke\\.com/loupan/p_\\w+/xiangqing/";
private Site site = Site.me()
//设置字符集,此处使用的字符集应该和网站使用的字符集相同,不然就会出现乱码的情况
.setCharset("utf8")
//设置超时时间为10s
.setTimeOut(10*1000)
//重试间隔时间为3S
.setRetrySleepTime(3000)
//重试次数为 3次
.setRetryTimes(3)
;
@Override
public void process(Page page) {
//判断是否为列表的url
if(page.getUrl().regex("https://bj.fang.ke.com/loupan/pg.*").match()) {
//获取单个楼盘详情页连接
List<String> all = page.getHtml().css("ul.resblock-list-wrapper").links().regex("https://bj.fang.ke.com/loupan/.*").all();
for (String comicUrl : all) {
//这种形式可以设置优先级
//Request request = new Request(comicUrl).setPriority(1);
Request request = new Request(comicUrl);
page.addTargetRequest(request);
}
//添加分页的楼盘页链接
for(int i=2; i<3; i++){
//这种形式可以设置优先级
// Request request = new Request("https://bj.fang.ke.com/loupan/pg"+i+"/").setPriority(0);
Request request = new Request("https://bj.fang.ke.com/loupan/pg"+i+"/");
page.addTargetRequest(request);
}
//https://bj.fang.ke.com/loupan/p_bjlxafsft/?fb_expo_id=460899605006036992
//https://bj.fang.ke.com/loupan/p_thsayhafsmo/?fb_expo_id=460899605006036993
//https://bj.fang.ke.com/loupan/p_gtydafsys/?fb_expo_id=460899605006036994
//判断是第三层最里面的详细信息页面(此页面获取楼盘所有详细信息),
// 通过本次的url来获取刚刚第二层中存入Request中的数据,从而保证了数据的一致性
}else if((page.getUrl().regex(detailedPageUrl).match())){
String sectionUrl = page.getUrl().toString();
//获取对象数据
Map<String,Object> urlNameMap=(Map<String,Object>)page.getRequest().getExtra("urlNameMap");
//通过url获取对应指定的那个项目的数据
Map<String,Object> dataObj=( Map<String,Object>)urlNameMap.get(sectionUrl);
if(dataObj==null){
page.setSkip(true);
}
//获取楼盘的详细信息数据
String style = page.getHtml().css("div.big-left ul.x-box li.all-row span.label-val", "text").toString();
System.out.println("真正的详细信息数据:"+style);
dataObj.put("物业类型",style);
page.putField("data",dataObj);
//判断是否为第二层,(此页面只获取楼盘名称,并存储,便于第三层数据对应使用)
}else if((page.getUrl().regex(pageUrl).match())){
//获取楼盘名称
String text = page.getHtml().css("h2.DATA-PROJECT-NAME", "text").toString();
//楼盘别名
String text1 = page.getHtml().css("div.other-name", "text").toString();
//将此处获取的信息存起来
Map<String,Object> dataObj=new HashMap<>();
dataObj.put("name",text);
dataObj.put("name2",text1);
//获取更多的鏈接,即第三层的连接
List<String> all1 = page.getHtml().css("div.more-building").links().all();
System.out.println("查看详细信息的链接--》"+all1);
//将每条第三层的连接和对应的数据绑定起来,再存入Request,等进入第三层页面的时候,再通过url指定获取对应的数据
Map<String,Object> urlNameMap=new HashMap<>();
for (String comicUrl : all1) {
//通过url绑定数据
urlNameMap.put(comicUrl,dataObj);
//此处也是设置优先级的写法
//Request request = new Request(comicUrl).setPriority(2);
Request request = new Request(comicUrl);
//将当前数据存入Request
request.putExtra("urlNameMap", urlNameMap);
page.addTargetRequest(request);
}
System.out.println("绑定数据:"+urlNameMap);
System.out.println("----------------------------------------------------------");
}
}
@Override
public Site getSite() {
return site;
}
public static void main(String[] args) {
//程序抓取的入口
Spider.create(new Test01())
//从这个url开始抓取
.addUrl(startUrl)
//设置使用布隆过滤器去重
.setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(10000000)))
//设置10个线程同时抓取
.thread(10)
//使用自己的Pipeline将结果保存到数据库中
.addPipeline(new MyPipeline())
.run();
}
}
- 一些注意的点
1.爬虫启动后无法进入process方法,可能是因为使用了布隆过滤器,或者redis对url进行了去重,删除掉之前的url即可
这一块的资料较少,本人也是查找了很久,希望对大家有所帮助,可以少走一些弯路