基於webmagic框架的多主題爬蟲關鍵詞切換

 

1、背景介紹

       多主題爬蟲中,我們一般先分析網站的url特點(重點是列表頁),再根據項目需求預先設定好關鍵詞,對待爬取url,或者稱爲種子url進行精準控制。

1.1、分析一

       帶關鍵詞的url場景有很多,如網站的特定版塊、某模塊發送的AJAX請求等都嵌入了關鍵詞。

       eg:我們需要爬取同程旅遊網杭州的旅遊景點信息,url是:https://so.ly.com/hot?q=%E6%9D%AD%E5%B7%9E

       其中%E6%9D%AD%E5%B7%9E是Unicode對關鍵詞--“杭州”編碼的結果

       eg:同程旅遊網從杭州到北京的國內遊,url是:

       https://gny.ly.com/list?src=%E6%9D%AD%E5%B7%9E&dest=%E5%8C%97%E4%BA%AC&prop=0,等同於https://gny.ly.com/list?src=杭州&dest=北京&prop=0,實際上也等同於https://gny.ly.com/list?src=杭州&dest=北京。在瀏覽器輸入上述url後會顯示該主題列表的第一頁,點擊下一頁我們會發現該主題列表第二頁url是:

       https://gny.ly.com/list?src=%E6%9D%AD%E5%B7%9E&dest=%E5%8C%97%E4%BA%AC&start=2

       第三頁是:

       https://gny.ly.com/list?src=%E6%9D%AD%E5%B7%9E&dest=%E5%8C%97%E4%BA%AC&start=3

       ····

       https://gny.ly.com/list?src=%E6%9D%AD%E5%B7%9E&dest=%E5%8C%97%E4%BA%AC&start=n

       由此,我們就可以得出該模塊的url拼接規則爲:https://gny.ly.com/list?src=關鍵詞1(Unicode編碼)+“&dest=”+關鍵詞2(Unicode編碼)+“&start=”+index(頁面索引)

       再比如:百度新聞,關鍵詞搜索url:

       https://www.baidu.com/s?rtt=1&bsst=1&cl=2&tn=news&rsv_dl=ns_pc&word=浙江++消防&pn=10

       https://www.baidu.com/s?rtt=1&bsst=1&cl=2&tn=news&rsv_dl=ns_pc&word=浙江++消防&pn=20

1.2、分析二

       再進一步抽象,一般我們在配置文件中設定關鍵詞,或者寫入數據庫中,然後爬蟲從中讀取並存入kw1List和kw2List中,

       兩種方式的配置舉例如下:

  • 配置1(yaml文件)
filters:
  searchfilter:
    kwfixvalue: [ 浙江, 江蘇, 上海, 北京, 天津 ]
    kwvalue: [ 火災, 坍塌, 爆炸, 事故, 安全, 傷亡 ]
  • 配置2(數據庫)
起始城市ID 起始城市名稱 目的地ID 目的地名稱
0510 無錫 0571 杭州
001 北京 021 南京
0519 常州 0996 烏魯木齊

 

       在拼接下一列表頁的邏輯中(即換頁,列表頁切換),我們需要用到的變量是: 當前關鍵詞1、當前關鍵詞2、當前關鍵詞1所處list1中的索引index1、當前關鍵詞2所處list2中的索引index2,以及已爬取到的頁面index(即網站所顯示的第幾頁);

2、解決

       經上述分析,將列表頁url拼接邏輯中表示關鍵詞的選擇切換抽取出來,並用一個pojo類定義,可以命名爲KeywordOptions,代碼如下:

  • currenyPage: 當前頁面索引(表示關鍵詞1+關鍵詞2搜索結果中的第幾頁)
  • currentFixIndex: 關鍵詞2所在list2中的index
  • kwFixValue: 關鍵詞2
  • currentIndex: 關鍵詞1所在list1中的index
  • kwValue: 關鍵詞1
public class KeywordOptions {
    Long currentPage;
    Integer currentFixIndex;
    String kwFixValue = null;
    Integer currentIndex;
    String kwValue = null;

    public KeywordOptions() {
    }

    public Long getCurrentPage() {
        return this.currentPage;
    }

    public void setCurrentPage(Long currentPage) {
        this.currentPage = currentPage;
    }

    public Integer getCurrentFixIndex() {
        return this.currentFixIndex;
    }

    public void setCurrentFixIndex(Integer currentFixIndex) {
        this.currentFixIndex = currentFixIndex;
    }

    public Integer getCurrentIndex() {
        return this.currentIndex;
    }

    public void setCurrentIndex(Integer currentIndex) {
        this.currentIndex = currentIndex;
    }

    public String getKwFixValue() {
        return this.kwFixValue;
    }

    public void setKwFixValue(String kwFixValue) {
        this.kwFixValue = kwFixValue;
    }

    public String getKwValue() {
        return this.kwValue;
    }

    public void setKwValue(String kwValue) {
        this.kwValue = kwValue;
    }
}

       並基於webmagic框架中的PageProcessor接口編寫抽象類BasePageProcessor,在該抽象類中根據通用性業務需求編寫相關方法, 首先是關鍵詞切換邏輯:

