package com.fenglu.webmagic;

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;

public class GithubRepoPageProcessor implements PageProcessor {


    @Override
    public void process(Page page) {

        //webmagic有三种解析方式,这是通过css选择器找内容
        page.putField("div", page.getHtml().css("div.p-price").all());

        //这是使用xpath来找元素,xpath原本是用来解析xml文件的,使用方法参考https://www.w3school.com.cn/xpath/index.asp
        //这个意思就是找到id为news_id 的div元素下的ul下的li下的div下的a标签的值
        page.putField("div1",page.getHtml().xpath("//div[@id=news_id]/ul/li/div/a"));

        //这是使用正则表达式,一般用来获取url
        page.putField("div2",page.getHtml().regex("").all());

        //这三种选择器可以组合使用,选择元素的组件叫 selectable,是webmagic的核心功能

    }

	//这里并没有设置编码,但是就是不乱码,
    private Site site = Site.me();
    @Override
    public Site getSite() {
        return site;
    }

    public static void main(String[] args) {
        Spider.create(new GithubRepoPageProcessor()).addUrl("https://search.jd.com/Search?keyword=%E9%BB%84%E9%87%91&enc=utf-8&wq=%E9%BB%84%E9%87%91&pvid=5cd48e608647401eb039462bbf333895").run();
    }


}

结果:
在这里插入图片描述

上图我们可以发现已经从京东中爬取到了黄金的价格标签信息

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

获取链接

使用pageProcesser获取链接并将链接放入Scheduler,下次自动爬取解析

 @Override
    public void process(Page page) {

        //获取链接,下面这段代码的意思就是根据div的id获取下面所有的链接并且是以9结尾的链接,然后将链接加入到Scheduler下次再次发送链接然后使用下面的 方式获取内容
        page.addTargetRequest((Request) page.getHtml().css("div#news_id").links().regex(".*9$").all());
        page.putField("url",page.getHtml().css("div.p-price").all());
    }

使用Pipeline保存结果

上面的代码全部输出到了控制台,这是因为使用了默认的consolePileline,那么如果我们想要将结果保存到文件中该怎么写呢??

    public static void main(String[] args) {
        Spider.create(new GithubRepoPageProcessor())
                .addPipeline(new FilePipeline("c:\\user\\webmagic"))
                .addUrl("https://search.jd.com/Search?keyword=%E9%BB%84%E9%87%91&enc=utf-8&wq=%E9%BB%84%E9%87%91&pvid=5cd48e608647401eb039462bbf333895")
                .thread(3)//表示有3个线程执行
                .run();
    }

在这里插入图片描述
上图中我们可以看出来,保存结果的方式有很多种,这里只是介绍了console和file两种,那么如果我们想要自己实现保存结果该怎么写呢》?只需要实现Pipeline接口并重写其中的方法即可

public class MysqlPipeline implements Pipeline {
    @Override
    public void process(ResultItems resultItems, Task task) {
        //这里的div就是processor中putfiled中的key
        Object div = resultItems.get("div");
    }
}

爬虫的配置启动和终止

下面是Spider类的一些方法
在这里插入图片描述

配置 site类

下图只是部分
在这里插入图片描述

案例

爬取的页面如下图
在这里插入图片描述
需要注意的是这里的定时任务并没有实现,如果有需要可以自己去实现


package com.fenglu.webmagic.job;

import com.fenglu.webmagic.entity.JobInfo;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.Page;
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 us.codecraft.webmagic.selector.Html;
import us.codecraft.webmagic.selector.Selectable;

import java.util.List;

@Component
public class JobPageProcessor implements PageProcessor {

    private final String url = "https://search.51job.com/list/290200,000000,0000,32,9,99,%2B,2,1.html?lang=c&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare=";

    @Autowired
    JobPipeline jobPipeline;

