爬知乎

零基础写Java知乎爬虫之抓取知乎答案

投稿:hebedich 字体:[增加 减小] 类型:转载 时间:2014-11-07 我要评论

上篇文章我们已经能把知乎的问题抓出来了,但是答案还木有抓出来。这一回合,我们就连着把答案也一起从网站中抠出来=。=

前期我们抓取标题是在该链接下:

http://www.zhihu.com/explore/recommendations

但是显然这个页面是无法获取答案的。

一个完整问题的页面应该是这样的链接:

http://www.zhihu.com/question/22355264

仔细一看,啊哈我们的封装类还需要进一步包装下,至少需要个questionDescription来存储问题描述:

import java.util.ArrayList;
public class Zhihu {
 public String question;// 问题
 public String questionDescription;// 问题描述
 public String zhihuUrl;// 网页链接
 public ArrayList<String> answers;// 存储所有回答的数组
 // 构造方法初始化数据
 public Zhihu() {
  question = "";
  questionDescription = "";
  zhihuUrl = "";
  answers = new ArrayList<String>();
 }
 @Override
 public String toString() {
  return "问题:" + question + "\n" + "描述:" + questionDescription + "\n"
    + "链接:" + zhihuUrl + "\n回答:" + answers + "\n";
 }
}

我们给知乎的构造函数加上一个参数,用来设定url值,因为url确定了,这个问题的描述和答案也就都能抓到了。

我们将Spider的获取知乎对象的方法改一下,只获取url即可:

 static ArrayList<Zhihu> GetZhihu(String content) {
  // 预定义一个ArrayList来存储结果
  ArrayList<Zhihu> results = new ArrayList<Zhihu>();
  // 用来匹配url,也就是问题的链接
  Pattern urlPattern = Pattern.compile("<h2>.+?question_link.+?href=\"(.+?)\".+?</h2>");
  Matcher urlMatcher = urlPattern.matcher(content);
  // 是否存在匹配成功的对象
  boolean isFind = urlMatcher.find();
  while (isFind) {
   // 定义一个知乎对象来存储抓取到的信息
   Zhihu zhihuTemp = new Zhihu(urlMatcher.group(1));
   // 添加成功匹配的结果
   results.add(zhihuTemp);
   // 继续查找下一个匹配对象
   isFind = urlMatcher.find();
  }
  return results;
 }

接下来,就是在Zhihu的构造方法里面,通过url获取所有的详细数据。

我们先要对url进行一个处理,因为有的针对回答的,它的url是:

http://www.zhihu.com/question/22355264/answer/21102139

有的针对问题的,它的url是:

http://www.zhihu.com/question/22355264

那么我们显然需要的是第二种,所以需要用正则把第一种链接裁切成第二种,这个在Zhihu中写个函数即可。

// 处理url
 boolean getRealUrl(String url) {
  // 将<a target=_blank href="http://www.zhihu.com/question/22355264/answer/21102139">http://www.zhihu.com/question/22355264/answer/21102139</a>
  // 转化成<a target=_blank href="http://www.zhihu.com/question/22355264">http://www.zhihu.com/question/22355264</a>
  // 否则不变
  Pattern pattern = Pattern.compile("question/(.*?)/");
  Matcher matcher = pattern.matcher(url);
  if (matcher.find()) {
   zhihuUrl = "<a target=_blank href="http://www.zhihu.com/question/">http://www.zhihu.com/question/</a>" + matcher.group(1);
  } else {
   return false;
  }
  return true;
 }

接下来就是各个部分的获取工作了。

先看下标题:

正则把握住那个class即可,正则语句可以写成:zm-editable-content\">(.+?)<

运行下看看结果:

哎哟不错哦。

接下来抓取问题描述:

啊哈一样的原理,抓住class,因为它应该是这个的唯一标识。

验证方法:右击查看页面源代码,ctrl+F看看页面中有没有其他的这个字符串。

后来经过验证,还真出了问题:

标题和描述内容前面的class是一样的。

那只能通过修改正则的方式来重新抓取:

// 匹配标题
   pattern = Pattern.compile("zh-question-title.+?<h2.+?>(.+?)</h2>");
   matcher = pattern.matcher(content);
   if (matcher.find()) {
    question = matcher.group(1);
   }
   // 匹配描述
   pattern = Pattern
     .compile("zh-question-detail.+?<div.+?>(.*?)</div>");
   matcher = pattern.matcher(content);
   if (matcher.find()) {
    questionDescription = matcher.group(1);
   }

