java爬虫学习记录

HttpClient的使用

使用HttpClient发起get请求

  • 请求携带参数直接在url上拼接即可
    @Resource
    private HttpClient httpClient;

    /**
     * 演示httpClient发起get请求
     * 1.创建httpclient
     * 2.创建httpGet
     * 3.调用httpclient的execute方法发起请求
     * 4.接受response返回值,进行解析
     */
    public void get() throws Exception{
        //get请求,携带参数直接在url中拼接
        String url = "https://itbaima.net/document";

        //创建请求对象
        HttpGet httpGet = new HttpGet(url);
            //执行请求
            HttpResponse response = httpClient.execute(httpGet);
            if (response.getStatusLine().getStatusCode()==200){
                System.out.println("请求成功");
                HttpEntity entity = response.getEntity();
                //使用工具类将响应数据转换为html格式的字符串
                String html = EntityUtils.toString(entity, "utf-8");
                //打印请求返回的数据
                System.out.println(html);

                //将数据放到一个文件中
              saveToFile(html,"get.html");
                //关闭流
                response.getEntity().getContent().close();
            }else{
                System.out.println("请求失败");
            }
    }

使用HttpClient发起post请求

  • 携带参数需要向httpPost对象设置一个HttpEntity,参数保存在HttpEntity中
 /**
     * 演示发起post请求
     * 1.创建HttpPost对象
     * 2.使用列表准备参数
     * 3.将参数放到一个httpEntity中
     * 4.将参数设置到htpPost对象中
     * 5.使用HttpClient发起Post请求
     * 6.获取响应结果
     * 7.打印响应的结果
     */
    public void post() throws Exception {
        //1.创建httpClient
        //2.1创建httpPost
        HttpPost httpPost = new HttpPost("https://www.jdcloud.com/cn/search");
        //2.2准备参数,?query=java
        List<NameValuePair> form = new ArrayList<>();
        form.add(new BasicNameValuePair("query","python"));
        //将参数放到http实体中
        HttpEntity httpEntity = new UrlEncodedFormEntity(form,"utf-8");
        httpPost.setEntity(httpEntity);
        //3.发起请求
        HttpResponse response = httpClient.execute(httpPost);
        //4.打印响应的结果
        System.out.println("response.getEntity().getContentType() = " + response.getEntity().getContentType());
        String html = EntityUtils.toString(response.getEntity(), "utf-8");
        saveToFile(html,"post.html");
        System.out.println("html = " + html);

    }

使用HttpClient连接池管理HttpClient连接

  • 需要HttpClient时,直接调用静态方法获取即可
public class HttpClientPool {
    //连接池对象
    private static PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
    public static HttpClient create(){
        //创建HttpClient对象放入连接池中管理
        return HttpClients.custom().setConnectionManager(poolingHttpClientConnectionManager).build();
    }
}

Jsoup的使用

  • 一个解析HTML的工具包

获取解析html文档对象的三种方式

  • 使用Jsoup的静态方法parse获取html文档对象
  1. 通过文件的方式
 /**
     * 通过File获取要解析的html文档对象
     * @throws IOException
     */
    public Document create01() throws IOException {
        Document document = Jsoup.parse(new File("get.html"));
        return document;
    }
  1. 通过html格式字符串的方式
 /**
     * 通过一个html的字符串获取文档对象
     */
    public Document create02(){
        Document document = Jsoup.parse("<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "  <head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <link rel=\"icon\" href=\"/favicon.ico\">\n" +
                "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
                "    <link rel=\"stylesheet\" href=\"https://lib.baomitu.com/element-plus/2.3.12/index.css\"/>\n" +
                "    <link rel=\"stylesheet\" href=\"https://lib.baomitu.com/normalize/latest/normalize.css\">\n" +
                "    <link rel=\"stylesheet\" href=\"https://lib.baomitu.com/font-awesome/6.4.2/css/all.min.css\">\n" +
                "    <title>柏码 - 让每一行代码都闪耀智慧的光芒!</title>\n" +
                "    <style>\n" +
                "      body{\n" +
                "        margin: 0;\n" +
                "        font-family: system-ui, -apple-system, \"Segoe UI\",\n" +
                "        Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\",\n" +
                "        \"Liberation Sans\", sans-serif, \"Apple Color Emoji\",\n" +
                "        \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n" +
                "      }\n" +
                "\n" +
                "      @font-face {\n" +
                "        font-family: \"Fira Code\";\n" +
                "        src: url(\"https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/firacode/6.2.0/ttf/FiraCode-Medium.ttf\");\n" +
                "      }\n" +
                "    </style>\n" +
                "    <script type=\"module\" crossorigin src=\"/assets/index-0afa16d6.js\"></script>\n" +
                "    <link rel=\"stylesheet\" href=\"/assets/index-4cb07b3b.css\">\n" +
                "  </head>\n" +
                "  <body style=\"width: 100vh;height: 100vh;overflow:hidden\">\n" +
                "    <div id=\"app\"></div>\n" +
                "    \n" +
                "  </body>\n" +
                "</html>");
        return document;
    }
  1. 通过url远程获取html的方式
    /**
     * 通过url获取远程的html文档对象
     */
    public Document create03() throws IOException {
        String url = "http://baidu.com";
        Document document = Jsoup.parse(new URL(url),10000);
        return document;
    }

