要从包图网下载资源,但是一个个点击下载又感觉烦琐,就想着能不能通过代码来批量下载。故有此文。

访问任意一个资源如http://ibaotu.com/sucai/614383.html ,点击“免费下载”会进入到http://ibaotu.com/?m=download&id=614383 ,有VIP账号的可以点击“VIP极速下载”(本博客测试时使用的是VIP极速下载),下载地址为http://ibaotu.com/?m=downloadopen&a=open&id=614383&down_type=1 。

可以看到下载地址中有资源编号,于是,开始考虑使用HttpClient批量访问。

第一个问题:重定向&乱码

当使用HttpClient批量访问下载路径时,会发现InvalidRedirectLocationException: Invalid redirect location异常信息,该问题出现的原因是当访问下载地址时,服务端返回了一个重定向而不是直接返回200,又因为重定向地址中有中文导致出现该问题(确切说,是地址的QueryString部分有乱码)。

解决方案是使用HttpClient的RedirectStrategy,把QueryString部分使用UTF-8转码一下即可,

    private URI sanitizeUrl(HttpRequest request, String sanitizeURL) throws ProtocolException {
        URI uri = null;

        try {
            URL url = new URL(URLDecoder.decode(sanitizeURL));
            String query  = null;
            if (StringUtils.isNotEmpty(url.getQuery())) {
                query = new String(url.getQuery().getBytes("ISO8859-1"), "utf-8");
            }
            uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(),
                    url.getPath(), query, url.getRef());

            LOG.info(request.getRequestLine().getUri() + " -> " + uri.toString());
        } catch (URISyntaxException | MalformedURLException | UnsupportedEncodingException e) {
            throw new ProtocolException(e.getMessage(), e);
        }

        return uri;
    }

身份识别

该系统使用一个名叫auth_id的cookie来识别用户身份,在请求时传入即可。

auth_id

验证码(一)

当下载资源到一定数量时如2000,系统会先显示验证码页面,只有点击了正确的验证码时才可以继续下载。验证码大致如下:

验证码图片

可以发现验证码的地址是http://ibaotu.com/index.php?m=downVarify&a=index&id=资源编号
这样我们就可以对每个资源都先访问验证码信息,再访问下载址,就可以愉快地下载了。

问题是如何破解验证码信息。

访问验证码页面时会发现在页面上生成9个文字让用户点击,可试着将某一个验证码的链接反复请求,发现显示的汉字是同一个汉字只是图片略有不同,由此可以考虑反复请求验证码页面以枚举一下该网站共有多少个图片验证码(该思路由吐糟了站长提供)。

我测试了一下,解析页面中的9个图片的路径,把它们放到redis中以去重,访问100次得到156个结果,又访问1000次还是156个结果,由此推测该网站只有156个验证码文字。

我把编码与汉字的对应关系整理了部分(只有36个,全是一个个肉眼识别的,_):

Y2V08108=伍
Y2V08108M0A4O0O0O1O0O0O8=瓦
Y2V08118M0A4O0O0O1O0O0O8=瓦
Y2V08108M0Q4O0O0O1O0O0O8=倾
Y2V08108M0g4O0O0O1O0O0O8=阵
Y2V08108M0w4O0O0O1O0O0O8=碳
Y2V08108N0A4O0O0O1O0O0O8=演
Y2V08108N0Q4O0O0O1O0O0O8=威
Y2V08108N0g4O0O0O1O0O0O8=附
Y2V08108N0w4O0O0O1O0O0O8=牙
Y2V08108O0A4O0O0O1O0O0O8=芽
Y2V08108O0Q4O0O0O1O0O0O8=永
Y2V08118=域
Y2V08118M0Q4O0O0O1O0O0O8=斜
Y2V08118M0g4O0O0O1O0O0O8=灌
Y2V08118M0w4O0O0O1O0O0O8=欧
Y2V08118N0A4O0O0O1O0O0O8=献
Y2V08118N0Q4O0O0O1O0O0O8=顺
Y2V08118N0g4O0O0O1O0O0O8=猪
Y2V08118N0w4O0O0O1O0O0O8=洋
Y2V08118O0A4O0O0O1O0O0O8=腐
Y2V08118O0Q4O0O0O1O0O0O8=请
Y2V08128=甚
Y2V08128M0A4O0O0O1O0O0O8=透
Y2V08128M0Q4O0O0O1O0O0O8=司
Y2V08128M0g4O0O0O1O0O0O8=危
Y2V08128M0w4O0O0O1O0O0O8=括
Y2V08128N0A4O0O0O1O0O0O8=脉
Y2V08128N0Q4O0O0O1O0O0O8=宜
Y2V08128N0g4O0O0O1O0O0O8=笑
Y2V08128N0w4O0O0O1O0O0O8=若
Y2V08128O0A4O0O0O1O0O0O8=尾
Y2V08128O0Q4O0O0O1O0O0O8=束
Y2V08138=迅
Y2V08138M0A4O0O0O1O0O0O8=壮
Y2V08138M0Q4O0O0O1O0O0O8=暴

(奇怪的是瓦字有两个编号,使用时忽略一个即可)

验证验证码的地址是http://ibaotu.com/index.php?m=downVarify&a=verifyCaptcha&answer_key=汉字编码&callback=资源id

有一个需要注意的点,当对一个资源验证后,可以反复下载该资源。

枚举验证码数量的代码如下:

        Jedis jedis = new Jedis();
        jedis.select(6);
        String url = "http://ibaotu.com/index.php?m=downVarify&a=index&id=634278";
        for (int i = 0; i < 10000; i++) {
            String pageSource = HttpClientUtil.doGet(url, null, null);
            Document document = Jsoup.parse(pageSource);
            Elements hanZiEle = document.select(".yanzheng-wrap .tips span");
            Elements imgEleList = document.select(".yanzheng-wrap .imgs-wrap img");
            for (Element element : imgEleList) {
                String src = element.attr("src");
                String attr = element.attr("data-key");
                jedis.set(attr, src);
            }
        }
        System.out.println("done");

代码执行后,本地redis中就有数据了。

验证码(二)

验证码算是委婉地破解了。
发现代码执行之后还是会有问题。经测试发现在验证验证码的时候还要传一个名叫answer_key的cookie。它的内容由验证码地址提供,验证码地址返回了几个响应头,如图

SetCookie响应头

其中那个answer_key后的值就是在验证验证码的请求中添加的cookie。

未完成

上面的完成后就可以愉快地下载了。

但是——当下载一定量的时候,系统的验证码页面会变成http://ibaotu.com/index.php?m=downVarify&a=load&id=资源编号 ,这时候页面会提醒“服务器繁忙,请等5分钟后下载”的提示。

个人认为服务端的这个验证是对用户判断的,否则就可以使用代理来下载文件。

说明

  • 请注重过程,切莫在意结果。
  • 思路已经提供,就不提供完整代码了。
  • 图片验证码码这个功能的弱点在于一个汉字对应一个编码,若是在生成图片验证码时使用随机生成一个uuid与汉字绑定,就无法爬了。