一、寫在前面
(本專欄分爲“java版微博爬蟲”和“python版網絡爬蟲”兩個項目,系列裏所有文章將基於這兩個項目講解,項目完整源碼已經整理到我的Github,有興趣的可以去看下,鏈接地址在文末。)
網絡爬蟲根據需求的不同也分不同種類:
1.一種是爬取網頁鏈接,通過url鏈接得到這個html頁面中指定的鏈接,把這些鏈接存儲起來,再依次以這些鏈接爲源,再次爬取連接指向html頁面中的鏈接……如此層層遞歸下去,常用的方法是廣度優先或者深度優先,根據爬取層次需求不同而選擇不同的方法達到最優效果,爬蟲的效率優化是一個關鍵。
搜索引擎的第一個步驟就是通過爬蟲得到需要索引的鏈接或數據,存放於數據庫,然後對這些數據建立索引,然後定義查詢語句,解析查詢語句並利用檢索器對數據庫裏的數據進行檢索。
2.一種是爬取數據信息,如文本信息、圖片信息等,有時需要做數據分析,通過某種手段來獲取數據樣本以供後續分析,常用的方法是爬蟲獲取指定數據樣本或利用現有的公共數據庫。本文的微博爬蟲屬於第二種類,根據自定義搜索關鍵字爬取微博信息數據。
對於網絡爬蟲原理,其實並不複雜。基本思路是:由關鍵字指定的url把所有相關的html頁面全抓下來(html即爲字符串),然後解析html文本(通常是正則表達式或者現成工具包如jsoup),提取微博文本信息,然後把文本信息存儲起來。
重點在於對html頁面源碼結構的分析,不同的html需要不同的解析方法;還有就是長時間爬取可能對IP有影響,有時需要獲取代理IP,甚至需要僞裝瀏覽器爬取。
對於微博,通常情況下是必須登錄才能看到微博信息數據(比如騰訊微博),但是有的微博有搜索機制,在非登錄的情況下可以直接通過搜索話題來查找相關信息(如新浪微博、網易微博)。考慮到某些反爬蟲機制,如果一個賬號總是爬取信息可能會有些影響(比如被封號),所以本文采用的爬蟲都是非登錄、直接進入微博搜索頁面爬取。這裏關鍵是初始url地址。
二、網頁分析
舉個例子,對於有搜索機制的微博,如新浪微博和網易微博:(這裏尤其要注意地址及參數!)
新浪微博搜索話題地址:http://s.weibo.com/weibo/蘋果手機&nodup=1&page=50
網易微博搜索話題地址:http://t.163.com/tag/蘋果手機
分別來看下網頁截圖和對應網頁源碼:
(1) 新浪微博:
(2) 網易微博:
我們需要做的就是把微博的文本提取出來,這裏有些特徵信息:根據關鍵字搜到的微博,其關鍵字會被標紅,在html源碼裏有體現,分別查看兩個網頁的源代碼。
可以看到,新浪微博的源碼全部爲html標籤,爲了反爬蟲故意將源碼混亂,並且漢字也做了處理,顯示漢字的utf-8編碼而不是直接顯示漢字,不太好找,這裏就需要查找紅色字體color:red的部分,其中<span style=\”color:red;\”>\u82f9\u679c\u624b\u673a<\span>中間夾着的哪些utf8編碼其實就是關鍵字“蘋果手機”。後面的那些utf8編碼就是本條微博的文本內容。
而網易微博在這方面就要看起來容易許多,至少html裏直接顯示的是漢字,比較好找,而且微博文本部分其實是以json格式體現的,直接解析json就可以提取文本數據了,當然也可以直接用正則。
三、爬取微博
這裏先寫個簡單的爬蟲,原理都差不多,就拿網易微博爲例,先說下爬蟲程序需要用到的工具包:
httpclient-4.3.1.jar -------建立HTTP鏈接,用於從url獲取html
httpcore-4.3.jar
httpmime-4.3.1.jar
httpclient-cache-4.3.1.jar
fluent-hc-4.3.1.jar
fastjson-1.1.41.jar ------解析json的工具包
jsoup-1.7.3.jar -------解析xml,html的工具包
dom4j-1.6.1.jar -------讀寫xml的工具包
commons-lang-2.1.jar
commons-logging-1.2.jar
commons-codec-1.8.jar
總體思路:
1. getHTML()方法:從url得到html字符串。
這裏有兩個關鍵點:
(1) 設置用戶cookie策略,屏蔽掉cookie rejected的報錯,當然可以不設置,直接用默認的client,即是CloseableHttpClient client = HttpClients.createDefault();創建的客戶端,但是會報錯;設置cookie的代碼如下:
CookieSpecProvider cookieSpecProvider = new CookieSpecProvider(){
public CookieSpec create(HttpContext context){
return new BrowserCompatSpec(){
@Override
public void validate(Cookie cookie, CookieOrigin origin) throws MalformedCookieException {
//Oh, I am easy;
}
};
}
};
Registry<CookieSpecProvider> r = RegistryBuilder
.<CookieSpecProvider> create()
.register(CookieSpecs.BEST_MATCH, new BestMatchSpecFactory())
.register(CookieSpecs.BROWSER_COMPATIBILITY, new BrowserCompatSpecFactory())
.register("easy", cookieSpecProvider)
.build();
(2) 設置socket超時socketTimeout和連接超時connectTimeout,這很關鍵,如果不設置的話,當網絡不好的情況下,某次請求沒有及時得到響應,程序可能會卡死。但是設置連接超時,超時之後再自動重連就可以避免這個問題。代碼如下:
RequestConfig requestConfig = RequestConfig.custom()
.setCookieSpec("easy")
.setSocketTimeout(5000) //設置socket超時時間
.setConnectTimeout(5000) //設置connect超時時間
.build();
2.isExitHTML()方法:
判斷html是否合法(有效html,有微博內容的),有時候會出現頁面不存在的情況,是因爲該關鍵字沒有微博信息,這是頁面有提示:“沒有找到相關的微博呢,換個關鍵詞試試吧!”如下圖;
3.writeWeibo2txt()方法:
正則解析(這裏主要解析微博文本內容content、用戶id、發文時間prettyTime),得到微博文本數據:控制檯輸出、寫到txt文件;
(這裏沒有用jsoup去解析html,直接用的正則,有時間後面再寫jsoup解析html的,比正則方便,當然對正則表達式這個工具掌握很熟練的朋友可以忽略……)
我的博客裏另外有一篇正則教程完全總結,可以去看下:正則表達式總結
本來網易微博的源代碼裏嵌入了json元素,前面的html標籤的都不是微博文本數據,在這個標籤後面 <scriptid="data_searchTweet" type="application/json">纔是json格式包括的微博文本,本來打算先把json串用正則匹配出來,後來發現,直接匹配關鍵字豈不更好?但這裏要注意的是匹配的時候可能會有很多換行符之類的東西,我這裏只匹配了三個field,正則表達式"id":\s"\d{19}",(\n*?)|(\s*?)"content":\s".*?",(\n*?)|(\s*?)"prettyTime":\s".*?"
順便推薦一個檢測正則的工具:http://tool.oschina.net/regex?optionGlobl=global
把源碼貼上來吧:
/**
* @note 1.根據搜索關鍵詞從指定url得到相應的html頁面,並驗證其合法性;
* 2.得到微博樣本:寫到txt文件裏
*
* @author DianaCody
* @since 2014-09-26 15:08
*
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.cookie.Cookie;
import org.apache.http.cookie.CookieOrigin;
import org.apache.http.cookie.CookieSpec;
import org.apache.http.cookie.CookieSpecProvider;
import org.apache.http.cookie.MalformedCookieException;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.cookie.BestMatchSpecFactory;
import org.apache.http.impl.cookie.BrowserCompatSpec;
import org.apache.http.impl.cookie.BrowserCompatSpecFactory;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
public class Weibo163 {
private String getHTML(String url) throws URISyntaxException, ClientProtocolException, IOException {
//採用用戶自定義cookie策略,不顯示cookie rejected的報錯
CookieSpecProvider cookieSpecProvider = new CookieSpecProvider(){
public CookieSpec create(HttpContext context){
return new BrowserCompatSpec(){
@Override
public void validate(Cookie cookie, CookieOrigin origin) throws MalformedCookieException {
}
};
}
};
Registry<CookieSpecProvider> r = RegistryBuilder
.<CookieSpecProvider> create()
.register(CookieSpecs.BEST_MATCH, new BestMatchSpecFactory())
.register(CookieSpecs.BROWSER_COMPATIBILITY, new BrowserCompatSpecFactory())
.register("cookie", cookieSpecProvider)
.build();
RequestConfig requestConfig = RequestConfig.custom()
.setCookieSpec("cookie")
.setSocketTimeout(5000) //設置socket超時時間
.setConnectTimeout(5000) //設置connect超時時間
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultCookieSpecRegistry(r)
.setDefaultRequestConfig(requestConfig)
.build();
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(requestConfig);
String html = "html獲取失敗"; //用於驗證是否取到正常的html
try{
CloseableHttpResponse response = httpClient.execute(httpGet);
html = EntityUtils.toString(response.getEntity());
//System.out.println(html); //打印返回的html
} catch (IOException e) {
e.printStackTrace();
}
return html;
}
private boolean isExistHTML(String html) throws InterruptedException {
boolean isExist = false;
Pattern pNoResult = Pattern.compile("\\\\u6ca1\\\\u6709\\\\u627e\\\\u5230\\\\u76f8"
+ "\\\\u5173\\\\u7684\\\\u5fae\\\\u535a\\\\u5462\\\\uff0c\\\\u6362\\\\u4e2a"
+ "\\\\u5173\\\\u952e\\\\u8bcd\\\\u8bd5\\\\u5427\\\\uff01"); //沒有找到相關的微博呢,換個關鍵詞試試吧!(html頁面上的信息)
Matcher mNoResult = pNoResult.matcher(html);
if(!mNoResult.find()) {
isExist = true;
}
return isExist;
}
private void writeWeibo2txt(String html, String savePath) throws IOException {
File htmltxt = new File(savePath); //新建一個txt文件用於存放爬取的結果信息
FileWriter fw = new FileWriter(htmltxt);
BufferedWriter bw = new BufferedWriter(fw);
//regex-----"id":\s"\d{19}",(\n*?)|(\s*?)"content":\s".*?",(\n*?)|(\s*?)"prettyTime":\s".*?"
Pattern p = Pattern.compile("\"id\":\\s\"\\d{19}\",(\\n*?)|(\\s*?)\"content\":\\s\".*?\",(\\n*?)|(\\s*?)\"prettyTime\":\\s\".*?\"");
Matcher m = p.matcher(html);
while(m.find()) {
System.out.println(m.group());
bw.write(m.group());
}
bw.close();
}
public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException {
Weibo163 crawler = new Weibo163();
String searchword = "iPad"; //搜索關鍵字爲"iPad"的微博html頁面
String html = crawler.getHTML("http://t.163.com/tag/"+searchword);
String savePath = "e:/weibo/html.txt"; //輸出到txt文件的存放路徑
if(html != "html獲取失敗") {
if(crawler.isExistHTML(html)) {
System.out.println(html);
crawler.writeWeibo2txt(html, savePath);
}
}
//Pattern p = Pattern.compile("<script id=\"data_searchTweet\" type=\"application/json\">.+?<\script>"); //<script id="data_searchTweet" type="application/json">.*?<\script>
//Matcher m = p.matcher(html);
//html = crawler.getHTML("http://s.weibo.com/weibo/"+searchword+"&nodup=1&page="+1);
//System.out.println(html);
}
}
程序運行截圖:
該java爬蟲項目源碼github地址:https://github.com/DianaCody/Spider_SinaTweetCrawler_java。
原創文章,轉載請註明出處:http://blog.csdn.net/dianacody/article/details/39584977
另外一個系列對爬蟲的整理筆記:http://www.crifan.com/files/doc/docbook/web_scrape_emulate_login/release/html/web_scrape_emulate_login.html