最后就是循环抓取答案了:

初步暂定正则语句:/answer/content.+?<div.+?>(.*?)</div>

改下代码之后我们会发现软件运行的速度明显变慢了,因为他需要访问每个网页并且把上面的内容抓下来。

比如说编辑推荐有20个问题,那么就需要访问网页20次,速度也就慢下来了。

试验一下,看上去效果不错:


OK,那就先这样好了~下次继续进行一些细节的调整,比如多线程,IO流写入本地等等。

附项目源码:

Zhihu.java

import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Zhihu {
 public String question;// 问题
 public String questionDescription;// 问题描述
 public String zhihuUrl;// 网页链接
 public ArrayList<String> answers;// 存储所有回答的数组
 // 构造方法初始化数据
 public Zhihu(String url) {
  // 初始化属性
  question = "";
  questionDescription = "";
  zhihuUrl = "";
  answers = new ArrayList<String>();
  // 判断url是否合法
  if (getRealUrl(url)) {
   System.out.println("正在抓取" + zhihuUrl);
   // 根据url获取该问答的细节
   String content = Spider.SendGet(zhihuUrl);
   Pattern pattern;
   Matcher matcher;
   // 匹配标题
   pattern = Pattern.compile("zh-question-title.+?<h2.+?>(.+?)</h2>");
   matcher = pattern.matcher(content);
   if (matcher.find()) {
    question = matcher.group(1);
   }
   // 匹配描述
   pattern = Pattern
     .compile("zh-question-detail.+?<div.+?>(.*?)</div>");
   matcher = pattern.matcher(content);
   if (matcher.find()) {
    questionDescription = matcher.group(1);
   }
   // 匹配答案
   pattern = Pattern.compile("/answer/content.+?<div.+?>(.*?)</div>");
   matcher = pattern.matcher(content);
   boolean isFind = matcher.find();
   while (isFind) {
    answers.add(matcher.group(1));
    isFind = matcher.find();
   }
  }
 }
 // 根据自己的url抓取自己的问题和描述和答案
 public boolean getAll() {
  return true;
 }
 // 处理url
 boolean getRealUrl(String url) {
  // 将<a target=_blank href="http://www.zhihu.com/question/22355264/answer/21102139">http://www.zhihu.com/question/22355264/answer/21102139</a>
  // 转化成<a target=_blank href="http://www.zhihu.com/question/22355264">http://www.zhihu.com/question/22355264</a>
  // 否则不变
  Pattern pattern = Pattern.compile("question/(.*?)/");
  Matcher matcher = pattern.matcher(url);
  if (matcher.find()) {
   zhihuUrl = "<a target=_blank href="http://www.zhihu.com/question/">http://www.zhihu.com/question/</a>" + matcher.group(1);
  } else {
   return false;
  }
  return true;
 }
 @Override
 public String toString() {
  return "问题:" + question + "\n" + "描述:" + questionDescription + "\n"
    + "链接:" + zhihuUrl + "\n回答:" + answers.size() + "\n";
 }
}

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) {
  // 定义一个字符串用来存储网页内容
  String result = "";
  // 定义一个缓冲字符输入流
  BufferedReader in = null;
  try {
   // 将string转成url对象
   URL realUrl = new URL(url);
   // 初始化一个链接到那个url的连接
   URLConnection connection = realUrl.openConnection();
   // 开始实际的连接
   connection.connect();
   // 初始化 BufferedReader输入流来读取URL的响应
   in = new BufferedReader(new InputStreamReader(
     connection.getInputStream(), "UTF-8"));
   // 用来临时存储抓取到的每一行的数据
   String line;
   while ((line = in.readLine()) != null) {
    // 遍历抓取到的每一行并将其存储到result里面
    result += line;
   }
  } catch (Exception e) {
   System.out.println("发送GET请求出现异常!" + e);
   e.printStackTrace();
  }
  // 使用finally来关闭输入流
  finally {
   try {
    if (in != null) {
     in.close();
    }
   } catch (Exception e2) {
    e2.printStackTrace();
   }
  }
  return result;
 }
 // 获取所有的编辑推荐的知乎内容
 static ArrayList<Zhihu> GetRecommendations(String content) {
  // 预定义一个ArrayList来存储结果
  ArrayList<Zhihu> results = new ArrayList<Zhihu>();
  // 用来匹配url,也就是问题的链接
  Pattern pattern = Pattern
    .compile("<h2>.+?question_link.+?href=\"(.+?)\".+?</h2>");
  Matcher matcher = pattern.matcher(content);
  // 是否存在匹配成功的对象
  Boolean isFind = matcher.find();
  while (isFind) {
   // 定义一个知乎对象来存储抓取到的信息
   Zhihu zhihuTemp = new Zhihu(matcher.group(1));
   // 添加成功匹配的结果
   results.add(zhihuTemp);
   // 继续查找下一个匹配对象
   isFind = matcher.find();
  }
  return results;
 }
}

