Pixiv爬虫分析记录

更多详见博客:https://www.oysterqaq.com/archives/850

仅仅只是忠实记录开发过程,最终教程另见

 

1)模拟登陆

在准备阶段收集了一些情报(个人习惯)得知Pixiv下载大图必须账户登录(实际上并不需要),按着网上python爬虫教程的分析,也试着对pixiv登陆过程进行了抓包分析(为了查看登录页面post的参数,务必勾选上preserve log)

登录界面url:https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index

输入账户名和密码后点击登录后,查看对登录接口所post的数据

Pixiv爬虫分析记录_第1张图片

参数中唯一有疑问的就是post_key了,既然是post的请求参数,大概率应该在表单中。新建一个无痕窗口,进入登录页面F12查看网页源码,找到表单部分,会发现几个隐藏域

    换浏览器多次抓包后基本实锤了,post_key是每次在第一次进入登录界面时服务器随机生成的一个校验码与sessionid相对应,添加到隐藏域中在post请求中一并提交到后台验证。

    那么情况就比较明朗了,在get登录页面的响应中将post_key取出,加入post参数中,之后持有返回的cookie维持在登录状态则web端模拟登陆完毕。

    2)抓取日排行

    日排行url:https://www.pixiv.net/ranking.php?mode=daily

    分析了下日排行页面的html,发现基本信息都包含在每个section内

    页面往下拉,抓取ajax请求链接

    ajax机制为每50个名次进行一次ajax请求,既然有了ajax请求链接,那么首页信息猜测也可以通过ajax请求获得(不需要分析html直接获取json数据),具体过程不说了,直接贴ajax请求的链接,不管是首页还是下一页的json都可以从这里获取(也就是说不用解析排行榜首页html也不用模拟登陆)

    ajax请求url:https://www.pixiv.net/ranking.php?mode=daily&p=1&format=json

    可以看出json中已经包含原图的链接,但是测试发现请求原图有请求来源限制(防盗链)

    Pixiv爬虫分析记录_第2张图片

    Referrer:https://www.pixiv.net/member_illust.php?mode=medium&illust_id=69526398

    Json中得到可以url:https://i.pximg.net/c/240x480/img-master/img/2018/07/04/00/03/26/69526398_p0_master1200.jpg和图片id,根据这两者进行拼接,可以直接get得到原图(但是后缀到底是png还是jpg并不明确,错误会返回403,默认使用jpg,预计在开发中若返回错误,换参递归调用自身)

    1)实现非会员热门度搜索

    众所周知,在pixiv是否是高级会员影响最大的部分是热门度搜索,普通会员搜索时默认按照日期排序,由于pixiv投稿并不筛选,导致各类良莠不齐的作品出现在搜索结果中

    普通会员:

    Pixiv爬虫分析记录_第3张图片

    高级会员:

    目标是实现非会员类热门度搜索,基本想法是提取筛选排序搜索结果的json数据

    搜索页面url:https://www.pixiv.net/search.php?word=fate&order=date_d&p=2

    使用postman模拟发送get请求后发现搜索结果是由js动态生成页面

    由于是第一次写爬虫,第一时间有点傻眼,查看html发现搜索结果的json数据是包含在一个tag的字段中

    估计是页面加载完成后通过js动态添加到html中展示

    推荐关键词位置:

    到这里就差不多了,大概过程就是获取总搜索结果数算出总页数,遍历页面的html,使用正则清洗筛选json数据字符串(只需要画作id,订阅个数,画作缩略图url),排序搜索结果(可以使用js排序json数据)

    实际上以上工作单线程版写完后发现,效率还是很低,关键就是web端搜索结果是分页展示而不是ajax,这就导致得一次一次获取html而不能直接获取json

    思维发散一下,注意到pixiv的app端,搜索结果是类似ajax加载的展示效果,猜测应该有直接返回搜索结果json的api,于是开始对app进行抓包,使用fiddler对手机抓包,需要注意的是在安装证书时候碰到了无法访问电脑端的问题,关闭windows防火墙就行(只是开启单个端口并没有作用)

    不出所料,app端确实是有直接返回json数据的接口

    查看请求头

    Pixiv爬虫分析记录_第4张图片

    多次请求后发现X-Client-Hash\X-Client-Time\Authorization用于校验:

    • X-Client-Hash为基于时间的加密字符串
    • X-Client-Time为pixiv规定格式的时间信息
    • Authorization猜测为类似sessionid

    Authorization是类似用户cookie,服务端不出意外一般可以一直使用,时间格式也好办,但是问题出在X-Client-Hash的生成方式,无法确定是如何对时间信息进行加密

    由于没法确定请求头的生成方式,考虑反编译apk,寻找生成X-Client-Hash的方法

    着手反编译(反编译结果其实并不好用),查找

        r8 = this;
                r7 = "  ~@~@~@~@~@~@~@~@~@~@~   Smob - Mod protection tool v2.5 by Kirlif'   ~@~@~@~@~@~@~@~@~@~@~  ";
                r0 = new java.text.SimpleDateFormat;
                r1 = "yyyy-MM-dd'T'HH:mm:ssZZZZZ";
                r7 = 3;
                r2 = java.util.Locale.US;
                r0.(r1, r2);
                r1 = new java.util.Date;
                r7 = 4;
                r1.();
                r0 = r0.format(r1);
                r7 = 0;
                r1 = new java.lang.StringBuilder;
                r1.();
                r7 = 0;
                r1.append(r0);
                r2 = "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c";
                r1.append(r2);
                r7 = 6;
                r1 = r1.toString();
                r7 = 6;
                r1 = jp.pxv.android.q.bb.b(r1);
                r2 = r9.request();
                r7 = 0;
                r2 = r2.newBuilder();
                r7 = 6;
                r3 = "User-Agent";
                r4 = jp.pxv.android.client.h.a;
                r2 = r2.addHeader(r3, r4);
                r7 = 5;
                r3 = "Content-Type";
                r7 = 4;
                r4 = "application/x-www-form-urlencoded;charset=UTF-8";
                r7 = 4;
                r2 = r2.addHeader(r3, r4);
                r7 = 6;
                r3 = "Accept-Language";
                r7 = 2;
                r4 = java.util.Locale.getDefault();
                r7 = 0;
                r4 = r4.toString();
                r2 = r2.addHeader(r3, r4);
                r3 = "App-OS";
                r7 = 5;
                r4 = "android";
                r2 = r2.addHeader(r3, r4);
                r7 = 3;
                r3 = "App-OS-Version";
                r7 = 6;
                r4 = android.os.Build.VERSION.RELEASE;
                r7 = 5;
                r2 = r2.addHeader(r3, r4);
                r7 = 4;
                r3 = "App-Version";
                r7 = 3;
                r4 = "5.0.104";
                r7 = 2;
                r2 = r2.addHeader(r3, r4);
                r7 = 3;
                r3 = "X-Client-Time";
                r0 = r2.addHeader(r3, r0);
                r7 = 4;
                r2 = "X-Client-Hash";
                r7 = 4;
                r0 = r0.addHeader(r2, r1);
                r7 = 6;
                r0 = r0.build();
                r7 = 0;
       jp.pxv.android.q.as.a(r8);
            r0 = "MD5";	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r7 = 3;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r0 = java.security.MessageDigest.getInstance(r0);	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r7 = 3;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r8 = r8.getBytes();	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r8 = r0.digest(r8);	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r0 = new java.lang.StringBuilder;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r0.();	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r7 = 4;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r1 = r8.length;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r7 = 5;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r2 = 0;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r3 = 0;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
        L_0x001d:
            if (r3 >= r1) goto L_0x003f;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
        L_0x001f:
            r7 = 1;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r4 = r8[r3];	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r7 = 1;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r5 = "%02x";	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r6 = 1;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r6 = 1;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r7 = 7;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r6 = new java.lang.Object[r6];	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r7 = 1;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r4 = java.lang.Byte.valueOf(r4);	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r6[r2] = r4;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r7 = 2;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r4 = java.lang.String.format(r5, r6);	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r0.append(r4);	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r7 = 0;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r3 = r3 + 1;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r7 = 3;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            goto L_0x001d;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r1 = 3;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
        L_0x003f:
            r7 = 4;	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r8 = r0.toString();	 Catch:{ NoSuchAlgorithmException -> 0x0047 }
            r7 = 4;
            return r8;
            r2 = 0;
        L_0x0047:
            r8 = move-exception;
            r7 = 4;
            r0 = "StringUtils";
            r1 = "NoSuchAlgorithmException";
            jp.pxv.android.q.ae.c(r0, r1, r8);
            r8 = "";
            r7 = 2;
            return r8;
            r5 = 4;

    刚看着结果emmm了一会,还是忍住恶心看下去,由于多次测试知道hash是根据时间变化的,半猜着的得出了结果

    public static String[] gethash() throws NoSuchAlgorithmException {
            SimpleDateFormat simpleDateFormat;
            String fortmat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ";
            simpleDateFormat = new SimpleDateFormat(fortmat, Locale.US);
            Date date = new Date();
            String time = simpleDateFormat.format(date);
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            String seed = time + "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c";
            byte[] digest = md5.digest(seed.getBytes());
            StringBuilder hash = new StringBuilder();
            for (int r3 = 0; r3 < digest.length; r3++) {
                hash.append(String.format("%02x", Byte.valueOf(digest[r3])));
            }
            return new String[]{time, hash.toString()};
        }

    到这里就先告一段落,由于自己挖坑,可能之前web端所做的分析都白费了(app端的请求操作相对web端方便了很多),之后将从app端的模拟登陆开始分析起,以上仅仅只是一次记录。

    你可能感兴趣的:(Pixiv爬虫分析记录)