解析html文档的方式

  1. 通过id获取html文档的元素
    /**
     * 通过元素的id解析
     */
    public void parseByElementId() throws IOException {
        Document document = create01();
        Element element = document.getElementById("app");
        System.out.println("element = " + element);
    }
  1. 通过元素className获取
    /**
     * 通过元素的类名解析
     * @throws IOException
     */
    public void parseByElementClassName() throws IOException {
        Document document = create01();
        Elements elements = document.getElementsByClass("test2");
        for (Element element : elements) {
            System.out.println("element = " + element.text());
        }
    }
  1. 通过元素的属性获取
    /**
     * 通过元素的属性解析
     * @throws IOException
     */
    public void parseByElementAttribute() throws IOException {
        Document document = create01();
        Elements elements = document.getElementsByAttribute("style");
        for (Element element : elements) {
            System.out.println("element = " + element.text());
        }
    }
  1. 通过元素的属性和属性值组合获取
    /**
     * 通过元素的属性和属性值解析
     * @throws IOException
     */
    public void parseByElementAttributeAndValue() throws IOException {
        Document document = create01();
        Elements elements = document.getElementsByAttributeValue("width", "100px");
        for (Element element : elements) {
            System.out.println("element = " + element);
        }
    }

  1. 通过css选择器的方式获取元素
    • 使用方式与jquery的方式类似
    /**
     * 通过CSS选择器进行解析,操作方式与jquery类似
     */
    public void parseByCSSSelector01() throws IOException {
        Document document = create01();
        Elements body = document.select("body");
        for (Element element : body) {
            System.out.println("element = " + element);
        }
    }
  1. 通过css选择器组合的方式获取元素
    /**
     * 通过CSS选择器组合进行解析
     */
    public void parseByCSSSelector02() throws IOException {
        Document document = create01();
        Elements elements = document.select("body  div[class=test2]");
        for (Element element : elements) {
            System.out.println("element = " + element);
        }
    }
  1. 获取元素标签内的值使用text()方法
    /**
     * 获取元素标签内的文本
     * @throws IOException
     */
    public void getText() throws IOException {
        Document document = create01();
        String text = document.select("main").text();
        System.out.println("text = " + text);
    }
  1. 获取元素属性的值使用attr()方法
    /**
     * 获取元素标签属性的值
     * @throws IOException
     */
    public void getAttributeValue() throws IOException {
        Document document = create01();
        String attribute = document.select("main").attr("width");
        System.out.println("attribute = " + attribute);
    }

使用HttpClient和Jsoup爬虫广科官网的综合案例

  1. 抓取广科官网的页面,解析图片的url
