零基礎(chǔ)寫Java知乎爬蟲之獲取知乎編輯推薦內(nèi)容
知乎是一個(gè)真實(shí)的網(wǎng)絡(luò)問答社區(qū),社區(qū)氛圍友好、理性、認(rèn)真,連接各行各業(yè)的精英。他們分享著彼此的專業(yè)知識(shí)、經(jīng)驗(yàn)和見解,為中文互聯(lián)網(wǎng)源源不斷地提供高質(zhì)量的信息。
首先花個(gè)三五分鐘設(shè)計(jì)一個(gè)Logo=。=作為一個(gè)程序員我一直有一顆做美工的心!
好吧做的有點(diǎn)小湊合,就先湊合著用咯。
接下來呢,我們開始制作知乎的爬蟲。
首先,確定第一個(gè)目標(biāo):編輯推薦。
網(wǎng)頁(yè)鏈接:http://www.zhihu.com/explore/recommendations
我們對(duì)上次的代碼稍作修改,先實(shí)現(xiàn)能夠獲取該頁(yè)面內(nèi)容:
import java.io.*;
import java.net.*;
import java.util.regex.*;
public class Main {
static String SendGet(String url) {
// 定義一個(gè)字符串用來存儲(chǔ)網(wǎng)頁(yè)內(nèi)容
String result = "";
// 定義一個(gè)緩沖字符輸入流
BufferedReader in = null;
try {
// 將string轉(zhuǎn)成url對(duì)象
URL realUrl = new URL(url);
// 初始化一個(gè)鏈接到那個(gè)url的連接
URLConnection connection = realUrl.openConnection();
// 開始實(shí)際的連接
connection.connect();
// 初始化 BufferedReader輸入流來讀取URL的響應(yīng)
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
// 用來臨時(shí)存儲(chǔ)抓取到的每一行的數(shù)據(jù)
String line;
while ((line = in.readLine()) != null) {
// 遍歷抓取到的每一行并將其存儲(chǔ)到result里面
result += line;
}
} catch (Exception e) {
System.out.println("發(fā)送GET請(qǐng)求出現(xiàn)異常!" + e);
e.printStackTrace();
}
// 使用finally來關(guān)閉輸入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
static String RegexString(String targetStr, String patternStr) {
// 定義一個(gè)樣式模板,此中使用正則表達(dá)式,括號(hào)中是要抓的內(nèi)容
// 相當(dāng)于埋好了陷阱匹配的地方就會(huì)掉下去
Pattern pattern = Pattern.compile(patternStr);
// 定義一個(gè)matcher用來做匹配
Matcher matcher = pattern.matcher(targetStr);
// 如果找到了
if (matcher.find()) {
// 打印出結(jié)果
return matcher.group(1);
}
return "Nothing";
}
public static void main(String[] args) {
// 定義即將訪問的鏈接
String url = " // 訪問鏈接并獲取頁(yè)面內(nèi)容
String result = SendGet(url);
// 使用正則匹配圖片的src內(nèi)容
//String imgSrc = RegexString(result, "src=\"(.+?)\"");
// 打印結(jié)果
System.out.println(result);
}
}
運(yùn)行一下木有問題,接下來就是一個(gè)正則匹配的問題了。
首先我們先來獲取該頁(yè)面的所有的問題。
右擊標(biāo)題,審查元素:
啊哈,可以看到標(biāo)題其實(shí)是一個(gè)a標(biāo)簽,也就是一個(gè)超鏈接,而其中能夠和其他超鏈接區(qū)分開的,應(yīng)該就是那個(gè)class了,也就是類選擇器。
于是我們的正則語句就出來了:question_link.+?href=\"(.+?)\"
調(diào)用RegexString函數(shù),并給它傳參:
public static void main(String[] args) {
// 定義即將訪問的鏈接
String url = " // 訪問鏈接并獲取頁(yè)面內(nèi)容
String result = SendGet(url);
// 使用正則匹配圖片的src內(nèi)容
String imgSrc = RegexString(result, "question_link.+?>(.+?)<");
// 打印結(jié)果
System.out.println(imgSrc);
}
啊哈,可以看到我們成功抓到了一個(gè)標(biāo)題(注意,只是一個(gè)):
等一下啊這一大堆的亂七八糟的是什么玩意?!
別緊張=。=它只是字符亂碼而已。
編碼問題可以參見:HTML字符集
一般來說,對(duì)中文支持較好的主流編碼是UTF-8,GB2312和GBK編碼。
網(wǎng)頁(yè)可以通過meta標(biāo)簽的charset來設(shè)置網(wǎng)頁(yè)編碼,譬如:
<meta charset="utf-8" />
我們右擊,查看頁(yè)面源代碼:
可以看到,知乎采用的是UTF-8編碼。
在這里和大家解釋一下查看頁(yè)面源代碼和審查元素的區(qū)別。
查看頁(yè)面源代碼是顯示整個(gè)頁(yè)面的所有代碼,沒有按照HTML的標(biāo)簽進(jìn)行排版,相當(dāng)于是直接查看源碼,這種方式對(duì)于查看整個(gè)網(wǎng)頁(yè)的信息,比如meta比較有用。
審查元素,或者有的瀏覽器叫查看元素,是針對(duì)你右擊的元素進(jìn)行查看,比如一個(gè)div或者img,比較適用于單獨(dú)查看某個(gè)對(duì)象的屬性和標(biāo)簽。
好的,我們現(xiàn)在知道了問題出在了編碼上,接下來就是對(duì)抓取到的內(nèi)容進(jìn)行編碼轉(zhuǎn)換了。
在java中實(shí)現(xiàn)很簡(jiǎn)單,只需要在InputStreamReader里面指定編碼方式就行:
// 初始化 BufferedReader輸入流來讀取URL的響應(yīng)
in = new BufferedReader(new InputStreamReader(
connection.getInputStream(),"UTF-8"));
此時(shí)再運(yùn)行程序,便會(huì)發(fā)現(xiàn)可以正常顯示標(biāo)題了:
好的!非常好!
但是現(xiàn)在才只有一個(gè)標(biāo)題,我們需要的是所有的標(biāo)題。
我們將正則稍加修改,把搜索到的結(jié)果存儲(chǔ)到一個(gè)ArrayList里面:
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.regex.*;
public class Main {
static String SendGet(String url) {
// 定義一個(gè)字符串用來存儲(chǔ)網(wǎng)頁(yè)內(nèi)容
String result = "";
// 定義一個(gè)緩沖字符輸入流
BufferedReader in = null;
try {
// 將string轉(zhuǎn)成url對(duì)象
URL realUrl = new URL(url);
// 初始化一個(gè)鏈接到那個(gè)url的連接
URLConnection connection = realUrl.openConnection();
// 開始實(shí)際的連接
connection.connect();
// 初始化 BufferedReader輸入流來讀取URL的響應(yīng)
in = new BufferedReader(new InputStreamReader(
connection.getInputStream(), "UTF-8"));
// 用來臨時(shí)存儲(chǔ)抓取到的每一行的數(shù)據(jù)
String line;
while ((line = in.readLine()) != null) {
// 遍歷抓取到的每一行并將其存儲(chǔ)到result里面
result += line;
}
} catch (Exception e) {
System.out.println("發(fā)送GET請(qǐng)求出現(xiàn)異常!" + e);
e.printStackTrace();
}
// 使用finally來關(guān)閉輸入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
static ArrayList<String> RegexString(String targetStr, String patternStr) {
// 預(yù)定義一個(gè)ArrayList來存儲(chǔ)結(jié)果
ArrayList<String> results = new ArrayList<String>();
// 定義一個(gè)樣式模板,此中使用正則表達(dá)式,括號(hào)中是要抓的內(nèi)容
Pattern pattern = Pattern.compile(patternStr);
// 定義一個(gè)matcher用來做匹配
Matcher matcher = pattern.matcher(targetStr);
// 如果找到了
boolean isFind = matcher.find();
// 使用循環(huán)將句子里所有的kelvin找出并替換再將內(nèi)容加到sb里
while (isFind) {
//添加成功匹配的結(jié)果
results.add(matcher.group(1));
// 繼續(xù)查找下一個(gè)匹配對(duì)象
isFind = matcher.find();
}
return results;
}
public static void main(String[] args) {
// 定義即將訪問的鏈接
String url = " // 訪問鏈接并獲取頁(yè)面內(nèi)容
String result = SendGet(url);
// 使用正則匹配圖片的src內(nèi)容
ArrayList<String> imgSrc = RegexString(result, "question_link.+?>(.+?)<");
// 打印結(jié)果
System.out.println(imgSrc);
}
}
這樣就能匹配到所有的結(jié)果了(因?yàn)橹苯哟蛴×薃rrayList所以會(huì)有一些中括號(hào)和逗號(hào)):
OK,這樣就算是完成了知乎爬蟲的第一步。
但是我們可以看出來,用這樣的方式是沒有辦法抓到所有的問題和回答的。
我們需要設(shè)計(jì)一個(gè)Zhihu封裝類,來存儲(chǔ)所有抓取到的對(duì)象。
Zhihu.java源碼:
import java.util.ArrayList;
public class Zhihu {
public String question;// 問題
public String zhihuUrl;// 網(wǎng)頁(yè)鏈接
public ArrayList<String> answers;// 存儲(chǔ)所有回答的數(shù)組
// 構(gòu)造方法初始化數(shù)據(jù)
public Zhihu() {
question = "";
zhihuUrl = "";
answers = new ArrayList<String>();
}
@Override
public String toString() {
return "問題:" + question + "\n鏈接:" + zhihuUrl + "\n回答:" + answers + "\n";
}
}
再新建一個(gè)Spider類來存放一些爬蟲常用的函數(shù)。
Spider.java源碼:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Spider {
static String SendGet(String url) {
// 定義一個(gè)字符串用來存儲(chǔ)網(wǎng)頁(yè)內(nèi)容
String result = "";
// 定義一個(gè)緩沖字符輸入流
BufferedReader in = null;
try {
// 將string轉(zhuǎn)成url對(duì)象
URL realUrl = new URL(url);
// 初始化一個(gè)鏈接到那個(gè)url的連接
URLConnection connection = realUrl.openConnection();
// 開始實(shí)際的連接
connection.connect();
// 初始化 BufferedReader輸入流來讀取URL的響應(yīng)
in = new BufferedReader(new InputStreamReader(
connection.getInputStream(), "UTF-8"));
// 用來臨時(shí)存儲(chǔ)抓取到的每一行的數(shù)據(jù)
String line;
while ((line = in.readLine()) != null) {
// 遍歷抓取到的每一行并將其存儲(chǔ)到result里面
result += line;
}
} catch (Exception e) {
System.out.println("發(fā)送GET請(qǐng)求出現(xiàn)異常!" + e);
e.printStackTrace();
}
// 使用finally來關(guān)閉輸入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
static ArrayList<Zhihu> GetZhihu(String content) {
// 預(yù)定義一個(gè)ArrayList來存儲(chǔ)結(jié)果
ArrayList<Zhihu> results = new ArrayList<Zhihu>();
// 用來匹配標(biāo)題
Pattern questionPattern = Pattern.compile("question_link.+?>(.+?)<");
Matcher questionMatcher = questionPattern.matcher(content);
// 用來匹配url,也就是問題的鏈接
Pattern urlPattern = Pattern.compile("question_link.+?href=\"(.+?)\"");
Matcher urlMatcher = urlPattern.matcher(content);
// 問題和鏈接要均能匹配到
boolean isFind = questionMatcher.find() && urlMatcher.find();
while (isFind) {
// 定義一個(gè)知乎對(duì)象來存儲(chǔ)抓取到的信息
Zhihu zhuhuTemp = new Zhihu();
zhuhuTemp.question = questionMatcher.group(1);
zhuhuTemp.zhihuUrl = " // 添加成功匹配的結(jié)果
results.add(zhuhuTemp);
// 繼續(xù)查找下一個(gè)匹配對(duì)象
isFind = questionMatcher.find() && urlMatcher.find();
}
return results;
}
}
最后一個(gè)main方法負(fù)責(zé)調(diào)用。
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// 定義即將訪問的鏈接
String url = " // 訪問鏈接并獲取頁(yè)面內(nèi)容
String content = Spider.SendGet(url);
// 獲取該頁(yè)面的所有的知乎對(duì)象
ArrayList<Zhihu> myZhihu = Spider.GetZhihu(content);
// 打印結(jié)果
System.out.println(myZhihu);
}
}
Ok這樣就算搞定了。運(yùn)行一下看看結(jié)果:
好的效果不錯(cuò)。
接下來就是訪問鏈接然后獲取到所有的答案了。
下一回我們?cè)俳榻B。
好了,以上就是簡(jiǎn)單的介紹了如何使用java來抓取知乎的編輯推薦的內(nèi)容的全部過程了,非常詳盡,也很簡(jiǎn)單易懂,對(duì)吧,有需要的小伙伴可以參考下,自由擴(kuò)展也沒問題哈
相關(guān)文章
SpringBoot中使用Filter和Interceptor的示例代碼
這篇文章主要介紹了SpringBoot中使用Filter和Interceptor的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06Spring Web MVC和Hibernate的集成配置詳解
這篇文章主要介紹了Spring Web MVC和Hibernate的集成配置詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下2017-12-12java.lang.NoClassDefFoundError錯(cuò)誤解決辦法
這篇文章主要介紹了java.lang.NoClassDefFoundError錯(cuò)誤解決辦法的相關(guān)資料,需要的朋友可以參考下2017-06-06SpringBoot概述及在idea中創(chuàng)建方式
SpringBoot提供了一種快速使用Spring的方式,基于約定大于配置的思想,可以讓開發(fā)人員不必在配置與邏輯業(yè)務(wù)之間進(jìn)行思維的切換,這篇文章主要介紹了SpringBoot概述及在idea中創(chuàng)建方式,需要的朋友可以參考下2022-09-09SpringCloud Hystrix-Dashboard儀表盤的實(shí)現(xiàn)
這篇文章主要介紹了SpringCloud Hystrix-Dashboard儀表盤的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Java泛型機(jī)制與反射原理相關(guān)知識(shí)總結(jié)
今天帶大家學(xué)習(xí)的是關(guān)于Java進(jìn)階的相關(guān)知識(shí),文章圍繞著Java泛型機(jī)制與反射原理展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06深入學(xué)習(xí)springboot線程池的使用和擴(kuò)展
這篇文章主要介紹了深入學(xué)習(xí)springboot線程池的使用和擴(kuò)展,springboot框架提供了@Async注解,幫助我們更方便的將業(yè)務(wù)邏輯提交到線程池中異步執(zhí)行,需要的朋友可以參考下2019-06-06Java中Future、FutureTask原理以及與線程池的搭配使用
這篇文章主要為大家詳細(xì)介紹了Java中Future、FutureTask原理以及與線程池的搭配使用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09Spring中11個(gè)最常用的擴(kuò)展點(diǎn)總結(jié),你知道幾個(gè)
我們知道IOC(控制反轉(zhuǎn))和AOP(面向切面編程)是spring的基石,除此之外spring的擴(kuò)展能力非常強(qiáng),下面這篇文章主要給大家介紹了關(guān)于Spring中11個(gè)最常用的擴(kuò)展點(diǎn)的相關(guān)資料,需要的朋友可以參考下2022-12-12