网络爬虫(1)
??? 我們現(xiàn)在從需求中提取關(guān)鍵詞來逐步分析問題。
??? 首先是“種子節(jié)點”。它就是一個或多個在爬蟲程序運行前手動給出的URL(網(wǎng)址),爬蟲正是下載并解析這些種子URL指向的頁面,從中提取出新的URL,然后重復(fù)以上的工作,直到達到設(shè)定的條件才停止。
??? 然后是“特定的策略”。這里所說的策略就是以怎樣的順序去請求這些URL。如下圖是一個簡單的頁面指向示意圖(實際情況遠比這個復(fù)雜),頁面A是種子節(jié)點,當(dāng)然最先請求。但是剩下的頁面該以何種順序請求呢?我們可以采用深度優(yōu)先遍歷策略,通俗講就是一條路走到底,走完一條路才再走另一條路,在下圖中就是按A,B,C,F,D,G,E,H的順序訪問。我們也可以采用寬度優(yōu)先遍歷策略,就是按深度順序去遍歷,在下圖中就是按A,B,C,D,E,F,G,H的順序請求各頁面。還有許多其他的遍歷策略,如Google經(jīng)典的PageRank策略,OPIC策略策略,大站優(yōu)先策略等,這里不一一介紹了。我們還需要注意的一個問題是,很有可能某個頁面被多個頁面同時指向,這樣我們可能重復(fù)請求某一頁面,因此我們還必須過濾掉已經(jīng)請求過的頁面。
??? 最后是“設(shè)定的條件”,爬蟲程序終止的條件可以根據(jù)實際情況靈活設(shè)置,比如設(shè)定爬取時間,爬取數(shù)量,爬行深度等。
??? 到此,我們分析完了爬蟲如何開始,怎么運作,如何結(jié)束(當(dāng)然,要實現(xiàn)一個強大,完備的爬蟲要考慮的遠比這些復(fù)雜,這里只是入門分析),下面給出整個運作的流程圖:
?
?數(shù)據(jù)結(jié)構(gòu)分析??? 根據(jù)以上的分析,我們需要用一種數(shù)據(jù)結(jié)構(gòu)來保存初始的種子URL和解析下載的頁面得到的URL,并且我們希望先解析出的URL先執(zhí)行請求,因此我們用隊列來儲存URL。因為我們要頻繁的添加,取出URL,因此我們采用鏈?zhǔn)酱鎯ΑO螺d的頁面解析后直接原封不動的保存到磁盤。
?技術(shù)分析??? 所謂網(wǎng)絡(luò)爬蟲,我們當(dāng)然要訪問網(wǎng)絡(luò),我們這里使用jsoup,它對http請求和html解析都做了良好的封裝,使用起來十分方便。根據(jù)數(shù)據(jù)結(jié)構(gòu)分析,我們用LinkedList實現(xiàn)隊列,用來保存未訪問的URL,用HashSet來保存訪問過的URL(因為我們要大量的判斷該URL是否在該集合內(nèi),而HashSet用元素的Hash值作為“索引”,查找速度很快)。
?
?代碼?
??? 以上分析,我們一共要實現(xiàn)2個類:
?
? ?① JsoupDownloader,該類是對Jsoup做一個簡單的封裝,方便調(diào)用。暴露出以下幾個方法:
—public Document downloadPage(String url);根據(jù)url下載頁面
—public Set<String> parsePage(Document doc, String regex);從Document中解析出匹配regex的url。
—public void savePage(Document doc, String saveDir, String saveName, String regex);保存匹配regex的url對應(yīng)的Document到指定路徑。
?
??? ② UrlQueue,該類用來保存和獲取URL。暴露出以下幾個方法:
—public void enQueue(String url);添加url。
—public String deQueue();取出url。
—public int getVisitedCount();獲取訪問過的url的數(shù)量;
?
package com.hjzgg.spider;import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.net.SocketTimeoutException; import java.util.HashSet; import java.util.Map; import java.util.Set;import org.jsoup.Connection; import org.jsoup.Connection.Response; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements;public class JsoupDownloader {public static final String DEFAULT_SAVE_DIR = "c:/download/";private static JsoupDownloader downloader;private JsoupDownloader() {}public static JsoupDownloader getInstance() {if (downloader == null) {synchronized (JsoupDownloader.class) {if (downloader == null) {downloader = new JsoupDownloader();}}}return downloader;}public Document downloadPage(String url) {try {System.out.println("正在下載" + url);Connection connection = Jsoup.connect(url);connection.timeout(1000);connection.followRedirects(false);//默認是true,也就是連接遵循重定向!設(shè)置為false,對重定向的地址進行篩選Response response = connection.execute();Map<String, String> headers = response.headers();System.out.println(response.statusCode() + " " + response.statusMessage());if(response.statusCode()==301 || response.statusCode()==302){重定向地址,位于信息頭header中Main.urlQueue.enQueue(headers.get("Location"));} else if(response.statusCode() == 404){//或者一些其他的錯誤信息,直接將改地址丟棄return null;}for(String name : headers.keySet())//在這里可以查看http的響應(yīng)信息頭信息System.out.println(name + " : " + headers.get(name));return connection.get();} catch(SocketTimeoutException e){//對于連接超時的url我們可以重新將其放入未訪問url隊列中 Main.urlQueue.enQueueUrlTimeOut(url);}catch (IOException e) {e.printStackTrace();}return null;}public Set<String> parsePage(Document doc, String regex) {Set<String> urlSet = new HashSet<String>();if (doc != null) {Elements elements = doc.select("a[href]");for (Element element : elements) {String url = element.attr("href");if (url.length() > 6 && !urlSet.contains(url)) {if (regex != null && !url.matches(regex)) {continue;}if(!url.contains("http"))url = doc.baseUri()+url;urlSet.add(url);}}}return urlSet;}public void savePage(Document doc, String saveDir, String saveName, String regex) {if (doc == null) {return;}if (regex != null && doc.baseUri() != null && !doc.baseUri().matches(regex)) {return;}saveDir = saveDir == null ? DEFAULT_SAVE_DIR : saveDir;saveName = saveName == null ? doc.title().trim().replaceAll("[\\?/:\\*|<>\" ]", "_") + System.nanoTime() + ".html" : saveName;File file = new File(saveDir + "/" + saveName);File dir = file.getParentFile();if (!dir.exists()) {dir.mkdirs();}PrintWriter printWriter;try {printWriter = new PrintWriter(file);printWriter.write(doc.toString());printWriter.close();} catch (FileNotFoundException e) {e.printStackTrace();}} } package com.hjzgg.spider;import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.Set;public class UrlQueue {private Set<String> visitedSet;// 用來存放已經(jīng)訪問過多urlprivate LinkedList<String> unvisitedList;// 用來存放未訪問過多urlpublic UrlQueue(String[] seeds) {visitedSet = new HashSet<String>();unvisitedList = new LinkedList<String>();unvisitedList.addAll(Arrays.asList(seeds));}/*** 添加url* * @param url*/public void enQueue(String url) {if (url != null && !visitedSet.contains(url)) {unvisitedList.addLast(url);}}/*** 添加訪問超時的url* * @param url*/public void enQueueUrlTimeOut(String url) {if (url != null) {visitedSet.remove(url);unvisitedList.addLast(url);}}/*** 添加url* * @param urls*/public void enQueue(Collection<String> urls) {for (String url : urls) {enQueue(url);}}/*** 取出url* * @return*/public String deQueue() {try {String url = unvisitedList.removeFirst();while(visitedSet.contains(url)) {url = unvisitedList.removeFirst();}visitedSet.add(url);return url;} catch (NoSuchElementException e) {System.err.println("URL取光了");}return null;}/*** 得到已經(jīng)請求過的url的數(shù)目* * @return*/public int getVisitedCount() {return visitedSet.size();} }?
package com.hjzgg.spider;import java.util.Set;import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements;public class Main {static UrlQueue urlQueue = new UrlQueue(new String[] { "http://192.168.1.201:8080/HZML/" });public static void main(String[] args) {JsoupDownloader downloader = JsoupDownloader.getInstance();long start = System.currentTimeMillis();while (urlQueue.getVisitedCount() < 1000) {String url = urlQueue.deQueue();if (url == null) {break;}Document doc = downloader.downloadPage(url);if (doc == null) {continue;}Set<String> urlSet = downloader.parsePage(doc, "userRequest\\?userRequest=showNoParticipateTask&taskid=\\d{1,2}|http://www.cnblogs.com/hujunzheng/(p|default|archive/\\d{4}/\\d{2}/\\d{2}/).*");urlQueue.enQueue(urlSet);downloader.savePage(doc, "I:\\博客園-hjzgg", null, "userRequest\\?userRequest=showNoParticipateTask&taskid=\\d{1,2}|http://www.cnblogs.com/hujunzheng/(p|default|archive/\\d{4}/\\d{2}/\\d{2}/).*");System.out.println("已請求" + urlQueue.getVisitedCount() + "個頁面");}long end = System.currentTimeMillis();System.out.println(">>>>>>>>>>抓取完成,共抓取" + urlQueue.getVisitedCount() + "到個頁面,用時" + ((end - start) / 1000) + "s<<<<<<<<<<<<");} }注:userRequest\\?userRequest=showNoParticipateTask&taskid=\\d{1,2} 是本地的網(wǎng)站中網(wǎng)頁里的地址,當(dāng)然種子地址也是本地網(wǎng)站!
在請求這個地址后會產(chǎn)生地址重定向,到博客園地址http://www.cnblogs.com/hujunzheng/,并完成相應(yīng)的下載!
1.這是請求超時的情況
2.正常訪問或者重定向訪問
?
轉(zhuǎn)載于:https://www.cnblogs.com/hujunzheng/p/4852948.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
- 上一篇: 期权买方并非收益无限,期权卖方的收益也不
- 下一篇: 阿里本地生活服务公司是什么