public void test() throws IOException {
        String url = "https://www.gdit.edu.cn/main.htm";
        HttpClient httpClient = HttpClientPool.create();
        HttpGet httpGet = new HttpGet(url);

        HttpResponse response = httpClient.execute(httpGet);

        String html = EntityUtils.toString(response.getEntity(), "utf-8");

        saveToFile(html,"lwj.html");
        Document document = Jsoup.parse(html);

        Elements elements = document.select("img");

        int count = 0;
        for (Element element : elements) {
            //System.out.println("element = " + element);
            String src = element.attr("src");
            System.out.println("src = " + src);
            Pattern pattern = Pattern.compile("^/_upload.*");
            //下载图片
            if (pattern.matcher(src).matches()) {
                System.out.println(++count);
                downloadImage(src,count+"");
            }
        }

    }
  1. 下载图片,拼接url下载图片
  public void downloadImage(String src,String filename) throws IOException {
        src = "https://www.gdit.edu.cn/" + src;
        HttpGet httpGet = new HttpGet(src);
        HttpResponse response = HttpClientPool.create().execute(httpGet);
        //写入文件
        response.getEntity().writeTo(new FileOutputStream("./imgs/"+filename+src.substring(src.lastIndexOf('.'))));

    }

WebMagic的使用

  • 四个核心组件
    1. Downloader:页面下载器,用获取页面
    2. PageProcessor:页面解析器,用于解析页面
    3. Scheduler:Url链接地管理器,用管理爬取的url地址,url去重等等
    4. Pipeline:数据持久器,用将数据保存到数据库或者保存的文件中,默认打印到控制台
  • Spider:爬虫的控制引擎,用启动爬虫
  • 依赖包
<!--引入WebMagic依赖-->
        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-core</artifactId>
            <version>0.7.3</version>
        </dependency>
        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-extension</artifactId>
            <version>0.7.3</version>
        </dependency>

PageProcessor组件的使用

  • 获取html对象,解析html对象的方式,Selectable接口提供了多种解析获取元素的方式

    1. 获取html的文档对象,使用jsoup的方式进行解析
        /**
         * 解析html对象的方式一
         * 使用jsoup解析
         * @param html
         */
        public void parse01(Html html){
            Document document = html.getDocument();
            Element element = document.getElementById("main");
            System.out.println("element = " + element);
        }
    
    1. 使用css选择器解析
        /**
         * 解析方式二
         * 使用css选择器解析
         * @param html
         */
        public void parse02(Html html){
            //使用css选择器解析,可以使用css()方法,也可以使用$()方法,两者等价
            Selectable selectable = html.css("#mian");
            //使用get()方法获取文本信息
            System.out.println("selectable = " + selectable.get());
        }
    
    1. 使用xpath解析
      /**
         * 解析方式三
         * 使用xpath解析
         * @param html
         */
        public void parse03(Html html){
            Selectable xpath = html.xpath("/div");
            System.out.println("xpath.get() = " + xpath.get());
        }
    
    1. 使用正则表达式解析
        /**
         * 解析方式四
         * 使用正则表达式解析
         * @param html
         */
        public void parse04(Html html){
            Selectable regex = html.regex("^<img>$");
            System.out.println("regex = " + regex);
        }
    
  • 自定义页面解析器,解析实习僧的页面

