Java 爬知乎某个问题下的所有图片

栏目: Java · 发布时间: 6年前

内容简介:Java 爬知乎某个问题下的所有图片

网上有许多关于知乎的爬虫,但都是用 Python 来实现的,由于我的主语言是 Java 所以想用 Java 来实现下。

本次用到了一个国人开发的优秀的爬虫框架: WebMagic

思路

首先打开知乎的一个问题 https://www.zhihu.com/question/43551423 ,然后打开 FireFox 的 F12 控制台,然后发现知乎的问题需要翻页,且是无刷新的请求,那必然是 AJAX 请求了。

筛选一下,发现一个可疑的东西,以 answers 开头的,应该就是回答内容的请求了。

Java 爬知乎某个问题下的所有图片

观察一下这个请求: https://www.zhihu.com/api/v4/questions/43551423/answers?include=data[*].is_normal,admin_closed_comment,reward_info,is_collapsed,annotation_action,annotation_detail,collapse_reason,is_sticky,collapsed_by,suggest_edit,comment_count,can_comment,content,editable_content,voteup_count,reshipment_settings,comment_permission,created_time,updated_time,review_info,question,excerpt,relationship.is_authorized,is_author,voting,is_thanked,is_nothelp,upvoted_followees;data[*].mark_infos[*].url;data[*].author.follower_count,badge[?(type=best_answerer)].topics&offset=0&limit=20&sort_by=default

可以发现其中的 43551423 代表的就是该问题的 ID 号,请求参数中的参数 include 为请求的内容, offset 为偏移量(表示从多少页开始请求), limit 为本页的答案数量(最大为 20), sort_by排序 方式。其实我们只需要关注 offset 即可,其他的默认就好。

有了思路以后,我们先用 Postman 来测试一下:

Java 爬知乎某个问题下的所有图片

但是得到了这样的结果,他告诉我 AuthenticationInvalidRequest 认证无效,看来是需要添加认证,那我们再去浏览器看下,刚才的请求头中还有什么信息传递了过去,然后发现了这个东东:

Java 爬知乎某个问题下的所有图片

我们把这个添加到请求头中,发现可以正常得到数据了:

Java 爬知乎某个问题下的所有图片

观察一下这个数据发现,这是 20 条用户的回答,因为我们请求的参数 limit 为 20 ,所以这里为 20 条,那么我们可以根据这个来判断是否翻页结束,每次 limit 都自增 20,直到得到的数据不满 20 条,则代表翻页结束,停止爬取。

然后得到了数据,就开始解析图片下载地址吧:

Java 爬知乎某个问题下的所有图片

img 元素就是我们要爬取的图片,可以看到 data-original 属性的内容与 src 属性的内容都是图片的地址,但验证后发现,src 可能是缩略图,所以我们还是选择 data-original 属性的图片地址。

得到图片地址后,下载到本地就可以了,直接看代码吧!

上代码!

package com.yfzz.zhihu3 ;

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Html;
import us.codecraft.webmagic.selector.JsonPathSelector;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;

public class ZhihuQuestion implements PageProcessor {

    private Site site = Site.me().setSleepTime(2000)
            .setCycleRetryTimes(5)
            .setRetryTimes(5)
            .setUserAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36")
            .addHeader("authorization", "你的认证信息")
            .setCharset("UTF-8");

    private static String questionID = "37787176";  // 要爬取的问题 ID 号
    private static String filePath = "e://zhihu";   // 文件存放路径,程序会自动在此路径后添加一级目录为问题标题
    private static int offset = 0;              // 偏移量,表示从第 n 个答案开始获取,limit 表示获取多少个(上限为20)
    private static int count = 0;               // 下载到的图片总数

