Jsoup实现习题知识点的初步标注

背景

继上次使用Jsoup简易爬取POJ题面之后,我所在的校创小组最近又有了新任务,就是要标注算法题所涉及的知识点。人工标注肯定不现实,所以想到了模拟百度搜索并从CSDN博客上爬取相应的题目解析,和预先定义好的知识点集合进行匹配,统计匹配成功次数,按匹配次数从大到小排序,从而实现习题知识点的初步标注。下面以标注HDU OJ的题目知识点为例:

定义知识点集合

在开始爬取之前,我们要预先定义用于匹配爬取结果的知识点集合。事实证明知识点集合的粒度和广度对标注结果影响很大,所以如何定义一个合适的知识点集合还需要再三斟酌。这里我只是大致罗列了一些算法知识点,保存在项目resource目录的algorithm.txt中:

模拟
贪心
枚举
递归
构造
递推
后缀数组
树状数组
差分约束
dp
BFS
DFS
迭代加深搜索
记忆化搜索
分治
排序
动态规划
最短路
拓扑排序
最小生成树
网络流
二分
二分图
数论
组合数学
计算几何
博弈
线段树
字典树
并查集
KMP
AC自动机
凸包
三分
GCD
扩展欧几里得
尺取法
RMQ
IDA*
背包
莫队算法
Polya

定义知识点分类实体类

/**
 * 分类实体类
 */
public class Category {
    private int index;
    private int occurCount;


    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public int getOccurCount() {
        return occurCount;
    }

    public void setOccurCount(int occurCount) {
        this.occurCount = occurCount;
    }

    @Override
    public String toString() {
        return "Category{" +
                "name=" + Main.getCandidate().get(index) +
                ", occurCount=" + occurCount +
                '}';
    }
}

引入Jsoup依赖

由于构建的是Maven项目,可以直接在pom.xml文件里引入Jsoup的相关依赖:

<dependency>
         <groupId>org.jsoup</groupId>
         <artifactId>jsoup</artifactId>
         <version>1.11.2</version>
</dependency>

定义爬虫工具类

下面是整个项目的核心,用于模拟百度搜索和爬取所需信息。

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * 爬虫工具类
 */
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * 爬虫工具类
 */
public class CrawlUtils {
    public static void crawl(int begin,int end) throws IOException {
        //用百度搜索关键词HDU+题号+CSDN博客
        for (int pid = begin; pid <= end; pid++) {
            Document doc = Jsoup.connect("https://www.baidu.com/s?ie=utf-8&wd=HDU"
                    + pid + "CSDN博客").get();
            Elements elements = doc.select("h3.t");

            //处理公共资源,加锁
            synchronized(CrawlUtils.class){
                List<String> arrayList = Main.getCandidate();
                Category[] c = Main.getCategories();
                for (int i = 0;i < c.length;i++) {
                    c[i] = new Category();
                    c[i].setIndex(i);
                    c[i].setOccurCount(0);
                }
                System.out.println("HDU" + pid + "分析结果:");
                //选取百度的前5个链接,分别解析
                for (int k = 0;k < 5;k++) {

                    //如果题号不匹配,直接跳过该链接
                    if(!elements.get(k).text().contains(String.valueOf(pid)))
                        continue;
                    String link = elements.get(k).selectFirst("a").attr("href");
                    //对于每个链接页面,爬取article标签中的文本
                    String text = Jsoup.connect(link).timeout(30000).get().select("article").text();

                    //将文本和预先定义的算法集合进行匹配,统计不同算法出现次数
                    for (int i = 0;i < arrayList.size();i++) {
                        int start = 0;
                        while(start < text.length() && text.indexOf(arrayList.get(i),start) != -1){
                            c[i].setOccurCount(c[i].getOccurCount() + 1);
                            start = text.indexOf(arrayList.get(i),start) + 1;
                        }
                    }

                }
                //按照出现次数从高到低排序
                Arrays.sort(c, new Comparator<Category>() {
                    public int compare(Category o1, Category o2) {
                        return o2.getOccurCount() - o1.getOccurCount();
                    }
                });
                //输出出现次数大于1的算法标签
                int tot = 0;
                for(int i = 0;i < 50;i++){
                    if(c[i].getOccurCount() > 0){
                        System.out.println(c[i]);
                        tot++;
                    }
                }
                if(tot == 0){
                    System.out.println("未匹配到算法分类");
                }
                System.out.println();
            }

        }
    }
}

为提高爬取效率,我们采用了多线程爬虫,即用多个线程同时爬取不同题目范围的URL信息。特别要注意的是,在本例中多个爬虫线程共用一个控制台,即控制台属于公共资源。在处理公共资源时(输出爬取结果),需要加锁,否则多个线程的爬取结果将交替输出。但不可以对整个crawl方法加锁,这样锁的粒度太大,多线程爬虫的效率没有得到发挥,即同一时刻只允许有一个线程爬取数据,其他线程都被同步阻塞,这显然是不符合预想的。

定义爬虫线程类

import java.io.IOException;

/**
 * 爬虫线程
 */
public class CrawlerThread implements Runnable{
    private int start;
    private int end;

    public CrawlerThread(int start, int end) {
        this.start = start;
        this.end = end;
    }

    public void run() {
        try {
            CrawlUtils.crawl(start,end);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

编写主方法开始爬取

为了提高效率,开了8个线程爬取。

import java.io.*;
import java.util.*;


public class Main {
    private static List<String> candidate = new ArrayList<String>(); //存放候选的算法分类
    private static Category[] categories = new Category[50];//保存不同分类,包括名称和出现次数

    static {  //导入候选的算法分类
        InputStream is = null;
        try {
            is = new FileInputStream("./resource/algorithm.txt");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(is,"GBK"));
            String line;
            while ((line = br.readLine()) != null) {
                candidate.add(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

    public static void main(String[] args) throws IOException {
        //开8个线程爬取
        Thread th[] = new Thread[8];
        for(int i = 0;i < 8;i++){
            th[i] = new Thread(new CrawlerThread((i + 2) * 500,(i + 2) * 500 + 500));
            th[i].start();
        }
    }

    public static List<String> getCandidate() {
        return candidate;
    }

    public static void setCandidate(List<String> candidate) {
        Main.candidate = candidate;
    }

    public static Category[] getCategories() {
        return categories;
    }

    public static void setCategories(Category[] categories) {
        Main.categories = categories;
    }
}

爬取结果

爬取结果

附Github链接:https://github.com/Steven1997/AlgorithmCrawler

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355