網路爬蟲技術Jsoup——爬到一切你想要的
本文由我的微信公眾號(bruce常)原創首發,
並同步發表到csdn部落格,歡迎轉載,2016年12月11日。
概述:
本週五,接到一個任務,要使用爬蟲技術來獲取某點評網站裡面關於健身場館的資料,之前從未接觸過爬蟲技術,於是就從網上搜了一點學習資料,本篇文章就記錄爬蟲技術Jsoup技術,爬蟲技術聽名稱很牛叉,其實沒什麼難點,慢慢的用心學習就會了。
Jsoup介紹:
Jsoup 是一個 Java 的開源HTML解析器,可直接解析某個URL地址、HTML文字內容,Jsoup官網jar包下載地址。
Jsoup主要有以下功能:
1. 從一個URL,檔案或字串中解析HTML
2. 使用DOM或CSS選擇器來查詢、取出資料
3. 對HTML元素、屬性、文字進行操作
4. 清除不受信任的HTML (來防止XSS攻擊)
使用Jsoup爬蟲技術你需要的能力有:
- 我們是用安卓開發的,首先肯定要有一定的安卓開發能力,會寫簡單的頁面。
- Jsoup中用到了Javascript語言,沒有此語言能力在獲取資料的時候就比較吃力,這是此爬蟲技術的重中之重。
- 查閱文件與解決問題的能力和技巧(有點廢話)
上面三條中對於一個安卓開發者來說,最難的就是熟練使用Javascript語言,小編就遇到了這個問題,小編還有一定的javascript基礎,系統的學習過此語言,但是在使用中還是很吃力的,問同學、問朋友、問同事,最後還是靠自己來獲取自己想要的資料。
爬蟲技術沒那麼難,思路就是這麼的簡單
- 得到自己想要爬取資料的url.
- 通過Jsoup的jar包中的方法將Html解析成Document,
- 使用Document中的一些列get、first、children等方法獲取自己想要的資料,如圖片地址、名稱、時間。
- 將得到的資料封裝成自己的實體類。
- 將實體中的資料在頁面載入出來。
實戰,獲取**點評網站中的場館資料:
先奉上效果圖,沒有圖不說話:
這就是今天要實現的效果,左邊圖片是場館的logo,右邊上方是場館的名稱,下邊是場館的地址資訊,點選進去可以根據超連結地址跳轉新的頁面,頁面的Url地址小編已經拿到,但可能是因為重定向的問題,webview沒有載入出來,有興趣的可以輸入連結地址來驗證。
首先:新建一個空的專案.
上面的效果,只要接觸過安卓開發的都能寫出來,所以不是本篇文章的重點,這裡就不過多說明,大家可以使用ListView或者RecyclerView來實現,我這裡用ListView。
小編這裡是為了加入側邊欄所以使用的是DrawerLayout,但後來沒有用到,所以也就沒有側邊欄的效果,不過後期如有時間會加上去的,上一頁下一頁是為了簡單的模仿瀏覽器中的操作,此效果只能顯示前9頁資料,網頁連結中有50頁的資料,為什麼沒有實現呢?
很簡單,因為50頁的連結地址不是一次性返回的,小編為了方便,只獲取了前9頁資料的url,畢竟是為了抓取資料顯示而已。
其次:主程式設計
- 通過網頁得到**點評健身場館的url地址是:http://www.dianping.com/search/category/2/45
- 抓取資料是一個耗時的操作,需要在一個執行緒中完成,這裡使用 new Thread(runnable).start()方式,在runnable程式碼中獲取場館的logo、名稱、地址如下:
Runnable runnable = new Runnable() {
@Override
public void run() {
Connection conn = Jsoup.connect(url);
// 修改http包中的header,偽裝成瀏覽器進行抓取
conn.header("User-Agent", userAgent);
Document doc = null;
try {
doc = conn.get();
} catch (IOException e) {
e.printStackTrace();
}
//獲取場館的資料
Element elementDiv = doc.getElementById("shop-all-list");
Elements elementsUl = elementDiv.getElementsByTag("ul");
Elements elements = elementsUl.first().getElementsByTag("li");
for (Element element : elements) {
Elements elements1 = element.children();
String targetUrl = elements1.get(0).getElementsByTag("a").attr("href");
String img = elements1.get(0).getElementsByTag("img").first().attr("data-src");
if (img.contains(".jpg")) {
int a = img.indexOf(".jpg");
img = img.substring(0, a + 4);
}
String radiumName = elements1.get(1).child(0).getElementsByTag("h4").text();
String address0 = elements1.get(1).child(2).getElementsByTag("a").get(1).text();
String address1 = elements1.get(1).child(2).getElementsByClass("addr").text();
RadiumBean radiumBean = new RadiumBean();
radiumBean.setImg(img);
radiumBean.setName(radiumName);
radiumBean.setAddress(address0 + " " + address1);
list.add(radiumBean);
}
// 執行完畢後給handler傳送一個空訊息
Message message = new Message();
message.arg1 = Integer.parseInt(curPage);
handler.sendMessage(message);
}
};
- 通過Jsoup.connect()方法,根據目標地址url來得到Connection物件,
- 將我們的app偽裝成瀏覽器,防止人家後臺發現我們在爬取人家的資料,這需要修改修改http包中的header,來設定User-Agent,此值可以在谷歌瀏覽器中輸入“about:version”來檢視,也可以訪問此地址檢視。
- 通過Connection物件的get()方法來獲得整個頁面原始碼所在的Document
- 通過分析原始碼,使用Document的物件來得到我們想要的資料,上面程式中img待變場館logo的url,radiumName是小編得到的場館的名稱,address0和address1是小編得到的場館地址的資訊,這裡通過組合來使用。
- 構造我們ListView所用到的資料
- 通過Handle來更新頁面資訊,curPage(當前頁)稍後說明。
- 在得到資料後頁面載入顯示
if (!list.isEmpty()) {
MyAdapter adapter = new MyAdapter(list, MainActivity.this);
info_list_view.setAdapter(adapter);
}
4.點選跳轉到場館的詳情頁,這裡本想用Webview載入的,但是可能是網頁重定向的問題,webview也能載入出來,但一會就顯示無法連線網路,所以場館詳情頁就顯示出了我們得到的場館詳情頁的url。
基本的抓取資料、載入資料流程就是這樣的,但是僅僅靠上面的資料還是不能完全實現我們的效果的。
完善頁面,實現上下頁翻頁功能。
- 頁面在爬取資料的時候顯示一個ProgressDialog來提示使用者。
ProgressDialog dialog = new ProgressDialog(this);
dialog.setMessage("正在抓取資料...");
dialog.setCancelable(false);
dialog.show();
資料載入完畢,關閉此dialog。
dialog.dismiss();
2.ProgresDialog載入前做是否有網路的判斷,有網的時候才顯示ProgressDialog,無網路的時候給出提示。
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;
}
3.完善runnable,抓取當前頁碼、上一頁、下一頁的連結地址。
// 獲取頁數的連結
if (firstLoad) {
Elements elementsPages = doc.getElementsByClass("content-wrap");
Elements elementsPageA = elementsPages.first().getElementsByClass("shop-wrap").first().child(1).getElementsByTag("a");
for (int i = 0; i < elementsPageA.size() - 2; i++) {
Element element = elementsPageA.get(i);
Element element1 = element.getElementsByClass("cur").first();
Map<String, Object> map = new HashMap<>();
if (element1 != null) {
curPage = element1.text();
map.put("page", "" + (i + 1));
map.put("url", url);
mMapList.add(map);
} else {
map.put("page", "" + (i + 1));
map.put("url", element.attr("href"));
mMapList.add(map);
}
}
}
firstLoad = false;
因為在網頁中,第一次進入返回了前9頁和第50頁的資料,這裡只取前9頁的資料,firstLoad代表第一次載入,mMapList用來存放頁碼和頁面跳轉時候的url,對js中的程式碼不明白的朋友們,要好好學學js,這裡小編就不介紹js了,至於我為什麼知道取這些欄位,那是小編盯著網頁源程式程式碼看了半天看出來的。
- 這個時候就用到了之前runnable中的Message物件中的curPage
curPage代表當前頁碼,從1開始………………在handle接收到訊息後顯示此頁碼資訊。
tvCurrentPage.setText("" + msg.arg1);
- 模仿網頁的上一頁下一頁,我們需要處理TextView的點選事件。
下一頁事件:
if (curPage.equals("" + (mMapList.size()))) {
Toast.makeText(this, "末頁", Toast.LENGTH_SHORT).show();
} else {
curPage = "" + (Integer.parseInt(curPage) + 1);
url = "http://www.dianping.com" + mMapList.get(Integer.parseInt(curPage) - 1).get("url").toString();
switchOver();
tvCurrentPage.setText(curPage);
}
上一頁事件:
if (curPage.equals("1")) {
Toast.makeText(this, "首頁", Toast.LENGTH_SHORT).show();
} else {
curPage = "" + (Integer.parseInt(curPage) - 1);
if (curPage.equals(1)) {
url = "http://www.dianping.com/search/category/2/45";
} else {
url = "http://www.dianping.com" + mMapList.get(Integer.parseInt(curPage) - 1).get("url").toString();
}
switchOver();
tvCurrentPage.setText(curPage);
}
經過小編測試,在點選下一頁的時候沒有bug,在點選上一頁的時候,會出現doc為null,從而奔潰的bug,小編在努力解決中,但還沒解決掉。
- 附上完整的runnable程式碼,畢竟這是此程式的關鍵部分。
Runnable runnable = new Runnable() {
@Override
public void run() {
Connection conn = Jsoup.connect(url);
// 修改http包中的header,偽裝成瀏覽器進行抓取
conn.header("User-Agent", userAgent);
Document doc = null;
try {
doc = conn.get();
} catch (IOException e) {
e.printStackTrace();
}
// 獲取頁數的連結
if (firstLoad) {
Elements elementsPages = doc.getElementsByClass("content-wrap");
Elements elementsPageA = elementsPages.first().getElementsByClass("shop-wrap").first().child(1).getElementsByTag("a");
for (int i = 0; i < elementsPageA.size() - 2; i++) {
Element element = elementsPageA.get(i);
Element element1 = element.getElementsByClass("cur").first();
Map<String, Object> map = new HashMap<>();
if (element1 != null) {
curPage = element1.text();
map.put("page", "" + (i + 1));
map.put("url", url);
mMapList.add(map);
} else {
map.put("page", "" + (i + 1));
map.put("url", element.attr("href"));
mMapList.add(map);
}
}
}
firstLoad = false;
//獲取場館的資料
Element elementDiv = doc.getElementById("shop-all-list");
Elements elementsUl = elementDiv.getElementsByTag("ul");
Elements elements = elementsUl.first().getElementsByTag("li");
for (Element element : elements) {
Elements elements1 = element.children();
String targetUrl = elements1.get(0).getElementsByTag("a").attr("href");
String img = elements1.get(0).getElementsByTag("img").first().attr("data-src");
if (img.contains(".jpg")) {
int a = img.indexOf(".jpg");
img = img.substring(0, a + 4);
}
String radiumName = elements1.get(1).child(0).getElementsByTag("h4").text();
String address0 = elements1.get(1).child(2).getElementsByTag("a").get(1).text();
String address1 = elements1.get(1).child(2).getElementsByClass("addr").text();
// StringBuilder stringBuilder = new StringBuilder();
//
// if (elements1.get(2).child(0).children().size()>0){
// String youhui = "";
// if (!"".equals(elements1.get(2).child(0).child(0).getElementsByClass("more").text())){
// youhui = elements1.get(2).child(0).getElementsByTag("a").get(1).attr("title");
// }else {
// youhui = elements1.get(2).child(0).getElementsByTag("a").get(1).attr("title");
//
// }
//
// stringBuilder.append(youhui+"+++");
// }
RadiumBean radiumBean = new RadiumBean();
radiumBean.setTargetUrl("http://www.dianping.com" + targetUrl);
radiumBean.setImg(img);
radiumBean.setName(radiumName);
radiumBean.setAddress(address0 + " " + address1);
list.add(radiumBean);
}
// 執行完畢後給handler傳送一個空訊息
Message message = new Message();
message.arg1 = Integer.parseInt(curPage);
handler.sendMessage(message);
}
};
有不明白的可以對照完整的runnable程式碼來理解。
通過上面的步驟,我們已經完成了抓取、載入、上下頁切換的效果。但但請看下面。
通過小編的切身體驗,發現jsoup爬蟲獲取資料時候的幾個需要注意的地方。
1. 個人要會js,再強調一遍,不會js,上面我寫的js的程式應該會非常的迷糊,即便會的人,因為每個人寫的也不一樣,也是不好看懂的。
2. 我們在爬取資料的時候所用的class id 等欄位一旦發生變化,那就得不到相應的標籤了,頁面就會發生奔潰,這一點也是致命的一點把。
3. 要想非常逼真的實現網頁中的效果,那你就要好好的看看網頁的原始碼了,網頁程式碼有很大的靈活性,需要你仔細分析記錄規律。
測試程式已經上傳到了github,有需要的可以下載源程式。
下載地址:點我點我點我
歡迎訪問201216323.tech來檢視我的CSDN部落格。
歡迎關注我的個人技術公眾號,快速檢視我的最新文章。
相關文章
- [網路爬蟲] Jsoup : HTML 解析工具爬蟲JSHTML
- jsoup爬蟲技術+druid連線池JS爬蟲UI
- 網路爬蟲技術及應用爬蟲
- 網路爬蟲技術是什麼,網路爬蟲的基本工作流程是什麼?爬蟲
- Java培訓教程之使用Jsoup實現簡單的爬蟲技術JavaJS爬蟲
- 網路爬蟲抓取邊界的法律與技術思考爬蟲
- 爬蟲專案(一)爬蟲+jsoup輕鬆爬知乎爬蟲JS
- 網路爬蟲技術手段有哪些?怎麼檢測是否為爬蟲IP?爬蟲
- Jsoup + HtmlUtil 實現網易新聞網頁爬蟲JSHTML網頁爬蟲
- 爬蟲與反爬蟲技術簡介爬蟲
- 爬蟲技術(二)-客戶端爬蟲爬蟲客戶端
- 網路爬蟲爬蟲
- 爬蟲技術解析:如何有效地收集網路資料爬蟲
- Java爬蟲利器HTML解析工具-JsoupJava爬蟲HTMLJS
- 網路爬蟲——爬蟲實戰(一)爬蟲
- 網路爬蟲的原理爬蟲
- 傻傻的網路爬蟲爬蟲
- 爬蟲技術抓取網站資料方法爬蟲網站
- 爬蟲技術淺析爬蟲
- 爬蟲技術實戰爬蟲
- 網路爬蟲精要爬蟲
- 網路爬蟲示例爬蟲
- Java爬蟲系列三:使用Jsoup解析HTMLJava爬蟲JSHTML
- 網路爬蟲的反扒策略爬蟲
- java爬蟲入門--用jsoup爬取汽車之家的新聞Java爬蟲JS
- 精通 Python 網路爬蟲:核心技術、框架與專案實戰Python爬蟲框架
- 爬蟲學習之基於Scrapy的網路爬蟲爬蟲
- 什麼是Python網路爬蟲?常見的網路爬蟲有哪些?Python爬蟲
- 限制IP到全流程防控,講解網路爬蟲與技術反爬的動態攻防爬蟲
- 網路爬蟲專案爬蟲
- 爬蟲學習之一個簡單的網路爬蟲爬蟲
- [Python] 網路爬蟲與資訊提取(1) 網路爬蟲之規則Python爬蟲
- 《用Python寫網路爬蟲》--編寫第一個網路爬蟲Python爬蟲
- Web 端反爬蟲技術方案Web爬蟲
- python爬蟲庫技術分享Python爬蟲
- python網路爬蟲_Python爬蟲:30個小時搞定Python網路爬蟲視訊教程Python爬蟲
- python網路爬蟲應用_python網路爬蟲應用實戰Python爬蟲
- Python爬蟲抓取技術的門道Python爬蟲