    @Override
    public void process(Page page) {

        //实现步骤:
        //1、先抓取页面看看拿到的东西是什么
        //String html = page.getHtml().toString();

        //2. 当获取到的内容是我们想要的,接下来再解析
        //表示先获取id为resultList的div的 class为el的子元素
        List<Selectable> nodes = page.getHtml().css("div#resultList div.el").nodes();
        //如果得到的集合是个空,则证明这是详情页,则去解析详情信息
        if (nodes == null || nodes.size() == 0){
            this.saveInfo(page);
        }else{
            //否则表示这就是列表页,获取详情url并加入队列
            for (Selectable node : nodes) {
                //根据实际情况获取详情页的url,如果该节点下只有一个url,则可以直接使用下面这种方式,如果有多个,则
                //node.links().regex("").css(".el").toString()
                String url = node.links().toString();
                System.out.println(url);
                //将解析到的url放入队列中
                page.addTargetRequest(url);
            }
            //如果需要获取分页下一页的信息,则需要将分页的url也加入到队列中,这样在详情解析完,队列中执行到下一页的url时会
            //爬取到下一页的信息
            //获取下一页的url,获取class为p_in的div的class为bk的li元素,get(1)表示获取第二个节点元素
            String next = page.getHtml().css("div.p_in li.bk").nodes().get(1).links().toString();
            //加入任务队列
            page.addTargetRequest(next);
        }
    }

    /**
     * 解析页面并持久化到数据库
     * @param page
     */
    private void saveInfo(Page page) {

        //获取招聘详情页面
        Html html = page.getHtml();

        //解析页面,获取想要的数据,将数据封装到对象中
        JobInfo jobInfo = new JobInfo();
        //使用css的方式获取文本时,需要根据选择器来选择到最后一个标签元素才行,例如这里获取公司信息时根据选择器找到这个a标签,然后获取文本值
        //<div class='cn'> <div>...</div> <p>...</p><p class='cname'><a href='...'>阿里巴巴集团</a></p> </div>
        jobInfo.setCompany(html.css("div.cn p.cname a","text").toString());

        //当内容比较复杂时可以选择jsoup来解析,使用css选择器从html中找到对应的节点标签字符串,然后将字符串交给jsoup解析即可
        //效果就是下面main方法中的效果,当我们要获取的内容是只需要去掉标签即可的时候就可以将这一块交给jsoup
        jobInfo.setAddress(Jsoup.parse(html.css("div.tmsg").nodes().get(1).toString()).text());
        //获取职位
        jobInfo.setJob("");
        //获取详情页的url
        jobInfo.setUrl(page.getUrl().toString());

        //...
        //下面的都一样,当获取完成后保存即可,但是这里的保存只是将数据保存到了内存中,我们还需要将其保存到数据库中,因此要
        //重写pipeline,我们这里重写名为JobPipeline
        page.putField("jobInfo",jobInfo);

    }

    public static void main(String[] args) {
        Document parse = Jsoup.parse("<p class=\"t\"><span title=\"高中数学老师\" class=\"jname at\">高中数学老师</span> <span class=\"time\">02-23发布</span> <!----> <!----> <!----> <!----> <!----></p>");
        System.out.println(parse.text());//解析结果: 高中数学老师 02-23发布
    }

    private Site site = Site.me()
            .setCharset("utf-8")//设置编码,一般情况如果不是utf-8 那就是gbk
            .setTimeOut(5000)//设置超时
            .setRetryTimes(3)//设置重试次数
            .setRetrySleepTime(3000);//设置重试间隔

    @Override
    public Site getSite() {
        return site;
    }

    /**
     * 使用spring的定时任务开启爬虫
     */
    @Scheduled
    private void process(){
        Spider.create(new JobPageProcessor())
                .addUrl(url)
                .setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(10000)))
                .addPipeline(jobPipeline)
                .thread(3)
                .run();
    }

}

获取到的下一页的节点
在这里插入图片描述
要获取该节点下的url

 String next = page.getHtml().css("div.p_in li.bk").nodes().get(1).links().toString();

jobPipeline


@Component
public class JobPipeline implements Pipeline {

    @Autowired
    private JobInfoService jobInfoService;

    @Override
    public void process(ResultItems resultItems, Task task) {
        JobInfo jobInfo = resultItems.get("jobInfo");
        jobInfoService.save(jobInfo);
    }
}

public interface JobInfoService {

    void save(JobInfo jobInfo);

}

@Service
public class JobInfoServiceImpl implements JobInfoService {

    @Override
    public void save(JobInfo jobInfo){
        //如果没有就保存

        //如果有就更新

    }
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