Java--汽车之家论坛反爬虫破解

2 篇文章 0 订阅
2 篇文章 0 订阅

问口碑的人比较多,写了一下思路,请点击这里

现在论坛的反爬虫也改成了字体映射,所以本篇破解方式已经不适用了,新的破解方式可以看我的口碑破解方法. ---2018-1-9

目前论坛可以用 , 口碑的不能用 . 最近的口碑破解有时间分享 ---2017.11.16


公司给的任务 ,需要爬取汽车之家论坛的内容, 由于文章的内容有一些反爬虫的机制, 所以并不好直接爬取. 在网上搜了一些解决办法后, 看到了"星光海豚"写的解决的方法.  反爬虫破解系列-汽车之家利用css样式代替文字破解方法  .

他的博客里有具体的破解原理, 原理很简单, 就是实现起来比较麻烦. 由于他是用Python实现的, 而我需要用的Java, 所以我就在根据他的原理用Java实现了一下, 大部分都是参考的他的原理和思路. 再次感谢"星光海豚".

用到了一个Jsoup包来解析网页

这是阿里云镜像的坐标

<dependency>
			<!-- jsoup HTML parser library @ https://jsoup.org/ -->
			<groupId>org.jsoup</groupId>
			<artifactId>jsoup</artifactId>
			<version>1.10.3</version>
		</dependency>



代码如下

