前幾天寫一個爬蟲,看到網上有使用jsoup直接去訪問並抓取目標url,可是我的感受jsoup解析html還行,其直接鏈接目標網頁的能力仍是相較HttpClient弱一些,因此使用了HttpClient來鏈接並下載目標網頁,而只單純的使用jsoup來解析網頁。html
jsoup解析網頁有幾種方法:包括從輸入流,從本地File對象,從url,從html字符串中。爲了不使用jsoup本身的網絡接口,同時減少磁盤的io,因此使用從輸入流中來進行Parse。java
InputStream htmlStream = entity.getContent(); reStream = saveToLocal(htmlStream, filePath);
將getContent函數返回的輸入流傳入下面的saveLocal中,實現將網頁文件存儲到本地磁盤上。而後再次把一樣的輸入流對象再交給jsoup來解析。網絡
Document doc = Jsoup.parse(htmlIn, "UTF-8", "");
而後執行程序,發現從目標輸入流中解析出來的待抓取set永遠是空的,斷點一看。才發現由於doc就已是空的了。原來,輸入流在一次read之後,其內部的指針已經移動到了尾部,因此對於同一個輸入流來講,通常是不能重複使用的。ide
可是還有一個辦法,就是使用inputStream接口中定義的mark方法和reset方法來實現一個相似於標記,返回的功能。在衆多輸入流中,貌似只有BufferInputStream這個輸入流已經實現並重寫的合適的mark和reset函數。也就是會說,其餘的大部分輸入流不具備這個二次使用的功能。除非本身再重寫其源碼。因此,直接將網絡流包裝成BufferInputSteam來使用其標記-回溯功能。函數
public BufferedInputStream saveToLocal(InputStream content, String filePath) throws IOException { FileOutputStream out = new FileOutputStream(filePath); BufferedInputStream reader = new BufferedInputStream(content,8192000); byte[] containter = new byte[1024]; reader.mark(reader.available() + 1); int count = 0; while ((count = reader.read(containter)) > 0) { out.write(containter); } out.flush(); out.close(); reader.reset(); return reader; }
這裏的意思是,先將網絡流包裝成BufferInputStream,而後再讀取輸入流以前,先標記一下,而後再讀取完畢,且輸出到輸出中之後,調用reset方法,將該輸入流的指針回溯到初始位置,而後這纔將其交給jsoup來用。這樣就解決了二次利用的問題。url
可是,在實際的運行過程當中,又發現了一個問題。就是,在抓取百度網頁時無壓力,可是在抓取新浪時出現一個「reset失敗」的exception。通過一段時間的查詢求解,原來這個mark和reset還有一些使用的限制的。他只能配合咱們的BufferInputStream的緩衝區來使用,也就是說:指針
假設緩衝區大小爲10,咱們要讀取的文件大小爲5,那麼咱們讀入前標記文件的起始位置,而後執行讀取。這時候,整個文件從首到尾都放入了緩衝區,因此咱們執行reset能夠找到標記,同時重定位到標記所指定的位置。這種狀況下尚未問題,可是若是文件並緩衝區大,咱們的緩衝區須要更新,好比一個100大小的文件,那麼在讀取到最後時,緩衝區裏只存儲了90-100的這一段內容,咱們再reset,就已經找不到那個標記了,因此reset函數就會報錯。htm
因此使用mark-reset時,要注意對緩衝區大小進行合理的設置,若是要讀取的文件老是很大,咱們也不可能把緩衝區調的那麼的大,則此時,咱們可能要作好,對於這樣大的文件的輸入流可能真的不能屢次使用的心理準備咯!對象