@Component
public class PageProcessorDemo implements PageProcessor {
    @Autowired
    private Site site;
    @Override
    public void process(Page page) {
        Html html = page.getHtml();
        //尝试获取职位名称的节点,如果存在说明是职位详情页,否则就是职位列表页
        List<Selectable> nodes = html.$("div.new_job_name","text").nodes();
        if (nodes.size() > 0){
            //职位详情页
            //职位名称
            String positionName = html.css("div.new_job_name>span","text").get();
            //最低日薪和最高日薪
            String salary = html.css("div.job_msg > span.job_money.cutom_font","text").get();
            int[] salary1 = getSalary(salary);
            Integer minSalary = salary1[0],maxSalary = salary1[1];
           //学历要求
            String edu = html.css("div.job_msg > span.job_academic","text").get();
            //工作地点
            String city = html.css("div.job_msg > span.job_position","text").get();
            //工作周期
            String cycle = html.css("div.job_msg > span.job_week.cutom_font","text").get();
            //实习时长
            String duration = html.css("div.job_msg > span:nth-child(5)","text").get();
            //职位描述
            String describe = html.css("div.job_part > div","text").get();
            //职位url
            String url = page.getUrl().get();
            //公司名称
            String companyName = html.css("div.com_intro > a.com-name","text").get();
            //公司行业
            String profession = "";
            //公司规模
            String size = "";
            //公司性质
            String nature = "";
            List<String> all = html.css("div.com-detail > div","text").all();
            if (all.size()==4){
                //公司行业,规模,性质信息都有
                profession = all.get(0);
                nature = all.get(1);
                size = all.get(2);
            }else if (all.size()==3){
                //有公司规模,性质信息
                nature = all.get(0);
                size = all.get(1);
            }
            //封装职位对象
            Position position = new Position(null, positionName, minSalary, maxSalary, companyName, city, cycle, duration, url, describe, edu);
            //封装公司对象
            Company company = new Company(null, companyName, profession, size, null, nature);
            //数据持久化
            page.putField("position",position);
            page.putField("company",company);
        }else{
            //职位列表页
            //获取每个职位的url
            List<String> links = html.css("div.clearfix.intern-detail  a").links().regex("^https://.*").all();
            //将url放入待爬列表中
            page.addTargetRequests(links);
            //跳过数据持久化
            page.setSkip(true);
        }
    }

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

    /**
     * 处理工资区间
     * @param salary
     * @return
     */
    public int[] getSalary(String salary){
        int[] result = new int[2];
        salary = salary.substring(0,salary.indexOf('/'));
        String[] split = salary.split("-");
        if (split.length==2){
            result[0] = Integer.valueOf(split[0].trim());
            result[1] = Integer.valueOf(split[1].trim());
        }else if (split.length==1){
            result[0] = Integer.valueOf(split[0].trim());
            result[1] = result[0];
        }
        return result;
    }
}

Pipeline的使用

  • webMagic提供了三种基础的Pipeline实现类
    1. ConsolePipeline:默认的持久化组件,将信息输出到控制台
    2. FilePipeline:将信息保存到文件中
    3. JsonFilePipeline:将数据按json格式保存到文件中
  • 我们也可以实现Pipeline接口进行自定义持久化组件
  • 自定义Pipeline持久化数据到数据库
@Component
public class MyPipeline implements Pipeline {

    @Autowired
    private PositionService positionService;
    @Autowired
    private CompanyService companyService;
    @Override
    public void process(ResultItems resultItems, Task task) {

        Position position = (Position)resultItems.get("position");
        //保存职位信息
        positionService.save(position);
        Company company = (Company) resultItems.get("company");
        //判断公司是否已经保存过
        QueryWrapper<Company> companyQueryWrapper = new QueryWrapper<>();
        companyQueryWrapper.eq("name",company.getName());
        Company one = companyService.getOne(companyQueryWrapper);
        if (null == one){
            companyService.save(company);
        }
    }
}

DownLoader页面下载器的使用

  • 没有特殊需求使用默认下载器即可,默认下载器是HttpClientDownLoader
  • 通过DownLoader设置代理,解决反爬问题
  • 免费代理
  • 免费代理
 void process() {
        //创建一个下载器对象
        HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
        //创建一个代理服务器对象,通过form可以设置多个代理服务器
        SimpleProxyProvider proxyProvider = SimpleProxyProvider.from(new Proxy("81.70.187.80", 8080));
        //给下载器设置代理服务器对象
        httpClientDownloader.setProxyProvider(proxyProvider);
        Spider.create(myPageProcessor)
                //指定使用代理服务器的下载器
                .setDownloader(httpClientDownloader)
                .addUrl("https://www.shixiseng.com/intern/inn_klti02rhwo1y?pcm=pc_SearchList")
                .run();
    }

