上一篇写了一个简单的新浪新闻爬虫作为上手主要是用jsoup包来对url页面进行抓取到本地,并在本地进行数据的解析提取。这里就不重复叙述jsoup的用法了,百度一下基本一大片。看了网上大神们都有对知乎进行爬取,今天我也拿它来试试手。写这篇文章的目的主要是想将自己在爬取中遇到的一些坑,以及怎么解决的做一下记录。也算是一次加深理解的过程。
爬取的目标页面 2017-12-31_172919.png目标是爬取问题推荐页面的所有问题。但是随后我就发现一个问题,第一次我是通过抓取这个https://www.zhihu.com/explore/recommendations链接来获取问题列表,但是当代码敲完测试的时候发现数据只有20条。。。这显然不是我所期望的,看了一遍代码 发现代码是没有毛病,那么问题出在哪里?排查了一片以及debug模式。最终发现是页面的问题。因为我忽略了一个重要的地方。页面是动态加载的,而且每次只加载20条。
问题就出在这个地方,这里其实还包含了一个地址(https://www.zhihu.com/node/ExploreRecommendListV2)。通过抓包可以发现(google的F12真的好用,推荐多去看看)
ajax请求头.png
请求参数.png
返回json结果.png
知道问题出在哪里其实就已经完成了一半了。说一下接下来我的思路:
- 因为用请求的参数可以看出其实这就是一个类似于一个分页信息一样。那么我只要在java代码中每次模仿页面的请求给知乎发送ajax请求,然后解析返回的json结果是不是就可以获取其中的问题信息了。
- 上面其实就已经有两个需求需要解决。
- 在java代码中模仿ajax发送请求。采用的是Httpclient。
- 解析返回的json,Gson jar包可以完美解决。
封装了httpclient post请求
public class HttpClientUtil {
/**
*
* @Title: doPost
* @Description: 模仿提交post请求
* @param @param url
* @param @param map 请求的参数 采用map集合封装参数
* @param @param charset 编码格式
* @param @return 参数
* @return String 返回类型
* @author liangchu
* @date 2017-12-31 下午7:09:14
* @throws
*/
public static String doPost(String url,Map<String,Object> map,String charset){
HttpClient httpClient = null;
HttpPost httpPost = null;
String result = null;
try{
httpClient = new SSLClient();
httpPost = new HttpPost(url);
//设置参数
List<NameValuePair> list = new ArrayList<NameValuePair>();
Iterator iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Entry<String,Object> elem = (Entry<String, Object>) iterator.next();
list.add(new BasicNameValuePair(elem.getKey(),(String) elem.getValue()));
}
if(list.size() > 0){
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list,charset);
httpPost.setEntity(entity);
}
HttpResponse response = httpClient.execute(httpPost);
if(response != null){
HttpEntity resEntity = response.getEntity();
if(resEntity != null){
result = EntityUtils.toString(resEntity,charset);
}
}
}catch(Exception ex){
ex.printStackTrace();
}
return result;
}
}
根据url链接地址获取对应的信息列表
/**
*
* @Title: spiderZH2
* @Description: 这里是采用httpclient包发送请求 获取需要加载的列表
* @param @param url 参数url地址 offset 根据offset显示问题信息列表
* @return void 返回类型
* @author liangchu
* @date 2017-12-31 下午2:11:23
* @throws
*/
public static void spiderZH2(String url,int offset){
try {
//String curl ="https://www.zhihu.com/node/ExploreRecommendListV2";
Map<String,Object> createMap = new HashMap<String,Object>();
String charset = "utf-8";
// method 提交的参数
createMap.put("method", "next");
Map<String,Object> map = new HashMap<String, Object>();
// 分页显示的数据
map.put("limit", 20);
map.put("offset", offset);
createMap.put("method", "next");
Gson gson = new Gson();
String mapStr = gson.toJson(map);
// 请求的参数
createMap.put("params", mapStr);
// 根据httpclient模仿post请求
String httpOrgCreateTestRtn = HttpClientUtil.doPost(url,createMap,charset);
Map maps = gson.fromJson(httpOrgCreateTestRtn, Map.class);
String html = maps.get("msg").toString();
Document doc = Jsoup.parse(html);
Elements elements =
doc.select("div[class=zm-item]").select("h2").
select("a[class=question_link]");
File file = new File("F:/replite/zhifuwenda.txt");
// 遍历每个问题节点
for (Element question : elements) {
// 获取连接地址
String qUrl = question.attr("href");
// 这里需要判断urlhttp格式
if(!qUrl.contains("https://")){
qUrl = "https://www.zhihu.com"+qUrl;
}
Document document2=Jsoup.connect(qUrl)
.userAgent("Mozilla/5.0 "
+ "(iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) "
+ "AppleWebKit/533.17.9"
+ " (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5")
.get();
// 问题标题
Elements title = document2.select("#root").select("div").select("main").
select("div").select("div:nth-child(10)").select("div.QuestionHeader").
select("div.QuestionHeader-content").select("div.QuestionHeader-main").
select("h1");
// 回答问题的内容
Elements content = document2.select("#root").select("div").select("main").
select("div").select("div.Question-main").select("div.Question-mainColumn").
select("div.Card.AnswerCard").select("div").select("div").
select("div.RichContent.RichContent--unescapable").
select("div.RichContent-inner");
if (!file.getParentFile().exists()) {//判断路径是否存在,如果不存在,则创建上一级目录文件夹
file.getParentFile().mkdirs();
}
FileWriter fileWriter=new FileWriter(file, true);
fileWriter.write("=============链接:"+qUrl+"\r\n");
fileWriter.write("=============标题:"+title.get(0).text()+"\r\n");
fileWriter.write("=============回答:"+content.get(0).text()+"\r\n");
fileWriter.close();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
调用
public static void main(String [] args){
// 这里采用循环的方式去除列表
String url = "https://www.zhihu.com/node/ExploreRecommendListV2";
for(int i=1;i<1000;i++){
spiderZH2(url,59+i*20);
}
}
数据采集截图
2017-12-31_191857.png改进的地方还有很多,比如说可以采用多线程采集,应该效率会比较高,在实际的应用应该是采集的数据可以存入redis中,然后在由redis insert进数据库。要改进的地方还有很多,时间的问题也就没有去考虑的。这也算是上一个版本一个小小的提升吧。O(∩_∩)O
网友评论