Java爬蟲實(shí)戰(zhàn)抓取一個(gè)網(wǎng)站上的全部鏈接
前言:寫這篇文章之前,主要是我看了幾篇類似的爬蟲寫法,有的是用的隊(duì)列來寫,感覺不是很直觀,還有的只有一個(gè)請求然后進(jìn)行頁面解析,根本就沒有自動爬起來這也叫爬蟲?因此我結(jié)合自己的思路寫了一下簡單的爬蟲。
一 算法簡介
程序在思路上采用了廣度優(yōu)先算法,對未遍歷過的鏈接逐次發(fā)起GET請求,然后對返回來的頁面用正則表達(dá)式進(jìn)行解析,取出其中未被發(fā)現(xiàn)的新鏈接,加入集合中,待下一次循環(huán)時(shí)遍歷。
具體實(shí)現(xiàn)上使用了Map<String, Boolean>,鍵值對分別是鏈接和是否被遍歷標(biāo)志。程序中使用了兩個(gè)Map集合,分別是:oldMap和newMap,初始的鏈接在oldMap中,然后對oldMap里面的標(biāo)志為false的鏈接發(fā)起請求,解析頁面,用正則取出<a>標(biāo)簽下的鏈接,如果這個(gè)鏈接未在oldMap和newMap中,則說明這是一條新的鏈接,同時(shí)要是這條鏈接是我們需要獲取的目標(biāo)網(wǎng)站的鏈接的話,我們就將這條鏈接放入newMap中,一直解析下去,等這個(gè)頁面解析完成,把oldMap中當(dāng)前頁面的那條鏈接的值設(shè)為true,表示已經(jīng)遍歷過了。
最后是當(dāng)整個(gè)oldMap未遍歷過的鏈接都遍歷結(jié)束后,如果發(fā)現(xiàn)newMap不為空,則說明這一次循環(huán)有新的鏈接產(chǎn)生,因此將這些新的鏈接加入oldMap中,繼續(xù)遞歸遍歷,反之則說明這次循環(huán)沒有產(chǎn)生新的鏈接,繼續(xù)循環(huán)下去已經(jīng)不能產(chǎn)生新鏈接了,因?yàn)槿蝿?wù)結(jié)束,返回鏈接集合oldMap
二 程序?qū)崿F(xiàn)
上面相關(guān)思路已經(jīng)說得很清楚了,并且代碼中關(guān)鍵地方有注釋,因此這里就不多說了,代碼如下:
package action; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class WebCrawlerDemo { public static void main(String[] args) { WebCrawlerDemo webCrawlerDemo = new WebCrawlerDemo(); webCrawlerDemo.myPrint("http://www.zifangsky.cn"); } public void myPrint(String baseUrl) { Map<String, Boolean> oldMap = new LinkedHashMap<String, Boolean>(); // 存儲鏈接-是否被遍歷 // 鍵值對 String oldLinkHost = ""; //host Pattern p = Pattern.compile("(https?://)?[^/\\s]*"); //比如:http://www.zifangsky.cn Matcher m = p.matcher(baseUrl); if (m.find()) { oldLinkHost = m.group(); } oldMap.put(baseUrl, false); oldMap = crawlLinks(oldLinkHost, oldMap); for (Map.Entry<String, Boolean> mapping : oldMap.entrySet()) { System.out.println("鏈接:" + mapping.getKey()); } } /** * 抓取一個(gè)網(wǎng)站所有可以抓取的網(wǎng)頁鏈接,在思路上使用了廣度優(yōu)先算法 * 對未遍歷過的新鏈接不斷發(fā)起GET請求,一直到遍歷完整個(gè)集合都沒能發(fā)現(xiàn)新的鏈接 * 則表示不能發(fā)現(xiàn)新的鏈接了,任務(wù)結(jié)束 * * @param oldLinkHost 域名,如:http://www.zifangsky.cn * @param oldMap 待遍歷的鏈接集合 * * @return 返回所有抓取到的鏈接集合 * */ private Map<String, Boolean> crawlLinks(String oldLinkHost, Map<String, Boolean> oldMap) { Map<String, Boolean> newMap = new LinkedHashMap<String, Boolean>(); String oldLink = ""; for (Map.Entry<String, Boolean> mapping : oldMap.entrySet()) { System.out.println("link:" + mapping.getKey() + "--------check:" + mapping.getValue()); // 如果沒有被遍歷過 if (!mapping.getValue()) { oldLink = mapping.getKey(); // 發(fā)起GET請求 try { URL url = new URL(oldLink); HttpURLConnection connection = (HttpURLConnection) url .openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(2000); connection.setReadTimeout(2000); if (connection.getResponseCode() == 200) { InputStream inputStream = connection.getInputStream(); BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream, "UTF-8")); String line = ""; Pattern pattern = Pattern .compile("<a.*?href=[\"']?((https?://)?/?[^\"']+)[\"']?.*?>(.+)</a>"); Matcher matcher = null; while ((line = reader.readLine()) != null) { matcher = pattern.matcher(line); if (matcher.find()) { String newLink = matcher.group(1).trim(); // 鏈接 // String title = matcher.group(3).trim(); //標(biāo)題 // 判斷獲取到的鏈接是否以http開頭 if (!newLink.startsWith("http")) { if (newLink.startsWith("/")) newLink = oldLinkHost + newLink; else newLink = oldLinkHost + "/" + newLink; } //去除鏈接末尾的 / if(newLink.endsWith("/")) newLink = newLink.substring(0, newLink.length() - 1); //去重,并且丟棄其他網(wǎng)站的鏈接 if (!oldMap.containsKey(newLink) && !newMap.containsKey(newLink) && newLink.startsWith(oldLinkHost)) { // System.out.println("temp2: " + newLink); newMap.put(newLink, false); } } } } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } oldMap.replace(oldLink, false, true); } } //有新鏈接,繼續(xù)遍歷 if (!newMap.isEmpty()) { oldMap.putAll(newMap); oldMap.putAll(crawlLinks(oldLinkHost, oldMap)); //由于Map的特性,不會導(dǎo)致出現(xiàn)重復(fù)的鍵值對 } return oldMap; } }
三 最后的測試效果
PS:其實(shí)用遞歸這種方式不是太好,因?yàn)橐蔷W(wǎng)站頁面比較多的話,程序運(yùn)行時(shí)間長了對內(nèi)存的消耗會非常大
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
- JAVA使用爬蟲抓取網(wǎng)站網(wǎng)頁內(nèi)容的方法
- 零基礎(chǔ)寫Java知乎爬蟲之抓取知乎答案
- 零基礎(chǔ)寫Java知乎爬蟲之將抓取的內(nèi)容存儲到本地
- Java爬蟲抓取視頻網(wǎng)站下載鏈接
- java爬蟲Gecco工具抓取新聞實(shí)例
- java正則表達(dá)式簡單使用和網(wǎng)頁爬蟲的制作代碼
- 基于Java HttpClient和Htmlparser實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲代碼
- java實(shí)現(xiàn)簡單的爬蟲之今日頭條
- Java實(shí)現(xiàn)的爬蟲抓取圖片并保存操作示例
- 一篇文章教會你使用java爬取想要的資源
相關(guān)文章
mybatis-plus如何配置自定義數(shù)據(jù)類型TypeHandle
這篇文章主要介紹了mybatis-plus如何配置自定義數(shù)據(jù)類型TypeHandle,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01關(guān)于@JsonProperty和@JSONField注解的區(qū)別及用法
這篇文章主要介紹了關(guān)于@JsonProperty和@JSONField注解的區(qū)別及用法,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08Java實(shí)現(xiàn)畫線、矩形、橢圓、字符串功能
本篇文章主要介紹了Java實(shí)現(xiàn)畫線、矩形、橢圓、字符串功能的實(shí)例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05SpringBoot編譯target目錄下沒有resource下的文件踩坑記錄
這篇文章主要介紹了SpringBoot編譯target目錄下沒有resource下的文件踩坑記錄,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08關(guān)于@PostConstruct、afterPropertiesSet和init-method的執(zhí)行順序
這篇文章主要介紹了關(guān)于@PostConstruct、afterPropertiesSet和init-method的執(zhí)行順序,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09spring boot前后端傳參的實(shí)現(xiàn)
這篇文章主要介紹了spring boot前后端傳參的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01SpringBoot整合Drools的實(shí)現(xiàn)步驟
Drools是一個(gè)易于訪問企業(yè)策略、易于調(diào)整以及易于管理的開源業(yè)務(wù)規(guī)則引擎,符合業(yè)內(nèi)標(biāo)準(zhǔn),速度快、效率高。業(yè)務(wù)分析師或?qū)徍巳藛T可以利用它輕松查看業(yè)務(wù)規(guī)則,從而檢驗(yàn)是否已編碼的規(guī)則執(zhí)行所需的業(yè)務(wù)規(guī)則。本文將講述SpringBoot整合Drools的步驟2021-05-05synchronized?和?Lock?的異同點(diǎn)(如何讓選擇)
這篇文章主要介紹了?synchronized和Lock的異同點(diǎn)(如何讓選擇),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09