package test;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class DecodeAutoHome {
	public String getArticle(String url) throws IOException, URISyntaxException {
		// 文章的文本内容
		String article = null;

		// 获取网页的Document对象
		Document doc = Jsoup.connect(url).get();
		// 获取文章的内容
		Element content = doc.getElementsByClass("rconten").get(0);

		String articleHtml = content.html();

		// #############################################################
		// 处理js混淆部分

		// 获取混淆的js代码
		String script = content.getElementsByTag("script").get(0).html();

		/*
		 * 1.判断混淆 无参数 返回常量 函数 function Ad_() { function _A() { return 'Ad__'; };
		 * if (_A() == 'Ad__') { return '8'; } else { return _A(); } }
		 * 
		 * 需要将Ad_()替换为8
		 */
		// 取出每个"混淆 无参数 返回常量 函数" 格式的函数
		String regex1 = "function\\s*(\\w+)\\(\\)\\s*\\{\\s*" + "function\\s+\\w+\\(\\)\\s*\\{\\s*"
				+ "return\\s+[\'\"]([^\'\"]+)[\'\"];\\s*" + "\\};\\s*"
				+ "if\\s*\\(\\w+\\(\\)\\s*==\\s*[\'\"]([^\'\"]+)[\'\"]\\)\\s*\\{\\s*"
				+ "return\\s*[\'\"]([^\'\"]+)[\'\"];\\s*" + "\\}\\s*else\\s*\\{\\s*" + "return\\s*\\w+\\(\\);\\s*"
				+ "\\}\\s*" + "\\}";
		Pattern p1 = Pattern.compile(regex1);
		Matcher m1 = p1.matcher(script);
		// 找出所有匹配的字符串,存入List中
		List<String> l1 = new ArrayList<String>();
		// 遍历每一条函数
		while (m1.find()) {
			// Matcher.group(int i) 此方法可以获取正则表达式中第i个括号里的内容.
			l1.add(m1.group());
		}
		for (String str : l1) {
			Pattern p11 = Pattern.compile(regex1);
			Matcher m11 = p11.matcher(str);
			while (m11.find()) {
				String name = m11.group(1);// 获取第一个括号里的内容
				String b = m11.group(2);// 获取第二个括号里的内容
				String c = m11.group(3);// 获取第三个括号里的内容
				String value = "";
				if (b.equals(c)) {
					value = m11.group(4);
				} else {
					value = m11.group(2);
				}
				script = script.replaceAll(name + "\\(\\)", value);// 给name的正则加上括号
			}

		}

		// 2.判断混淆 无参数 返回函数 常量
		/*
		 * function wu_() { function _w() { return 'wu_'; }; if (_w() == 'wu__')
		 * { return _w(); } else { return '5%'; } } 需要将wu_()替换为5%
		 */

		// 取出每一个"混淆 无参数 返回函数 常量" 格式的函数

		String regex2 = "function\\s*(\\w+)\\(\\)\\s*\\{\\s*" + "function\\s*\\w+\\(\\)\\s*\\{\\s*"
				+ "return\\s*[\'\"]([^\'\"]+)[\'\"];\\s*" + "\\};\\s*"
				+ "if\\s*\\(\\w+\\(\\)\\s*==\\s*[\'\"]([^\'\"]+)[\'\"]\\)\\s*\\{\\s*" + "return\\s*\\w+\\(\\);\\s*"
				+ "\\}\\s*else\\s*\\{\\s*" + "return\\s*[\'\"]([^\'\"]+)[\'\"];\\s*" + "\\}\\s*" + "\\}";
		Pattern p2 = Pattern.compile(regex2);
		Matcher m2 = p2.matcher(script);
		// 找出所有匹配的字符串,存入List中
		List<String> l2 = new ArrayList<String>();
		while (m2.find()) {
			l2.add(m2.group());
		}
		for (String str : l2) {
			Pattern p21 = Pattern.compile(regex2);
			Matcher m21 = p21.matcher(str);
			while (m21.find()) {
				String name = m21.group(1);// 获取第一个括号里的内容
				String b = m21.group(2);
				String c = m21.group(3);
				String value = "";
				if (!b.equals(c)) {
					value = m21.group(4);
				} else {
					value = m21.group(2);
				}
				script = script.replaceAll(name + "\\(\\)", value);
			}
		}

		// 3.var 参数等于返回值函数
		/*
		 * 类似于此的混淆 var ZA_ = function(ZA__) { 'return ZA_'; return ZA__; };
		 */
		// 需要替换ZA__(',5_198')为,5_198

		// 从js中,利用正则,提取上面这种格式的变量
		String regex3 = "var\\s*([^=]+)\\s*=\\s*function\\(\\w+\\)\\{\\s*" + "[\'\"]return\\s*\\w+\\s*[\'\"];\\s*"
				+ "return\\s+\\w+;\\s*" + "\\};";
		Pattern p3 = Pattern.compile(regex3);
		Matcher m3 = p3.matcher(script);
		// 找出所有匹配的字符串,存入List中
		List<String> l3 = new ArrayList<String>();
		while (m3.find()) {
			l3.add(m3.group());
		}
		for (String str : l3) {
			Pattern p31 = Pattern.compile(regex3);
			Matcher m31 = p31.matcher(str);
			while (m31.find()) {
				String name31 = m31.group(1);
				// 再次利用正则,在js中截取ZA__(',5_198')
				String regex32 = name31 + "\\(([^\\)]+)\\)";
				Pattern p32 = Pattern.compile(regex32);
				Matcher m32 = p32.matcher(script);
				while (m32.find()) {
					String value32 = m32.group(1);
					script = script.replaceAll(regex32, value32);
				}
			}

		}
		// System.out.println(script);
		// 4.var 无参数 返回常量 函数
		/*
		 * 类似于此结构 var jW_ = function() { 'return jW_'; return '34;'; };
		 */
		// 需要替换jW_()为34;
		String regex4 = "var\\s*([^=]+)=\\s*function\\(\\)\\s*\\{\\s*" + "[\'\"]return\\s*\\w+\\s*[\'\"];\\s*"
				+ "return\\s*[\'\"]([^\'\"]+)[\'\"];\\s*" + "\\};";
		Pattern p4 = Pattern.compile(regex4);
		Matcher m4 = p4.matcher(script);
		// 找出所有匹配的字符串,存入List中
		List<String> l4 = new ArrayList<String>();
		while (m4.find()) {
			l4.add(m4.group());
			/*
			 * String name4 = m4.group(1);//获取第一个括号里的内容 String value4 =
			 * m4.group(2);//获取第一个括号里的内容 script =
			 * script.replaceAll(name4+"\\(\\)", value4);
			 */
		}
		for (String str4 : l4) {
			Pattern p41 = Pattern.compile(regex4);
			Matcher m41 = p41.matcher(str4);
			while (m41.find()) {
				String name41 = m41.group(1);// 获取第一个括号里的内容
				String value41 = m41.group(2);// 获取第一个括号里的内容
				script = script.replaceAll(name41 + "\\(\\)", value41);
			}
		}
		// 5.无参数 返回常量 函数
		/*
		 * 类似于此结构 function HB_() { 'return HB_'; return '_;'; }
		 */
		// 需要将HB_()替换为_;
		String regex5 = "function\\s*(\\w+)\\(\\)\\s*\\{\\s*" + "[\'\"]return\\s*[^\'\"]+[\'\"];\\s*"
				+ "return\\s*[\'\"]([^\'\"]+)[\'\"];\\s*" + "\\}\\s*";
		Pattern p5 = Pattern.compile(regex5);
		Matcher m5 = p5.matcher(script);
		// 找出所有匹配的字符串,存入List中
		List<String> l5 = new ArrayList<String>();
		while (m5.find()) {
			l5.add(m5.group());

		}
		// 遍历每一条匹配到的字符串
		for (String str5 : l5) {
			Pattern p51 = Pattern.compile(regex5);
			Matcher m51 = p51.matcher(str5);
			while (m51.find()) {
				String name51 = m51.group(1);// 获取第一个括号里的内容,也就是HB_
				String value51 = m51.group(2);// 获取第二个括号里的内容,也就是-;
				String regex51 = name51 + "\\(\\)";// 组合HB_()的正则表达式
				script = script.replaceAll(regex51, value51);
			}

		}
		// 6.无参数 返回常量 函数 中间无混淆代码
		/*
		 * 类似于此结构的 function do_() { return ''; } 需要将do_()替换为
		 */
		String regex6 = "function\\s*(\\w+)\\(\\)\\s*\\{\\s*" + "return\\s*[\'\"]([^\'\"]*)[\'\"];\\s*" + "\\}\\s*";
		Pattern p6 = Pattern.compile(regex6);
		Matcher m6 = p6.matcher(script);
		// 找出所有匹配的字符串,存入List中
		List<String> l6 = new ArrayList<String>();
		while (m6.find()) {
			l6.add(m6.group());
		}
		for (String str6 : l6) {
			Pattern p61 = Pattern.compile(regex6);
			Matcher m61 = p61.matcher(str6);
			while (m61.find()) {
				String name61 = m61.group(1);
				String value6 = m61.group(2);
				String regex61 = name61 + "\\(\\)";
				script = script.replaceAll(regex61, value6);
			}
		}

		// System.out.println(script);
		// 7.字符串拼接时使无参常量函数
		/*
		 * 类似于下面这种结构 (function() { 'return rv_'; return '%' })() 将上面这个替换为%
		 */

		String regex7 = "\\(function\\(\\)\\s*\\{\\s*" + "[\'\"]return[^\'\"]+[\'\"];\\s*"
				+ "return\\s*([\'\"][^\'\"]*[\'\"]);?\\s*" + "\\}\\)\\(\\)";
		Pattern p7 = Pattern.compile(regex7);
		Matcher m7 = p7.matcher(script);
		// 找出所有匹配的字符串,存入List中
		List<String> l7 = new ArrayList<String>();
		while (m7.find()) {
			l7.add(m7.group());

		}
		// 遍历每一个匹配的字符串,并替换为实际的值
		for (String str7 : l7) {
			Pattern p71 = Pattern.compile(regex7);
			Matcher m71 = p71.matcher(str7);
			while (m71.find()) {
				String value7 = m71.group(1);
				script = script.replace(str7, value7);

			}
		}

		// 8.字符串拼接时使用返回参数的函数
		/*
		 * 类似于以下这种结构 (function(eR__) { 'return eR_'; return eR__; })('%9')
		 * 将整体替换为%9
		 */

		String regex8 = "\\(function\\(\\w+\\)\\s*\\{\\s*" + "[\'\"]return[^\'\"]+[\'\"];\\s*" + "return\\s*\\w+;\\s*"
				+ "\\}\\)\\(([\'\"][^\'\"]*[\'\"])\\)";
		Pattern p8 = Pattern.compile(regex8);
		Matcher m8 = p8.matcher(script);
		// 找出所有匹配的字符串,存入List中
		List<String> l8 = new ArrayList<String>();
		while (m8.find()) {
			l8.add(m8.group());

		}
		// 遍历每一个匹配的字符串,并替换为实际的值
		for (String str8 : l8) {
			Pattern p81 = Pattern.compile(regex8);
			Matcher m81 = p81.matcher(str8);
			while (m81.find()) {
				String value8 = m81.group(1);
				script = script.replace(str8, value8);

			}
		}

		// .获取所有var pz_='8'格式的变量
		Pattern p = Pattern.compile("var \\w+=\'.*?\'");
		Matcher m = p.matcher(script);
		while (m.find()) {
			if (m.group().contains("<")) {
				continue;
			}
			// System.out.println(m.group());
			String varName = m.group().split(" ")[1].split("=")[0];
			String varValue = m.group().split(" ")[1].split("=")[1].replaceAll("'", "");
			script = script.replaceAll(varName, varValue);
		}

		// 将js中所有的空格,+,',都去掉
		script = script.replaceAll("[\\s+']", "");
		// ((?:%\w\w|[A-Za-z\d])+)
		Pattern p10 = Pattern.compile("((?:%\\w\\w)+)");
		Matcher m10 = p10.matcher(script);
		String[] words = {};
		while (m10.find()) {
			//System.out.println(m10.group());
			// 将在js端decodeURIComponent编码的字符串,用下面这种方法解码.
			String result = new java.net.URI(m10.group()).getPath();
			words = result.split("");
			// System.out.println(Arrays.toString(words));
		}
		// 从 字符串密集区域后面开始寻找索引区域,连续匹配十次以上,确保是索引
		Pattern p11 = Pattern.compile("([\\d,]+(;[\\d,]+)+){10,}");
		Matcher m11 = p11.matcher(script);
		String[] indexs = {};
		while (m11.find()) {
			indexs = m11.group().split("[;,]");
		}

		//System.out.println(Arrays.toString(indexs));

		// js中的混淆解决完成
		// #########################################################################
		// 开始替换<span>标签
		// 获取<span>里class属性的数字
		Pattern p12 = Pattern.compile("<span\\s*class=[\'\"]hs_kw(\\d+)_([^\'\"]+)[\'\"]></span>");
		Matcher m12 = p12.matcher(articleHtml);
		while (m12.find()) {
			// 将String类型的数字转为int类型
			int num = Integer.parseInt(m12.group(1));
			// 以class属性中的数字为下标,寻找对应的索引号.
			String index = indexs[num];
			// 将String类型的索引号转为int类型
			int indexWord = Integer.parseInt(index);
			// 获取每个<span>对应的文字
			String word = words[indexWord];
			articleHtml = articleHtml.replace(m12.group(), word);
		}
		Document artcileDoc = Jsoup.parse(articleHtml);
		article = artcileDoc.text().replaceAll(" ", "");// 获取文字,去空格

		return article;
	}

	// 测试方法
	public static void main(String[] args) throws IOException, URISyntaxException {
		DecodeAutoHome d = new DecodeAutoHome();
		String url = "http://club.autohome.com.cn/bbs/thread-c-3980-65944945-1.html";
		String article = d.getArticle(url);
		System.out.println(article);
	}
}