    @Override
    public void process(Page page) {
        Html html = page.getHtml();
        // 得到当前页的所有答案的 ID 号,主要用处是为了判断是否到页尾。
        List<String> idList = new JsonPathSelector("$.data[*].id").selectList(page.getRawText());
        String title = new JsonPathSelector("$.data[*].question.title").selectList(page.getRawText()).get(0);

        int getSize = idList.size();        // 当前页获取到的回答个数
        if (getSize == 20) {
            // 将下一页添加到队列中
            offset += 20;
            String url = "https://www.zhihu.com/api/v4/questions/" + questionID + "/answers?include=data%5B%2A%5D.is_normal%2Cadmin_closed_comment%2Creward_info%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Ccreated_time%2Cupdated_time%2Creview_info%2Cquestion%2Cexcerpt%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%2Cupvoted_followees%3Bdata%5B%2A%5D.mark_infos%5B%2A%5D.url%3Bdata%5B%2A%5D.author.follower_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&offset=" + offset + "&limit=20&sort_by=default";
            page.addTargetRequest(url);
        }

        // 根据正则匹配图片地址并去除特殊字符。
        // 例: 正则得到的图片链接 : \"https://pic2.zhimg.com/v2-94ae015b8e1bd2e0dc9bdd7d7da6d7ed_r.jpg\" 需要去除特殊字符 "
        List<String> imgList = html.regex("data-original=\"(.*?)\"").replace("\\\\"", "").all();

        // 图片链接去重复
        HashSet<String> set = new HashSet<String>(imgList);
        count += set.size();
        
        System.out.println("正在下载第" + offset + "-" + (offset + getSize) + "个回答的图片,当前页图片数量为:" + set.size()  + ",目前总图片数量:" + count);
        for (String url : set) {
            String fileName = url.substring(url.lastIndexOf('/') + 1, url.length());
            try {
                String savePath = filePath + "/" + title + "_" + questionID;    // 下载路径:指定路径/标题/问题ID
                downloadPicture(url, savePath, fileName);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    public static void downloadPicture(String urlString, String savePath, String filename) throws Exception {
        File file = new File(savePath + File.separator + filename);

        if (!new File(savePath).exists()) {
            System.out.println("下载目录不存在,已创建:" + savePath);
            new File(savePath).mkdirs();
        }
        if (file.exists()) {
            System.out.println("文件已存在,跳过该文件:" + file.getName());
            return;
        }

        OutputStream os = new FileOutputStream(file);
        // 构造URL
        URL url = new URL(urlString);
        // 打开连接
        URLConnection con = url.openConnection();
        //设置请求超时为5s
        con.setConnectTimeout(5 * 1000);
        // 输入流
        InputStream is = con.getInputStream();

        // 1K的数据缓冲
        byte[] bs = new byte[1024];
        // 读取到的数据长度
        int len;
        // 开始读取
        while ((len = is.read(bs)) != -1) {
            os.write(bs, 0, len);
        }
        // 完毕,关闭所有链接
        os.close();
        is.close();
    }

    @Override
    public Site getSite() {
        return site;
    }

    public static void main(String[] args) throws Exception {
        Spider.create(new ZhihuQuestion()).
                thread(1).
                addUrl("https://www.zhihu.com/api/v4/questions/" + questionID + "/answers?include=data%5B%2A%5D.is_normal%2Cadmin_closed_comment%2Creward_info%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Ccreated_time%2Cupdated_time%2Creview_info%2Cquestion%2Cexcerpt%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%2Cupvoted_followees%3Bdata%5B%2A%5D.mark_infos%5B%2A%5D.url%3Bdata%5B%2A%5D.author.follower_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&offset=0&limit=20&sort_by=default")
                .run();
    }
}

爬取结果展示

Java 爬知乎某个问题下的所有图片

前 20 个回答就有 1050 张图片!!!

然后我用浏览器打开了这个网页,发现……

Java 爬知乎某个问题下的所有图片

竟然有 9559 个回答,我的天,我还是停了吧,估计下载完这些,我这小硬盘都要满了,然后看了下已经下载完成的。

Java 爬知乎某个问题下的所有图片

嗯,按照这个情况,下载完这些,估计上 10G 了,那么知乎这么多钓鱼贴,咳咳,自己理解吧。

总结

这只是一个简单的例子,为了防止给知乎的服务器带了太大的压力,这里我的代码是写成了单线程的方式。后续我会再更新一些关于 Java 的爬虫以及详细思路,有什么问题可以在评论里给我留言。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

金字塔原理

金字塔原理

[美] 巴巴拉·明托 / 王德忠、张珣 / 民主与建设出版社 / 2002-12 / 39.80元

《金字塔原理》是一本讲解写作逻辑与思维逻辑的读物,全书分为四个部分。 第一篇主要对金字塔原理的概念进行了解释,介绍了如何利用这一原理构建基本的金字塔结构。目的是使读者理解和运用简单文书的写作技巧。 第二篇介绍了如何深入细致地把握思维的环节,以保证使用的语句能够真实地反映希望表达的思想要点。书中列举了许多实例,突出了强迫自己进行“冷静思维”对明确表达思想的重要性。 第三篇主要针对的......一起来看看 《金字塔原理》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具