配合selenium前端自动化测试框架,自定义DownLoader页面下载器,解决页面动态加载的问题

        <!--引入selenium依赖    -->
        <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.8.0</version>
        </dependency>
  • 下载chrome浏览器驱动,并将驱动加入到环境变量中
  • 使用案例:打开京东,输入电脑进行搜索
    void chromeTest() throws InterruptedException, IOException {
        //创建Chrome的配置信息,设置chrome驱动的位置
        System.setProperty("webdriver.chrome.driver","C:\\Users\\杨逸\\AppData\\Local\\chromedriver-win64\\chromedriver.exe");
        //解决只允许本地连接的警告:Only local connections are allowed.高版本似乎不允许接受所有远程连接
        System.setProperty("webdriver.chrome.whitelistedIps", "127.0.0.1");
        //创建一个浏览器配置对象
        ChromeOptions chromeOptions = new ChromeOptions();
        //设置参数
        //开启无头模式
        //chromeOptions.addArguments("--headless");
        //设置窗口大小
        chromeOptions.addArguments("--window-size=1920,1080");
        //设置允许所有远程连接
        chromeOptions.addArguments("--remote-allow-origins=*");
        //设置请求头
        chromeOptions.addArguments("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36");
        //创建浏览器驱动对象
        ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);

        //访问京东
        chromeDriver.get("https://www.jd.com");

        //添加cookie信息
        WebDriver.Options manage = chromeDriver.manage();
        Map<String, String> cookie = getCookie();
        //manage.deleteAllCookies();
        for (Map.Entry<String, String> entry : cookie.entrySet()) {
            //注意添加的cookie不能含有空格
            manage.addCookie(new Cookie(entry.getKey().trim(), entry.getValue().trim()));
        }
        //刷新当前页,获取原始对象刷新页面
        chromeDriver.navigate().refresh();

        //输入信息,
        chromeDriver.findElement(By.ByCssSelector.cssSelector("#key")).sendKeys("电脑");
        Thread.sleep(1000);
        //点击搜索
        chromeDriver.findElement(By.cssSelector("#search > div > div.form > button")).click();
        Thread.sleep(5000);
        //滚动
        chromeDriver.executeScript("window.scrollTo(0,document.body.scrollHeight)");
        Thread.sleep(5000);

        String html = chromeDriver.getPageSource();
        System.out.println("html = " + html);
        //保存抓取到的页面
        FileOutputStream fileOutputStream = new FileOutputStream("selenium.html");
        fileOutputStream.write(html.getBytes());

        //关闭浏览器
        fileOutputStream.close();
        chromeDriver.close();
    }
  • 案例二:爬取实习僧平台上java实习的所有数据
  • 涉及窗口的打开和关闭
  • 窗口的滚动
package com.yangyi.webMagicDemo.component;

import lombok.SneakyThrows;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Request;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.downloader.Downloader;
import us.codecraft.webmagic.selector.PlainText;

import javax.annotation.PreDestroy;
import java.util.*;

/**
 * @Projectname: webMgaicDemo
 * @Filename: SeleniumDownLoader
 * @Author: 杨逸
 * @Data:2023/10/15 11:43
 * @Description: 配合Selenium的页面下载器
 */
@Component
public class SeleniumDownLoader implements Downloader {
    private ChromeDriver chromeDriver;
    public SeleniumDownLoader(){
        //初始化无头浏览器
        //创建Chrome的配置信息,设置chrome驱动的位置
        System.setProperty("webdriver.chrome.driver","C:\\Users\\杨逸\\AppData\\Local\\chromedriver-win64\\chromedriver.exe");
        //解决只允许本地连接的警告:Only local connections are allowed.高版本似乎不允许接受所有远程连接
        System.setProperty("webdriver.chrome.whitelistedIps", "127.0.0.1");
        //创建一个浏览器配置对象
        ChromeOptions chromeOptions = new ChromeOptions();
        //设置参数
        //开启无头模式
        //chromeOptions.addArguments("--headless");
        //设置窗口大小
        chromeOptions.addArguments("--window-size=1920,1080");
        //设置允许所有远程连接
        chromeOptions.addArguments("--remote-allow-origins=*");
        //设置请求头
        chromeOptions.addArguments("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36");
        //创建浏览器驱动对象
        chromeDriver = new ChromeDriver(chromeOptions);
    }