输出结果

昌河Q35带你来到浙南最美时尚体育小镇――泰顺百丈   “我爱我的家乡,我爱这片土地,
所以我眼中总是饱含着热泪”。――,今天我就开着昌河Q35带诸位看官老爷逛逛我的家乡――泰顺百丈。
当然,作为一个业余的车评人,我会告诉大家昌河Q35最专业的用车感受。昌河Q35有个姐姐叫绅宝X35,
样子都长的不错,可惜今天姐姐不在,先给大家介绍一下妹妹。当你上一辆车的时候,最直观的感受就是加速,
如果说发动机是一辆车的心脏,那变速箱无疑就是它的灵魂。昌河Q35全系搭载均为一台型号为A151的1.5L自然吸
气发动机,最大功率为116马力(85千瓦),峰值扭矩为148牛·米,传动系统匹配的是5挡手动变速箱和4速手自一体
变速箱,此车型搭载的是4速手自一体变速箱。昌河Q35的动力系统与绅宝X35完全相同。Q35的定位非常年轻活泼,
但是年轻往往是冲劲有余,后劲不足,所以0到40的加速有点猛,到80中规中矩,到100,我只能呵呵了。当然,
在高速上Q35还是很容易的就可以让你吃到罚单的,一段高速跑下来,感觉这个车好开,容易上手,悬挂不硬,转
向不神经质,只要你缓缓加油,变速箱也都能领会,不过给你过多的顿挫感,但是,我真的要说但是,没有一点驾
驶乐趣。比较适合居家过日子,狂野你就别指望了。都说去西藏一定要自驾,因为风景都在路上,来到泰顺何尝不
是,随手一拍,这郁郁葱葱的美丽公路都可以给你做屏保。再说,泰顺也称之为温州的青藏高原。在西藏,是眼睛的
天堂身体的地狱,而在泰顺,也是如此,因为这里的新鲜空气和氧离子含量会让吸惯了雾霾和尾气的你很不适应,甚
至醉倒。泰顺沿线路边都有农家乐,味道正宗价格便宜,最重要的是,健康。过了这条隧道,再有15分钟车程,就能到
达我们今天行程目的地―百丈,为什么此刻我有心跳声,因为那里有我刻骨铭心的梦想,
因为不曾完成。跑山路的时候我就后悔了,为什么我不选一台手动的呢,老司机都会懂的哦转眼间,
已经到这个浙南最美小镇百丈了,满眼的湖光山色,蓝天白云,厌倦了城市喧嚣的我,只想在此一屋二人一日三餐,逍遥余生
。传说宋朝初年从飞云湖下游的平阳坑滩脚上溯至该地需要经过99滩。清朝泰顺《分疆录》一书则记载“百丈谣”曰“百丈百滩,
一滩一丈,迢迢罗阳,如在天上”,于是“百丈”之名。建镇于1935年,是当地唯一的自然镇,自古就是水运埠头,建国前后曾
是浙、闽两省七县的物资吞吐口岸,是有名的百年商埠,被称为“小上海”,辉煌一时。后为解决500万温州群众的饮水问题,
这里就成了大水缸,浙江省第二大人工湖。历经十年沉寂后,而今迎来全新发展,变身为时尚体育小镇,成为赛艇运动员与游客的天堂。
百丈入口处,时尚体育小镇的主题与Q35时尚动感不谋而合。昌河和百丈的发展是非常相似的,有历史、有故事、有变革,有许多分
分合合,历经沉浮后颠覆了过往,都以新貌出现在世人前面,是希望还是瓶颈,且笑看风云。百丈是名副其实的红色历史文化古镇。
1935年11月至1937年1月,中共南坑洋区委、寿泰线苏维埃政府就在百丈镇黄坑地区建立以夏明君为主席的南坑洋苏维埃政府。那段历
史值得铭记。这里是百丈镇新建的红色文化主题公园。在《战狼2》燃爆的八月,红色的确非常给力。缓缓驶入小镇,运动气息扑面而
来。Q35的整体造型颠覆了我对昌河的一贯认知,就像这里曾经是深山密林,而今一条条彩道可以让你骑行健身,也可以漫步湖边。在
这里,道路即赛道。百丈的客运码头,可以乘船游湖。训练中的运动员,谁曾想过,原来那个人比狗少的荒寂小镇,因为众多国家赛
艇运动员的入驻,又变得生机勃勃了。湖下是曾经的老百丈,现在来自全国的青年赛艇运动员正在水面上为荣誉挥洒着汗水。青山绿
水中皮划艇训练,美醉了吧2017全国青年赛艇锦标赛在百丈镇的直升机停机坪举行。赛事剪影赛艇的下水点矫健的运动员在搬运比赛
用的赛艇。我在水中劈波斩浪,也欲与天公试比高长期的户外训练被晒的黝黑的皮肤勾勒出的线条让我很是羡慕,我在健身房这么久
都没练出来。颁奖仪式现场拿了奖牌的运动员笑的很开心,一分汗水一分回报,当然还是天赋最重要。除了赛艇,百丈镇还有曲棍球
训练基地。百丈的第一家海鲜铺,食材新鲜,以往百丈当地人买菜都是靠外面车子拉进来的,很多时候没菜吃了也要等上两三天,现在
好了在家门口就可以买到新鲜的食材了。而且,最关键的是,老板娘好漂亮,人也很好,大家下次去的时候可以关顾一下,在此我就不
发老板娘美图了,留点神秘感等你。看完赛事,还有正事,那就自己动手洗车,然后奉上车辆细节。加了透镜的车灯凌厉有神。大面积
的雾灯符合SUV的定位,看上去很狂野。轮毂的造型很运动喷了红色卡钳,当然装饰的作用大些。尾灯与北汽绅宝X35的差异化较为明显。
这个屁股看上去还是很耐操的,就是菊花小了点,很容易夹手。这里做了个悬浮式腰线的设计,我觉得是提升了车辆的档次感。接缝处
做工有所进步,但还有待提高。全系标配的行李架是个加分项,如果没有行李架,这车完全就不是这感觉了。避震,底盘悬挂偏软,但
韧性和路面的反馈还是不错的。车辆的底盘还算工整,此处应当有掌声,我可是趴在60度的路面上拍的。后背箱的容积和工整度都不错
。第二排座椅可以按比例放倒,日常使用足够了。后备箱内有隔层,可以放些杂物。来张大鹏展翅,我们一起看内饰吧。昌河Q35与绅宝
X35的内饰,除了车标外,其他几乎没什么分别,整体布局配色都很不错,但是塑料感偏强,当然这个价位的车子你不可能要求它有奔驰
的做工。空调出风口的造型是不错,但使用起来总觉得还是常规的方便门板上的仿皮设计还是能营造出一些豪华感的。从这个角度看,
如果不是中间那个很LOW的显示屏,我都觉得自己是在开一辆跑车。方向盘的手感还是不错的,转向力度也轻,适合女生驾驶。配备车身
稳定控制系统。这个地方我真的要好好批评一下了,为什么做的一点阻尼都没有,松松垮垮,根本找不到感觉。Q35这四速变速箱是来自
爱信的吗?平路开开还可以,但是山路爬坡,我真宁可手动,而且档位的清晰度真不好,挂倒挡容易挂到空挡去,挂D挡又容易挂到2挡去,
可能是我最近健身练得力气变大了,有点控制不住吧。又一装逼神器,真越野的话,我怕会被我掰断。被架子和放硬币的,不能搁太高的杯
子,不然刹车时候容易甩出来。扶手箱空间不算大,刚好让我放个钱包和眼镜盒,不过,主要是我的钱包比较大装逼到牙齿,看到这个我
好想给你加个T。座椅是真心舒服,软硬适中。腰部头部臀部支撑都很到位。关键部位还打孔。后排的中间也是有头枕的,而且这种造型的
头枕不会让脖子受累。这个后排空间不能说宽敞,但也不至于局促,符合紧凑定位。后配配有儿童安全锁的。前大灯前灯夜间照明效果还
算不错,但是与近光灯交集的地方会有重影,看着不爽。尾灯历经几天的纠结与修改终于把这篇帖子写完了,因为在写这篇帖子的时候,我
做了十年来最重要的决定,我要辞职了,回到百丈去创业。十年中,我无数次的问自己:“十年的体制内生活,你厌倦吗?”我不厌倦,是憎
恨,辞职是这十年来最让我向往也是最开心兴奋的决定了,我的前半生是在无数的文件、报告和会议中度过的,而后半生,我想换一种活法了。


注意 :汽车之家还在不断的更新,所以代码是有时效性的.



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值