Main.java

import java.util.ArrayList;
public class Main {
 public static void main(String[] args) {
  // 定义即将访问的链接
  String url = "<a target=_blank href="http://www.zhihu.com/explore/recommendations">http://www.zhihu.com/explore/recommendations</a>";
  // 访问链接并获取页面内容
  String content = Spider.SendGet(url);
  // 获取编辑推荐
  ArrayList<Zhihu> myZhihu = Spider.GetRecommendations(content);
  // 打印结果
  System.out.println(myZhihu);
 }
}

以上就是抓取知乎答案的全部记录,非常的详尽,有需要的朋友可以参考下

### 使用虫实现乎网站模拟登录的方法 为了完成乎网站的模拟登录操作,可以采用 Python 的 `requests` 或者 `Selenium` 库来处理请求和交互行为。以下是具体方法和技术细节: #### 1. 登录接口分析 乎提供了两种主要的登录方式:邮箱登录和手机号码登录。这两种登录方式对应的 URL 地址分别为: - 邮箱登录地址:`https://www.zhihu.com/login/email`[^4] - 手机号登录地址:`http://www.zhihu.com/login/phone_num` 在实际开发过程中,可以通过浏览器开发者工具捕获这些 POST 请求的具体参数。 #### 2. 处理反虫机制 乎具有较为完善的反虫机制,主要包括验证码验证以及对异常访问频率的监控。因此,在编写虫时需要注意以下几点: - **验证码识别**:通常情况下,乎会返回一张图片形式的验证码供用户输入。可以借助第三方 OCR 工具(如 Tesseract)解析验证码内容[^1]。 - **User-Agent 设置**:为了避免被服务器标记为机器人,需设置合理的 User-Agent 字符串模仿真实用户的浏览器环境[^2]。 #### 3. 实现代码示例 以下是一个基于 `requests` 和 `selenium` 的简单模拟登录案例: ```python import requests from selenium import webdriver def login_with_requests(email, password): url = "https://www.zhihu.com/login/email" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", "Referer": "https://www.zhihu.com/" } data = {"email": email, "password": password} session = requests.Session() response = session.post(url=url, data=data, headers=headers) if response.status_code == 200 and "_xsrf" in response.cookies: print("Login successful!") else: print("Failed to log in.") def login_with_selenium(username, pwd): driver_path = "/path/to/chromedriver" # 替换为你自己的 chromedriver 路径 options = webdriver.ChromeOptions() options.add_argument("--headless") browser = webdriver.Chrome(executable_path=driver_path, options=options) try: browser.get('https://www.zhihu.com/signin') # 切换到账号密码模式 switch_button = browser.find_element_by_xpath("//div[@class='SignFlow-tab']") switch_button.click() input_email = browser.find_element_by_name("username") input_pwd = browser.find_element_by_name("password") input_email.send_keys(username) input_pwd.send_keys(pwd) submit_btn = browser.find_element_by_xpath("//button[@type='submit']") submit_btn.click() cookies = {i["name"]: i["value"] for i in browser.get_cookies()} return cookies finally: browser.quit() if __name__ == "__main__": username = "your_username_here" password = "your_password_here" # Requests 方式尝试登录 login_with_requests(username, password) # Selenium 方式尝试登录 cookies = login_with_selenium(username, password) ``` 上述代码分别展示了利用 `requests` 发送 HTTP 请求的方式以及通过 `selenium` 自动化控制网页表单填写的过程。 #### 4. 数据抓取注意事项 成功登录之后即可进一步执行数据采集任务。然而需要注意的是,频繁地向目标站点发起请求可能会触发其防护措施甚至封禁 IP 地址。建议合理安排时间间隔,并遵守《robots.txt》文件中的规定[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值