摘要:基於 Jsoup 實現一個 Android 的網絡爬蟲程序,抓取網頁的內容並顯示出來。寫這個程序的主要目的是抓取海投網的宣講會信息(公司、時間、地點)並在移動端顯示,這樣就能夠隨時隨地的瀏覽在學校舉辦的宣講會信息了。html
Jsoup 是一個 Java 的開源HTML解析器,可直接解析某個URL地址、HTML文本內容。它提供了一套很是方便的API,可經過DOM,CSS以及相似於jQuery的操做方法來取出和操做數據。java
Jsoup主要有如下功能:node
從一個URL,文件或字符串中解析HTML;android
使用DOM或CSS選擇器來查找、取出數據;git
對HTML元素、屬性、文本進行操做;github
清除不受信任的HTML (來防止XSS攻擊)瀏覽器
好了,下面寫幾段代碼來講明 Jsoup 是如何優雅的進行 HTML 文檔處理的。首先,咱們須要去Jsoup官網 下載Jsoup的jar
包,而後加入項目的依賴庫中。網絡
Jsoup 能夠從一個字符串、文件或者一個 URL 中解析HTML,解析的目的主要是爲了獲得一個乾淨完整的解析結果,並生成 Document 對象實例。多線程
// Parse a document from a String String html = "<html><head><title>神奕的博客</title></head>" +"<body><p>搭個博客寫學習筆記!!</p></body></html>"; Document doc = Jsoup.parse(html); // Load a Document from a File File input = new File("D://a.html"); Document doc = Jsoup.parse(input, "UTF-8"); // Load a Document from a URL Document doc = Jsoup.connect("http://example.com/").get();
當加載和解析一個本地的HTML文件時,若是在加載文件的時候發生錯誤,將拋出 IOException,應做適當處理。dom
將HTML解析成一個Document
以後,就可使用傳統的 DOM 方法進行數據抽取。例如:
// 海投網 String url = "http://xjh.haitou.cc/wh/uni-1/after/hold/page-1/"; Document doc = Jsoup.connect(url).get(); Elements elements = doc.getElementsByTag("company"); for(Element e : elements) { System.out.println(e.text()); }
Document 對象和 Elements 對象提供了一系列相似於DOM的方法來查找元素,好比 getElementById(String id)、getElementsByTag(String tag) 等等。更多方法請看《Jsoup Cookbook》。
另外,還可使用 Selector 選擇器(相似於CSS或jQuery語法)來查找元素。以下:
// 海投網 String url = "http://xjh.haitou.cc/wh/uni-1/after/hold/page-1/"; Document doc = Jsoup.connect(url).get(); // 經過標籤company查找元素 Elements company = doc.select("company"); // 帶有href屬性的a元素 Elements links = doc.select("a[href]"); // 擴展名爲.png的圖片 Elements pngs = doc.select("img[src$=.png]"); // class等於content的div標籤 Element content = doc.select("div.content").first();
選擇器實現了很是強大和靈活的查找功能。select
方法在Document、Element 或 Elements 對象中均可以使用,且是上下文相關的,所以可實現指定元素的過濾或者鏈式選擇訪問。select
方法將返回一個Elements集合,並提供一組方法來抽取和處理結果。
經過 DOM 方法或者 Selector 方法查找到一些 Elements 元素以後,咱們須要從這些元素中取得數據,下面是幾個經常使用的方法:
取得一個屬性的值,可使用Node.attr(String key)
方法;
取得一個元素中的文本,可使用Element.text()
方法;
取得元素或屬性中的HTML內容,可用Element.html()
或Node.outerHtml()
方法
取得一個元素的 id :Element.id()
取得一個元素的標籤名:Element.tagName()
取得一個元素的類名:Element.className()
在解析一個 Document 以後可能想修改其中的某些屬性值、HTML或文本內容,而後再保存到磁盤或都輸出到前臺頁面。例如:咱們能夠爲文檔中的全部圖片增長可點擊連接、修改連接地址或者是修改文本等。Jsoup 提供了不少方法用來進行修改,這裏就不列舉了,請移步 Jsoup Cookbook。
海投網是一個爲高校畢業生服務的招聘信息網,創始人是華中科技大學的畢業生。如今我要抓取在華中科技大學舉辦的宣講會的信息,網頁以下圖:
查看網頁源代碼,以下圖:
能夠看出,公司名是在一個 company 標籤內,宣講會時間是在一個類名爲 text-center 的 td 標籤內,學校的具體地點則是在一個類名爲 preach-tbody-addre 的 td 標籤內。這麼一分析,要提取華中科技大學的宣講會信息就變得挺簡單了。
Java代碼以下:
import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { String url = "http://xjh.haitou.cc/wh/uni-1/after/hold/page-1/"; Connection conn = Jsoup.connect(url); // 修改http包中的header,假裝成瀏覽器進行抓取 conn.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:32.0) Gecko/ 20100101 Firefox/32.0"); Document doc = conn.get(); // 獲取tbody元素下的全部tr元素 Elements elements = doc.select("tbody tr"); for(Element element : elements) { String companyName = element.getElementsByTag("company").text(); String time = element.select("td.text-center").first().text(); String address = element.getElementsByClass("preach-tbody-addre").text(); System.out.println("公司:"+companyName); System.out.println("宣講時間:"+time); System.out.println("宣講學校:華中科技大學"); System.out.println("具體地點:"+address); System.out.println("---------------------------------"); } } }
某些網站禁止爬蟲,不能抓取或者抓取必定數量後封IP。這時候咱們須要假裝成瀏覽器進行抓取,這能夠經過修改http包中的header來實現(設置User-Agent)。運行上面的程序獲得輸出結果:
公司:瑞聲科技(常州)有限公司 宣講時間:2015-03-07 19:00(週六) 宣講學校:華中科技大學 具體地點:大學生活動中心305階梯教室 --------------------------------- 公司:普聯技術有限公司 宣講時間:2015-03-08 19:00(週日) 宣講學校:華中科技大學 具體地點:大學生活動中心305階梯教室 --------------------------------- 公司:大聯大投資控股股份有限公司 宣講時間:2015-03-09 09:30(週一) 宣講學校:華中科技大學 具體地點:大學生活動中心305階梯教室 --------------------------------- ......
開發 Android 程序,你須要搭建開發環境,很簡單:先安裝Java的JDK(最好不低於1.6),而後去Android官網下載並安裝 Android Studio 就好了。
在Android程序中使用 Jsoup 須要注意兩點:
在AndroidManifest.xml文件中添加網絡訪問權限android.permission.INTERNET
Android在4.0以後,不容許在主線程裏執行網絡(http)請求,也就是說 Jsoup 的代碼須要寫在子線程裏。
4.0 版本之後,若是你在主線程裏嘗試進行網絡操做,會報android.os.NetworkOnMainThreadException 的異常。因此咱們須要開闢子線程進行異步加載,用到Thread
、Runnable
、Handler
這三個類:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.share_mblog_view); // 開闢一個線程 new Thread(runnable).start(); } Runnable runnable = new Runnable(){ @Override public void run() { /** * 要執行的操做 */ // 執行完畢後給handler發送一個空消息 handler.sendEmptyMessage(0); } } Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); /** * 處理UI */ // 當收到消息時就會執行這個方法 } }
若是在沒有可用網絡的狀況下執行網絡爬蟲程序,App將會報錯。因此在每次執行以前都應該先判斷網絡是否可用。大體步驟以下:
① 獲取ConnectivityManager對象
Context context = activity.getApplicationContext(); // 獲取手機全部鏈接管理對象(包括對wi-fi,net等鏈接的管理) ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);② 獲取NetworkInfo對象
NetworkInfo info = cm.getActiveNetworkInfo();③ 判斷網絡類型,Android的網絡分爲兩大類:WIFI 和 手機網絡
// WIFI 斷定條件 info != null && info.getType() == ConnectivityManager.TYPE_WIFI // 手機網絡 斷定條件 info !=null && info.getType() == ConnectivityManager.TYPE_MOBILE
而手機網絡具體又分爲不少類,好比移動3G、移動2G、聯通2G等等。這裏就不說了,自行Google。
④ 判斷網絡鏈接是否可用(包括全部網絡類型)
public boolean isNetworkAvailable(Activity activity) { Context context = activity.getApplicationContext(); ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (cm == null) return false; else { // 獲取全部NetworkInfo對象 NetworkInfo[] networkInfo = cm.getAllNetworkInfo(); if (networkInfo != null && networkInfo.length > 0) { for (int i = 0; i < networkInfo.length; i++) if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) return true; // 存在可用的網絡鏈接 } } return false; }注意,上述操做須要在 AndroidManifest.xml 文件中添加訪問網絡狀態的權限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
另外,本程序在 UI 界面開發上涉及到 Android 中的 ListView(顯示)、PopupWindow(菜單)、ProgressDialog(加載)、AlertDialog(提示)等控件的使用。由於本文並非討論 Android 控件的使用,在這裏就不贅述了。
源碼下載:https://github.com/SongLee24/android-crawler