private boolean nextKeyword(KeywordOptions ko) {
    if (this.searchFilterConfig == null) {
        return false;
    } else {
        int kwSize = this.kwValues.size();
        int kwFixSize;
        
        if (this.kwFixValues == null) {
            kwFixSize = 0;
        } else {
            kwFixSize = this.kwFixValues.size();
        }

        if (ko.getCurrentIndex() >= kwSize - 1) {
            ko.setCurrentIndex(0);
            if (ko.getCurrentFixIndex() >= kwFixSize - 1) {
                return false;
            } else {
                ko.setCurrentFixIndex(ko.getCurrentFixIndex() + 1);
                ko.setKwValue((String)this.kwValues.get(ko.getCurrentIndex()));
                if (this.kwFixValues != null) {
                    ko.setKwFixValue((String)this.kwFixValues.get(ko.getCurrentFixIndex()));
                }
                return true;
            }
        } else {
            ko.setCurrentIndex(ko.getCurrentIndex() + 1);
            ko.setKwValue((String)this.kwValues.get(ko.getCurrentIndex()));
            if (this.kwFixValues != null) {
                ko.setKwFixValue((String)this.kwFixValues.get(ko.getCurrentFixIndex()));
            }
            return true;
        }
    }
}

 其中根據KeywordOptions對象拼接url的方法如下,將該方法設爲public,以便後續根據不同拼接規則可以繼承重寫

public String koToUrl(KeywordOptions ko) {
    StringBuilder builder = new StringBuilder(this.baseUrl);
    builder.append(ko.getCurrentPage());
    if (this.searchFilterConfig == null) {
        return builder.toString();
    }else if (ko.getKwValue() == null && ko.getKwFixValue() == null) {
        return builder.toString();
    } else {
        builder.append("&");
        if (ko.getKwValue() != null) {
            if (this.kwCharset != null) {
                try {
                    builder.append(URLEncoder.encode(ko.getKwValue(), this.kwCharset));
                } catch (UnsupportedEncodingException var5) {
                    var5.printStackTrace();
                }
            } else {
                builder.append(ko.getKwValue());
            }
        }

        if (ko.getKwFixValue() != null) {
            builder.append("+");
            if (this.kwCharset != null) {
                try {
                    builder.append(URLEncoder.encode(ko.getKwFixValue(), this.kwCharset));
                } catch (UnsupportedEncodingException var4) {
                    var4.printStackTrace();
                }
            } else {
                builder.append(ko.getKwFixValue());
            }
        }

        return builder.toString();
    }
}

最後得到下一列表頁請求(封裝url)

public synchronized Request nextListPage(KeywordOptions ko) {
    //判斷任務是否結束,列表切換是否鎖定
    if (!this.listAddLock && !this.isComplete) {
        //獲取配置文件解析器實例
        ConfigParser parser = ConfigParser.getInstance();
        Boolean fixed = (Boolean)parser.getValue(this.commonConfig, "fixed", false, this.spiderConfig.getConfigPath() + ".common");
        //判斷頁面url是否爲固定
        if (fixed) {
            return null;
        } else {
            String url;
            //判斷當前頁是否爲列表頁尾頁
            if (ko.getCurrentPage() >= this.totalPages) {
                //爲真則切換關鍵詞
                ko.setCurrentPage(Long.valueOf(String.valueOf(this.commonConfig.get("firstpage"))));
                if (this.nextKeyword(ko)) {
                    url = this.koToUrl(ko);
                    return this.nextListPageHook(this.pushRequest(url, ko));
                } else {
                    this.isComplete = true;
                    return this.nextListPageHook((Request)null);
                }
            } else {
                //非尾頁,則當前頁面索引加一
                ko.setCurrentPage(ko.getCurrentPage() + 1L);
                url = this.koToUrl(ko);
                return this.nextListPageHook(this.pushRequest(url, ko));
            }
        }
    } else {
        return null;
    }
}

在BasePageProcessor中編寫頁面處理邏輯,相關代碼如下:

public void process(Page page) {
    Iterator var4;
    if (page.getUrl().toString().contains(this.baseUrl)) {
        //判斷是否下載異常,自定義錯誤碼600
        if (page.getStatusCode() == 600) {
            this.listAddLock = false;
            return;
        }
        //解析列表頁,後續業務會重寫processList(page)方法
        if (this.processList(page)) {
            this.processSuccessListPageCount.incrementAndGet();
            logger.info("list page crawl success url={}", page.getUrl());
            this.listAddLock = false;
        } else {
            this.processErrorListPageCount.incrementAndGet();
            logger.warn("list page crawl failed url={}", page.getUrl());
        }
        //每個List request中存儲KeywordOptions實例
        KeywordOptions ko = (KeywordOptions)JSON.parseObject((String)page.getRequest().getExtra("ko"), KeywordOptions.class);
        if (ko != null) {
            List<Request> requests = page.getTargetRequests();
            var4 = requests.iterator();

            while(var4.hasNext()) {
                Request request = (Request)var4.next();
                request.putExtra("kw", ko.getKwValue());
            }
        }
        //獲取下一列表頁
        Request listpage = this.nextListPage(ko);
        if (listpage != null) {
            listpage.putExtra("nocheckdup", true);
            page.putField("listPage", listpage);
        }
    } else {
        //詳細頁解析,同樣先進行異常檢查
        if (page.getStatusCode() == 600) {
            return;
        }
        
        try {
            //processPage方法也會被後續具體業務重寫
            this.processPage(page);
            this.processSuccessPageCount.incrementAndGet();
        } catch (Exception var7) {
            this.processErrorPageCount.incrementAndGet();
            logger.warn("page process failed url={} , error:{}", new Object[]{page.getUrl(), var7});
        }

        ResultItems items = page.getResultItems();
        String keyword = (String)page.getRequest().getExtra("kw");
        if (keyword == null) {
            keyword = this.kwValues != null ? (String)this.kwValues.get(0) : null;
        }

        if (keyword != null) {
            var4 = items.getAll().entrySet().iterator();

            while(var4.hasNext()) {
                Map.Entry<String, Object> entry1 = (Map.Entry)var4.next();
                Map<String, Object> map = (Map)entry1.getValue();
                map.put("keyword", keyword);
            }
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章