Shiliew 植树人

多线程实现Java爬虫

2017-09-19

  之前实现了对网页数据的爬取和存储,然后准备将这些取得的数据进行分析。但是因为需要进行分词以及关键字提取,用到了BosonNLP,无奈免费使用的部分还是有一些限制,再者分析并不是我学习的重点,那就先将分析放下。因为单线程爬虫的效率比较低,因此使用多线程实现Java爬虫。

背景

  使用Java爬虫爬取腾讯招聘的信息。先通过其地址“http://hr.tencent.com/position.php”爬取到各个地区和工作种类的url并保存,然后循环爬取这些url。原本使用的单线程循环遍历去爬取,效率比较低。现在需要使用多线程去竞争这些url,缩短爬取的时间。

//获取需要爬取的url集合
public List<String> getUrl(String url) throws IOException {
        List<String> lidList = new ArrayList<>();
        List<String> tidList = new ArrayList<>();
        Document document = Jsoup.connect(url).post();
        List<String> urlList = new ArrayList<>();
        String css1 = "div#additems a";
        Elements elements1 = document.select(css1);
        String css2 = "div#searchrow3 div.left a";
        Elements elements2 = document.select(css2);
        for (Element element1 : elements1){
            String cityId = element1.attr("href");
            String lid = cityId.substring(cityId.lastIndexOf("=")+1);
            if (!lid.equals("0")){
                lidList.add(lid);
            }
        }
        for (Element element2 : elements2){
            String kindId = element2.attr("href");
            String tid = kindId.substring(kindId.lastIndexOf("=")+1);
            if (!tid.equals("0")){
                tidList.add(tid);
            }
        }
        for (String lid : lidList){
            for (String tid : tidList){
                urlList.add("position.php?keywords=&lid=" + lid + "&tid=" + tid);
            }
        }
        return urlList;
    }

//获取url的Document
public Document getHtml(String url) throws IOException {
        return Jsoup.connect(url).post();
    }

//保存并获得下一页的url
public String printHtml(Document document) throws IOException {
        SaveData saveData = new SaveData();
        saveData.saveDataByTxt(document);
        Element nextNode = document.getElementById("next");
        String url = "javascript:;";
        if (nextNode != null){
            url = nextNode.attr("href");
        }
        return url;
    }

//保存Document中需要的数据
public void saveDataByTxt(Document document) throws IOException {

        String css1 = "div#additems a.active span font";
        Element e1 = document.select(css1).get(0);
        String css2 = "div#searchrow3 div.left a.active span font";
        Element e2 = document.select(css2).get(0);
        String txtName = (e1.ownText() + "_" + e2.ownText() + ".txt").replace("/","or").replace("\\","or");
        File file = new File(path + txtName);
        FileWriter fileWriter = null;
        try {
            fileWriter = new FileWriter(file, true);
            String cssSelect = "table.tablelist tr";
            Elements elements = document.select(cssSelect);
            for (Element element : elements){
                if (!element.attr("class").equals("f")){
                    if (element.attr("class").equals("h") && file.length() > 0){
                        continue;
                    }
                    Elements elements1 = element.getAllElements();
                    for (Element element1 : elements1){
                        String text = element1.ownText().trim().replaceAll("\\u00A0","");
                        if(!StringUtil.isBlank(text)){
                            fileWriter.append(text + "\t");
                        }
                    }
                    Elements aTag = element.select("a");
                    String url = aTag.attr("href");
                    fileWriter.append(url + "\n");
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileWriter != null){
                fileWriter.flush();
                fileWriter.close();
            }
        }

    }

  以上代码是爬取并保存相应的资源,可以略过。

单线程

  单线程实现非常简单,只需要循环遍历即可。

public static void main(String [] args) throws Exception {
        SpiderTest st = new SpiderTest();
        List<String> urlList = st.getUrl("http://hr.tencent.com/position.php");
        for (String url : urlList){
            while (!url.equals("javascript:;")){
                url = "http://hr.tencent.com/"+ url;
                try {
                    url = st.printHtml(st.getHtml(url));
                }catch (Exception e){
                    System.out.println("报错Url:" + url);
                    throw new Exception();
                }
            }
        }
    }

多线程

  使用多线程,即思考需要并行的功能和它们竞争的共同资源,那么这里自然是多个爬虫并行竞争urlList里面的数据,而这个urlList肯定就是由多个爬虫线程共享。

public class SpiderThread implements Runnable{

    private SpiderTest spiderTest;

    private List<String> list;

    private int no;

    //构造方法
    public SpiderThread(List<String> list, SpiderTest spiderTest, int no){
        this.list = list;
        this.spiderTest = spiderTest;
        this.no = no;
    }

    @Override
    public void run() {
        System.out.println("线程" + no +"启动了!");
        String url;
        while (!list.isEmpty()){
            synchronized (list){
                url = list.get(0);
                list.remove(url);
            }
            try {
                while (!url.equals("javascript:;")){
                    url = "http://hr.tencent.com/" + url;
                    url = spiderTest.printHtml(spiderTest.getHtml(url));
                }
            } catch (IOException e) {
                System.out.println("报错Url:" + url);
                e.printStackTrace();
            }
        }
    }
}

  每当爬虫获取list中的一条数据,则将该数据删除,以免其他线程再获得该数据,重复爬取相同的内容。使用synchronized关键字给list加锁,保证线程安全,避免多个线程删除同一条数据。

public static void main(String [] args) throws Exception {
        SpiderTest st = new SpiderTest();
        //多线程启动
        List<String> urlList = st.getUrl("http://hr.tencent.com/position.php");
        for (int i = 0; i < 10 ; i++){
            new Thread(new SpiderThread(urlList, st, i)).start();
        }
    }

总结

  多线程的实现需要知道哪些功能是需要并行执行的,并且并行的线程是否需要访问共享的资源。如果需要,则必须保证该资源的线程安全。


上一篇 Java实现爬虫

Comments

comments powered by HyperComments