最近朋友有个新需求,就是做一个发票校验的爬虫,由于这个网站有一些不是很友好的反爬,导致对新手的非常不友好~~~所以周六花了点时间康康了。
难度还行,通过分析是sojson的企业版本,有可能为最新版本的v6,也有可能是v5然后加了个webdriver的检测,因为需要收费才能用v6的加强版反无头浏览器了。所以不管他了,黑猫白猫都是
新增测试接口。解析国税总局全类型发票数据,包括最新的增值税电子专用发票
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-43uYZyYx-1594828779186)(http://gouzai.pw/images/货物运输业增值税专用发票.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0eTuiwGv-1594828779188)(http://gouzai.pw/images/货物运输业增值税专用发票_官网.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wX971aoB-1594828779199)(http://gouzai.pw/images/增值税电子专用发票.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WvHGFa5M-1594828779200)(http://gouzai.pw/images/增值税电子专用发票_官网.png)]
测试接口:
请求地址 | Content-Type | 参数形式 | 请求方法 |
---|---|---|---|
http://106.53.31.110:8080/check?fpdm=3100171320&fphm=79262007&date=20170620&code=184553&channel=yd | url | JSON | GET |
生产接口:
请求地址 | Content-Type | 参数形式 | 请求方法 |
---|---|---|---|
http://localhost:8080/check?fpdm=3100171320&fphm=79262007&date=20170620&code=184553&channel=yd | url | JSON | GET |
具体参数:
参数名 | 必选 | 类型 | 说明 |
---|---|---|---|
fpdm | Yes | String | 发票代码 |
fphm | Yes | String | 发票号码 |
date | Yes | String | 发票时间 |
code | Yes | String | 开具金额或者校验六位。不知道就不填,服务器会返回提示之后再根据填写。 |
channel | Yes | String | 你猜,不填就拉闸 |
返回结果:
参数名 | 类型 | 说明 |
---|---|---|
message | String | 结果提示 |
code | String | 0为成功处理 |
time | String | 请求所花费的时间(毫秒) |
data | String | 解析的数据 |
info | String | 原始数据 |
数据字段中文意思:::请联系本人QQ1095085167,避免外泄。花了三四天重新解析的东西。。。
Java用例:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @Description
* @auther Gouzai
* @create 2020-07-12 22:04
*/
public class demo {
public static void main(String[] args) throws IOException {
String url = "http://106.53.31.110:8080/check?fpdm=3100171320&fphm=79262007&date=20170620&code=184553&channel=yd";
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
InputStream inputStream = null;
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK){
inputStream = conn.getInputStream();
} else {
inputStream = conn.getErrorStream();
}
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuffer sb = new StringBuffer();
String line = null;
while((line = bufferedReader.readLine())!= null){
sb.append(line);
}
String result = sb.toString();
System.err.println(result);
com.alibaba.fastjson.JSONObject json = com.alibaba.fastjson.JSONObject.parseObject(result);
System.err.println(json);
}
}
一开始就给了个见面礼。其实还行,过这个debugger的文章已经多到不能再多了,这里主要是分析流程所以略。。。
过了debugger的检测之后就干干净净舒舒服服,妈妈也在不用担心我调试的时候被无情的打断了。
虽然sojson = ob混淆 + 自己写的代码
一般来说,ob混淆是不带debugger的,看了一下跟sojson贼像,那就是他了。看了没有那个sojson的广告,也就可以确定是定制版本了
四五月份学了点js的AST处理,然后简单那处理一下。
然后用Charles替换上去即可,charles 的食用方法我之前有写过。。。因为我是扣那些代码我就反混淆那些,处理sojson都已经是轻车熟路了,直接嘀嘀嘀了。。。
然后刷新一下,重复一遍过debuger的方法,然后在网络上下个断点,就能发现整个流程了。
在这里加ajaxSetup,其实这个也是我在反推流程的时候无意发现的,然后就喜提url签名。。。
RSA 在js实现的公共库只有JSEncrypt和基于JSEncrypt二次开发的nodejs版本,暂未找到其他的比较有标准化的RSA。但是他们依赖于浏览器内部crypto或者就是nodejs实现的crypto。暂未找到一个未依赖系统的特定库的RSA实现,在Java的内置js引擎是无法正常运行的。所以走到这一步都是通过拿到这个值让Java实现Rsa加密。
package cn.gov.chinatax.utils;
import sun.misc.BASE64Decoder;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* @Description
* @auther Gouzai
* @create 2020-06-05 18:44
*/
public class RSA {
public static String encryp(String str,String key) {
try {
X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(new BASE64Decoder().decodeBuffer(key));
// RSA对称加密算法
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// 取公钥匙对象
PublicKey publicKey = keyFactory.generatePublic(bobPubKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(str.getBytes());
return Base64.getEncoder().encodeToString(bytes);
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
}
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCXY6ndiMJE7wF0qg9emVQik7FnCBidCr8V+yG/++iN/CwV0Rfe81wnjg2I23nbLJVuT63Y1T4x2etNr58BTHuzrCRy8gj3HPaS0GSGuiN7EWI1s0Bg6N78nvStPxeinyD8Qh3Bqa+5Z014nbOqn20kW4d3efLAeI7A6yc2uMPvfwIDAQAB
验证码这里采用的是鱼导的定制识别方法,下面有测试接口。不过为了方便测试,所有每天会有上限为500次的识别机会,已经足够测试了。一张发票一天就只能查询五次。。。
由于验证码识别率超过98%,基本没有见到因为由于验证码错误导致的查询失败。
这里有个是fplx(发票类型),在全局搜索一下即可,跟获取服务器配置的js一样,在同一个文件中
先签名
在组织拼接代码然后传进去
临时存放起来
动态生成拼接处理的js代码
拿到初始化数据
解析数据
设置到文本中
显示,完事了
到这里就整个流程是已经走完了。简称一条龙服务。。。
测试的两个分票类型:第一个是没有详情清单的(喜鹊楼茶餐厅)。第二个有详情清单(沃尔玛)
政府的程序员都那么无聊吗???全用数组,不打算更新了吗?。。。更新一个字段得全部流程都要改。。。
参数名称的命名方式竟然是中文拼音的第一个,比如pflx(发票类型),fpdm(发票代码),fphm(发票号码)。。。