    @Override
    public Page download(Request request, Task task) {
        //获取要访问的url
        String url = request.getUrl();
        chromeDriver.get(url);
        //判断是列表页还是详情页
        List<WebElement> elements = chromeDriver.findElements(By.cssSelector("div.new_job_name"));
        if(elements.size()>0){
            //详情页
            Page page = createPage(chromeDriver.getPageSource(), chromeDriver.getCurrentUrl());
            //获取详情页窗口的句柄
            String windowHandleBefore = chromeDriver.getWindowHandle();
            //点击进入公司详情页
            chromeDriver.findElement(By.cssSelector("#__layout > div > div.intern-detail-page.primary-content.clearfix > div.job-box > div.job-content > div.content_right > div.job-about > div.con-job > div.com_intro > a.com-name")).click();
            //获取公司详情页的url,放到header中保存
            String companyUrl = chromeDriver.getCurrentUrl();
            Map<String,List<String>> headers = new HashMap<>();
            headers.put("companyUrl", Arrays.asList(companyUrl));
            page.setHeaders(headers);

            //获取所有仓库句柄
            Set<String> windowHandles = chromeDriver.getWindowHandles();
            for (String windowHandle : windowHandles) {
                //关闭不是当前窗口的其他窗口
                if (!windowHandleBefore.equals(windowHandle)) {
                    chromeDriver.switchTo().window(windowHandle).close();
                    chromeDriver.switchTo().window(windowHandleBefore);
                }
            }
            //关闭当前窗口
           //chromeDriver.close();
            return page;
        }
        //列表页
        Page page = createPage(chromeDriver.getPageSource(), chromeDriver.getCurrentUrl());
        //判断是否能翻页
        WebElement element = chromeDriver.findElement(By.cssSelector("#__layout > div > div.interns > div.result-list.clearfix > div.primary-content.f-l > div:nth-child(1) > div.pagination-wrap > div > button.btn-next"));
        if (element.isEnabled()) {
            //下滑到底部
            chromeDriver.executeScript("window.scrollTo(0,document.body.scrollHeight)");
            System.out.println("element = " + element);
            //点击翻页,获取下一页的url
            element.click();
            page.addTargetRequest(new Request(chromeDriver.getCurrentUrl()));
        }
        return page;
    }

    @Override
    public void setThread(int i) {

    }

    /**
     *
     * @param html 页面
     * @param url 页面的URL
     * @return
     */
    private Page createPage(String html,String url){
        Page page = new Page();
        //设置页面原生文本信息
        page.setRawText(html);
        //设置页面的URL信息
        page.setUrl(new PlainText(url));
        //设置Request对象
        page.setRequest(new Request(url));
        //设置抓取成功标志
        page.setDownloadSuccess(true);
        return page;
    }

    @PreDestroy
    public void destroy(){
        chromeDriver.quit();
    }
}

Scheluder URl队列管理器的使用

  • 没有特殊需求使用默认的即可
  • 作用:
    1. 对待抓取的URL队列进行管理
    2. 对已抓取的URL进行去重
  • 对url进行管理的类
说明备注
DuplicateRemovedScheduler抽象基类,提供一些模板方法继承它可以实现自己的功能
QueueScheduler使用内存队列保存待抓取URL默认管理url使用的队列管理器
PriorityScheduler使用带有优先级的内存队列保存待抓取URL耗费内存较QueueScheduler更大,但是当设置了request.priority之后,只能使用PriorityScheduler才可使优先级生效
FileCacheQueueScheduler使用文件保存抓取URL,可以在关闭程序并下次启动时,从之前抓取到的URL继续抓取需指定路径,会建立.urls.txt和.cursor.txt两个文件
RedisScheduler使用Redis保存抓取队列,可进行多台机器同时合作抓取需要安装并启动redis
  • 对url进行去重的类
  • 默认使用HashSetDuplicateRemover进行去重
说明
HashSetDuplicateRemover使用HashSet来进行去重,占用内存较大
BloomFilterDuplicateRemover使用BloomFilter来进行去重,占用内存较小,但是可能漏抓页面
  • 演示使用布隆过滤器BloomFilterDuplicateRemover进行url过滤
    @RequestMapping("/start")
    public String start(){
        //初始化抓取的url
        String baseUrl = "https://www.shixiseng.com/interns?type=intern&keyword=java&area&months&days&degree&official&enterprise&salary=-0&publishTime&sortType&city=%E5%85%A8%E5%9B%BD&page=";
        List<String> urls = new ArrayList<>();
        for (int i = 1; i <= 11; i++) {
            urls.add(baseUrl+i);
        }
        //创建配置url管理器,设置url去重策略
        QueueScheduler queueScheduler = new QueueScheduler();
        //创建布隆过滤器的参数设置为页面大致数量即可
        queueScheduler.setDuplicateRemover(new BloomFilterDuplicateRemover(100000));
        //启动爬虫
        Spider.create(pageProcessorDemo)
                .addUrl(urls.toArray(new String[11]))
                .addPipeline(myPipeline)
                .addPipeline(new ConsolePipeline())
                //配置管理url和对url去重的url管理器队列
                .setScheduler(queueScheduler)
                .thread(5)
                .start();
        return "ok";
    }

Spider爬虫控制引擎的使用

  • 使用静态方法create()指定一个页面处理器,然后创建一个Spider爬虫控制引擎
  • 通过addUrl()方法,指定爬虫爬取的初始地址
  • 有数据持久化需求可以通过setPipeline()添加数据持久化处理器,可以添加多个
  • 通过thread()方法指定启动爬虫最多可以开启多少个线程
  • 启动爬虫有三种方式
    1. run(),同步爬虫,在本线程进行爬虫
    2. start(),异步爬虫,开启一个新线程,在新的线程中进行爬虫
    3. runAsync,异步爬虫
public String start(){
        String baseUrl = "https://www.shixiseng.com/interns?type=intern&keyword=java&area&months&days&degree&official&enterprise&salary=-0&publishTime&sortType&city=%E5%85%A8%E5%9B%BD&page=";
        List<String> urls = new ArrayList<>();
        for (int i = 1; i <= 11; i++) {
            urls.add(baseUrl+i);
        }
        //启动爬虫
        Spider.create(pageProcessorDemo)
                .addUrl(urls.toArray(new String[11]))
                .addPipeline(myPipeline)
                .addPipeline(new ConsolePipeline())
                .thread(5)
                .start();
        return "ok";
    }

Site对象的使用

  • 通过该对象可以设置爬取站点时,携带的信息,比如:浏览器类型,Cookie,等等
  • 该对象需要配置页面处理器PageProcessor使用
    @Bean
    public Site shiXiSengSite(){
        Site site = Site.me();
        //设置浏览器类型
        site.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36");

        //设置cookie
        Map<String, String> cookie = getCookie();
        for (Map.Entry<String, String> entry : cookie.entrySet()) {
            site.addCookie(entry.getKey(),entry.getValue());
        }
        return site;
    }

定时爬虫

@Component
public class Timer {
    /**
     * 定时方式一
     * 使用@Scheduled注解设置定时的规则
     * 每间隔一秒启动一次
     * fixedRate与fixedRateString作用等价,前者参数是数值,后者参数是数值的字符串
     */
    @Scheduled(fixedRate = 1000)
    //@Scheduled(fixedRateString = "1000")
     public void timingTest1(){
         System.out.println("定时器1");
         System.out.println("LocalDateTime.now().toLocalTime() = " + LocalDateTime.now().toLocalTime());
     }

    /**
     * 定时方式二
     * 当前任务执行完,等待一秒后再执行
     */
    @Scheduled(fixedDelay = 1000)
    //@Scheduled(fixedDelayString = "1000")
    public void timingTest2(){
         System.out.println("定时器二");
         System.out.println("LocalDateTime.now().toLocalTime() = " + LocalDateTime.now().toLocalTime());
     }

    /**
     * 定时方式三
     * 使用cron表达式进行定时,间隔两秒执行一次
     * 一共有七个占位符,依次表示秒,分钟,小时,日,月,周,年
     * 最后一个占位符年,可以省略
     */
    @Scheduled(cron = "0/2 * * * * * ")
     public void cornTest(){
         System.out.println("使用cron表达式设置定时任务");
         System.out.println("LocalDateTime.now().toLocalTime() = " + LocalDateTime.now().toLocalTime());
     }
}