巧妙绕开跨域机制,使用JS在浏览器部署验证码识别爬虫
巧妙绕开跨域机制,使用JS在浏览器部署验证码识别爬虫_未来博客-CSDN博客_js爬虫跨域
关键词:跨域,JS,JavaScript,爬虫,验证码,图像识别
遇到的问题
可能有小伙伴知道,构建一个网页,编写JS爬虫识别验证码很难实现,这是因为,要使用JS识别验证码,需要先把验证码的图片绘制到Canvas中,再使用训练好的识别库去遍历像素及进行操作,但因为你的网页和验证码的域不同,无论用哪种方法,浏览器都会报各种各样的跨域警告:
获取验证码图片
绘制到本地网页的Canvas
画布跨域污染
跨域报错
为何要用网页搭载JS构建爬虫?
浏览器的JS似乎不擅长用于图像识别,那为何还要这样做?
浏览器设计网页很方便,代码量少。
无需编写成可执行文件,电脑安装了浏览器即可运行、公司内网限制自己安装软件。
无需安装特殊环境(Python、Java)
便于传播
使用浏览器的JS来构建爬虫看来确实是个糟糕的注意,但当前是最佳解决方案。
运行效果
思路
既然在本地网页加载跨域图片会导致报错,就不在本地网页加载。
目标网页是别人的服务器发送给我的,而我没有目标服务器的管理权限。
目标网页是在本地浏览器运行的。
我可以植入JS到本地网页达到操控效果?
向远程服务器发送请求
服务器将网页代码发送给我
浏览器解析网页代码,呈现
通过浏览器开发者界面向网页植入自己写的JS程序
启动爬虫,加载验证码图片
使用loadJS向已经加载完毕的网页添加JS代码
于是我百度了一下,找了一个可以向已经加载完毕的网页添加JS代码的JS代码,非常简单,直接在开发者工具中插入
function loadJs(url,callback){
var script=document.createElement('script');
script.type="text/javascript";
if(typeof(callback)!="undefined"){
if(script.readyState){
script.onreadystatechange=function(){
if(script.readyState == "loaded" || script.readyState == "complete"){
script.onreadystatechange=null;
callback();
}
}
}else{
script.onload=function(){
callback();
}
}
}
script.src=url;
document.body.appendChild(script);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
首先我尝试加载本地代码:
loadJs('C:\Users\Antonio\test.js');
1
浏览器报错:不能加载本地代码,可以理解,安全限制,也就是说这个代码必须从远程服务器加载,只要是http或者https协议的就行,我想了一下,别人的代码都可以用cdn(内容分发网络)加载,为啥我的不行,于是我去腾讯云开了一个对象存储服务,把我的JS放到里面,直接加载该链接即可:
loadJs('https://tools-*********.cos.ap-guangzhou.myqcloud.com/main.js');
1
于是浏览器就帮我把爬虫程序加载到网页里面去了:
爬虫主体(main.js)
基本流程
之所以要用网页部署爬虫,是因为网页的界面比较容易设计,在目标网页植入JS之后,我们需要对目标网页进行更改来显示我们的操作界面,然后再进行操作:
爬虫加载完毕,开始执行部署过程
删除目标网页的body,加载自己的body
删除目标网页的样式表和链接,加载自己的样式表
根据需要,删除多余的JS,添加自己的JS
目标网页变成了我们的界面,只有URL是原来的URL
用户交互与执行爬虫任务
删除网页body及样式表等,加载样式表、加载JS
//删除body
document.body.innerHTML='';
//删除css
var styles = document.getElementsByTagName('style');
for(var i = styles.length - 1; i >= 0; i--) {
document.head.removeChild(styles[i]);
}
//删除link
var links = document.getElementsByTagName('link');
for(var i = links.length - 1; i >= 0; i--) {
document.head.removeChild(links[i]);
}
//加载js
loadJs('https://unpkg.com/tesseract.js@2.0.0/dist/tesseract.min.js');
loadJs('https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js');
function heredoc(fn) {
return fn.toString().split('\n').slice(1,-1).join('\n') + '\n'
}
//添加style
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML= heredoc(function(){/*
body h1 {
font-family: Cambria, 'Hoefler Text', 'Liberation Serif', Times, 'Times New Roman', 'serif';
color: aliceblue;
}
body h2 {
font-family: Cambria, 'Hoefler Text', 'Liberation Serif', Times, 'Times New Roman', 'serif';
color: aliceblue;
}
body h7 {
font-family: Cambria, 'Hoefler Text', 'Liberation Serif', Times, 'Times New Roman', 'serif';
}
body h4 {
font-family: Cambria, 'Hoefler Text', 'Liberation Serif', Times, 'Times New Roman', 'serif';
}
*/});
document.head.appendChild(style);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
构建网页主体
//构建网页
document.title = 'coded by AntoniotheFuture';
document.body.innerHTML= heredoc(function(){/*
<body style="width: 100%;height: 100%;padding: 0px;margin: 0px;text-align: center;">
<div id="head" style="width: 100%;background-color: dodgerblue;height: 100px;margin-top: 0px;">
<div id="titlediv" style="text-align: left;width: 90%;max-width:1000px;margin-left: auto;margin-right: auto;">
<h1 style="margin-top: 0px;">Demo</h1>
<h2>爬虫</h2>
</div>
</div>
<div id="main" style="width: 90%;max-width:1000px;border: dodgerblue solid 1px;margin: auto;height:auto;min-height: 700px">
<div style="width: 100%;display: inline-block">
<div id="leftpan" style="width: 100%;float: left">
<div id="options" style="width: 100%;margin-top: 10px;text-align: left;padding-left: 2%;">
<label >设置间隔(毫秒、防止被检测)</label>
<input type="text" style="" title="PasteFrom" id='splittime' value="500">
<label>验证码识别重试次数</label>
<input type="text" style="" id='retrytime' title="PasteFrom" value="3">
<br>
<label>查询方式:</label>
<input type="checkbox" name="types" id='ualificano' value="ualificano"/> 号码
<input type="checkbox" name="types" id='practicecode' value="practicecode" /> 编号
<input type="checkbox" name="types" id='name' value="name" checked="checked" /> 姓名
<input type="checkbox" name="types" id='cardno' value="cardno" checked="checked" /> 身份证后四位
</div>
<hr>
<div id="Res-notice" style="text-align: left;width: 100%;padding-left: 2%;">
<label style="text-align: left;" >在这里粘贴要查询的数据(可直接复制Excel中数据)</label>
</div>
<div style="width: 100%;text-align: center;margin-top: 5px;position:relative">
<div id="sourcetext" contenteditable style="width:auto;min-width: 96%; overflow-x: auto; margin-left: 2%; text-align:left;overflow-y:scroll;min-height: 300px;background-color: transparent;color:darkcyan;border: solid 1px rgba(0,0,0,0.50);;visibility:visible;height: 300px;"></div>
</div>
<div id="Res-notice" style="text-align: left;width: 100%;padding-left: 2%;">
<label style="text-align: left;" >日志</label>
</div>
<div style="width: 100%;text-align: center;margin-top: 5px;position:relative;display: block;">
<div id="logtext" contenteditable style="width:auto;min-width: 96%; overflow-x: auto; margin-left: 2%; text-align:left;overflow-y:scroll;min-height: 150px;background-color: transparent;color:darkcyan;border: solid 1px rgba(0,0,0,0.50);;visibility:visible;height: 150px;"></div>
</div>
<div id="Buttons2" style="width: 100%;margin-top: 10px;text-align: left;padding-left: 2%;">
<input onClick="test();" type="button" style="font-size: 16px;margin: 0px;background-color: aqua;" title="PasteFrom" value="测试">
<input onClick="start();" type="button" style="font-size: 16px;margin: 0px;background-color: aqua;" title="PasteFrom" value="开始执行">
<input onClick="stop();" type="button" style="font-size: 16px;background-color: lightsalmon;" title="PasteFrom" value="停止执行">
<input onClick="ClearInput();" type="button" style="font-size: 16px;" title="PasteFrom" value="清空输入框">
<input onClick="copyresult();" id="ShowFI" type="button" style="font-size: 16px;visibility:visible" title="PasteFrom" value="复制结果">
<input onClick="showlog();" id="ShowFI" type="button" style="font-size: 16px;visibility:visible" title="PasteFrom" value="显示/隐藏日志框">
</div>
<hr>
<div id='status' style="width: 100%;text-align: center;margin-top: 5px;position:relative;display: block;">
这里用于显示验证码图片和识别结果/运行状态
</div>
<div id="Res-notice" style="text-align: left;width: 100%;padding-left: 2%;">
<label style="text-align: left;" >查询结果</label>
</div>
<div style="width: 100%;text-align: center;margin-top: 5px;position:relative">
<div id="resulttext" contenteditable style="font-size: 10px;width:auto;min-width: 96%; overflow-x: auto; margin-left: 2%; text-align:left;overflow-y:scroll;min-height: 300px;background-color: transparent;color:darkcyan;border: solid 1px rgba(0,0,0,0.50);;visibility:visible;height: 300px;"></div>
</div>
<div id="Buttons" style="width: 100%;margin-top: 10px;text-align: left;padding-left: 2%;">
</div>
</div>
</div>
</div>
<hr style="margin-top: 100px;">
<div id="Bottom">
<div style="text-align: left;width: 60%;max-width:1000px;margin-left: auto;margin-right: auto;">
</div>
</div>
</body>
*/});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
为了避免麻烦,我已经隐去了部分信息
我习惯定义公共变量和公共函数
//定义常量
var captchaURL = 'http://somesite/captchacn.svl';
var queryURL = 'http://somesite/do.do';
var defaultst = 500;
var defaultrt = 3;
var OKColor = 'lightgreen';
var ErrColor = 'lightcoral';
var ShowFunctionInfoHint = "FunctionInfo 显示函数信息";
var NotShowFunctionInfoHint = "NoFunctionInfo 不显示函数信息";
var ShowFunctionInfo = true;
var source = []; //元数据
var logmsg = ''; //日志信息
var result = ''; //结果信息
var splittime = 500; //间隔时间
var retrytime = 3;//重试时间
var status = '';//状态信息
var types = [];
var running = false;
//获取参数
function getAttrs(){
var sourcetext = document.getElementById('sourcetext').innerText;
source=sourcetext.split(/[(\r\n)\r\n]+/);
source.forEach((item,index)=>{
if(!item){
source.splice(index,1);//删除空项
}
});
splittime = document.getElementById('splittime').value;
retrytime = document.getElementById('retrytime').value;
if(!isInteger(splittime)){
splittime = defaultst;
}
if(splittime < 0){
splittime = defaultst;
}
if(!isInteger(retrytime)){
retrytime = defaultrt;
}
if(retrytime < 0){
retrytime = defaultrt;
}
types.length = 0;
//types.splice(0,types.length);
var typesoption = document.getElementsByName("types");
for (var i = 0; i < typesoption.length; ++i) {
if(typesoption[i].checked) {
types.push(typesoption[i].value);
}
}
}
//检查参数
function checkAttrs(){
if(splittime == ''){splittime = defaultst}
if(retrytime == ''){retrytime = defaultrt}
if(!source){return '请输入要查询的数据'}
if(types.length < 2){return '至少需要两个查询方式'}
return true;
}
//将信息显示出来
function syncmsg(){
document.getElementById('logtext').innerHTML = logmsg;
document.getElementById('status').innerHTML = status;
document.getElementById('resulttext').innerHTML = result;
}
//判断整型
function isInteger(obj) {
return obj%1 === 0
}
//清空输入框
function ClearInput(){
document.getElementById('sourcetext').innerHTML = '';
}
//清空状态
function chearstatus(){
var el = document.getElementById('status');
var childs = el.childNodes;
for(var i = childs.length - 1; i >= 0; i--) {
el.removeChild(childs[i]);
}
}
//测试图片与识别
function test(){
//获取图片
chearstatus();
var img = new Image();
img.src = captchaURL + '?v=' + Math.random();
img.onload = function(){
document.getElementById('status').appendChild(img);
Tesseract.recognize(img, 'eng')
.then(function(result){
alert(result.text);
});
}
}
//清空日志
function clearlog(){
var el = document.getElementById('logtext');
var childs = el.childNodes;
for(var i = childs.length - 1; i >= 0; i--) {
el.removeChild(childs[i]);
}
}
function addlog(content){
document.getElementById('logtext').appendChild(content);
}
//构建状态语
function showstatus(total,success,trytime,hit,totaltime){
var t = '进度:' + success + '/' + total + ' ' + Number(success/total*100).toFixed() + '%' + '<br>' +
'识别成功率' + Number(hit/trytime*100).toFixed() + '%' + '<br>' +
'总用时:' + totaltime + '秒;平均用时:' + Number(totaltime/total).toFixed();
document.getElementById('status').innerHTML = t;
}
//判断验证码是否符合要求
function checkcap(cap){
if(cap.length != 4){
return false;
}
for (var i in cap) {
var asc = cap.charCodeAt(i);
if (!(asc >= 48 && asc <= 57 || asc >= 65 && asc <= 90 || asc >= 97 && asc <= 122)) {
return false;
}
}
return true;
}
//ajax同步
function fetch(url,querydata) {
const p = new Promise((resolve, reject) => {
$.ajax(url, {
dataType: 'json',
processData: false,
contentType: false,
timeout: 5000,
type : "post",
data: querydata,
dataType : "json",
success: function (data) {
resolve(data);
},
error: function () {
reject(new Error('返回错误'))
}
})
})
return p
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
为了避免麻烦,我已经隐去了部分信息
爬虫主体
基本流程如下:
用户输入要批量查询的信息
加载参数、清空结果、日志区域
目标信息存储为数组、遍历、构建POST/GET请求
获取验证码图片并加载到Canvas进行识别,识别结果加入请求参数
发送请求获得回传、查看是否通过
显示结果到界面
重复三次
yes
no
async function start(){
//构建参数
getAttrs();
checkresult = checkAttrs();
if(checkresult != true){
alert(checkresult);
}
clearlog();
var total = source.length;
var success = 0;
var trytime = 0;
var hit = 0;
var img = new Image();
var querydata = {};
var ishit = false;
var tryc = 0;
var i;
var ii;
var r;
var cap;
var cans = document.createElement("canvas");
cans.style.backgroundColor = "#808080";
var ctrx = cans.getContext('2d');
var t1 = (new Date()).valueOf()/1000;
var worker = new Tesseract.createWorker();
for(i = 0;i<types.length;i++){
querydata[types[i]] = '';
}
querydata['captcha'] = '';
result = '';
for(i = 0;i < total;i++){
var t2 = (new Date()).valueOf()/1000;
showstatus(total,success,trytime,hit,t2 - t1);
s = source[i].split('\t');
var fdata = new FormData();
for(ii = 0;ii < s.length;ii++){
fdata.append(Object.keys(querydata)[ii],s[ii]);
querydata[Object.keys(querydata)[ii]] = s[ii];
}
ishit = false;
tryc = 0;
do {
//todo:加入延时
trytime ++;
tryc ++;
img.src = captchaURL + '?v=' + new Date();
var promise = new Promise((reslove)=>{
img.onload = async function(){
cans.width = img.width;
cans.height = img.height;
ctrx.drawImage(img,0,0);//,img.width+20,img.height+20
//var logimg = new Image();
var newcans = document.createElement("canvas");
newcans.width = img.width;
newcans.height = img.height;
newcans.getContext('2d').drawImage(img,0,0);//,img.width,img.height
addlog(newcans);
await worker.load();
await worker.loadLanguage('eng');
await worker.initialize('eng');
var checkc = false;
var nospace = '';
var cc = 0;
do {
const { data } = await worker.recognize(cans);
//去除空格
nospace = data.text.replace(/\s+/g,"");
checkc = checkcap(nospace);
cc++;
}
while(!checkc && cc < 5);
querydata['captcha'] = nospace.substring(0,4);
fdata.append('captcha',querydata['captcha']);
console.log(JSON.stringify(querydata));
var ele = document.createElement('a');
ele.innerHTML = querydata['captcha'];
addlog(ele);
addlog(document.createElement('br'));
var queryresult = await fetch(queryURL + "?checkcaptch&time="+new Date(),fdata);
if (queryresult.result === "succeed") {
console.log('识别成功');
hit ++;
ishit = true;
if(queryresult.dataGrid.total >= 1){
for(r = 0;r < queryresult.dataGrid.total; r++){
result += source[i] + ',' + JSON.stringify(queryresult.dataGrid.rows[r]) + '<br>';
}
}else{
result += source[i] + ',无信息<br>';
}
} else{
console.log('识别失败');
}
reslove();
}
})
await promise
}
while(!ishit && tryc < retrytime);
if(!ishit){
result += source[i] + ',查询失败<br>';
}
success ++;
document.getElementById('resulttext').innerHTML = result;
}
t2 = (new Date()).valueOf()/1000;
showstatus(total,success,trytime,hit,t2 - t1);
alert('执行完毕');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
完整代码
完整的代码已在上面列出,如需完整文件可到GIthub下载:
github
已知问题
主程序所有异步请求改成了同步,会对性能和效率有一定影响。
使用的第三方库:tesseract.js 似乎不适合用于识别验证码,识别率比较低(20%~50%成功率),如果你发现了更好用的库,请告诉我!!!
也是第三方库的问题,每次部署完成后都需要加载一个有点大的训练包,外国资源,网络不好的需要等待比较长时间:
学艺不精,见笑了
————————————————
版权声明:本文为CSDN博主「Antonio·Future」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_15041159/article/details/104994247
关键词:跨域,JS,JavaScript,爬虫,验证码,图像识别
遇到的问题
可能有小伙伴知道,构建一个网页,编写JS爬虫识别验证码很难实现,这是因为,要使用JS识别验证码,需要先把验证码的图片绘制到Canvas中,再使用训练好的识别库去遍历像素及进行操作,但因为你的网页和验证码的域不同,无论用哪种方法,浏览器都会报各种各样的跨域警告:
获取验证码图片
绘制到本地网页的Canvas
画布跨域污染
跨域报错
为何要用网页搭载JS构建爬虫?
浏览器的JS似乎不擅长用于图像识别,那为何还要这样做?
浏览器设计网页很方便,代码量少。
无需编写成可执行文件,电脑安装了浏览器即可运行、公司内网限制自己安装软件。
无需安装特殊环境(Python、Java)
便于传播
使用浏览器的JS来构建爬虫看来确实是个糟糕的注意,但当前是最佳解决方案。
运行效果
思路
既然在本地网页加载跨域图片会导致报错,就不在本地网页加载。
目标网页是别人的服务器发送给我的,而我没有目标服务器的管理权限。
目标网页是在本地浏览器运行的。
我可以植入JS到本地网页达到操控效果?
向远程服务器发送请求
服务器将网页代码发送给我
浏览器解析网页代码,呈现
通过浏览器开发者界面向网页植入自己写的JS程序
启动爬虫,加载验证码图片
使用loadJS向已经加载完毕的网页添加JS代码
于是我百度了一下,找了一个可以向已经加载完毕的网页添加JS代码的JS代码,非常简单,直接在开发者工具中插入
function loadJs(url,callback){
var script=document.createElement('script');
script.type="text/javascript";
if(typeof(callback)!="undefined"){
if(script.readyState){
script.onreadystatechange=function(){
if(script.readyState == "loaded" || script.readyState == "complete"){
script.onreadystatechange=null;
callback();
}
}
}else{
script.onload=function(){
callback();
}
}
}
script.src=url;
document.body.appendChild(script);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
首先我尝试加载本地代码:
loadJs('C:\Users\Antonio\test.js');
1
浏览器报错:不能加载本地代码,可以理解,安全限制,也就是说这个代码必须从远程服务器加载,只要是http或者https协议的就行,我想了一下,别人的代码都可以用cdn(内容分发网络)加载,为啥我的不行,于是我去腾讯云开了一个对象存储服务,把我的JS放到里面,直接加载该链接即可:
loadJs('https://tools-*********.cos.ap-guangzhou.myqcloud.com/main.js');
1
于是浏览器就帮我把爬虫程序加载到网页里面去了:
爬虫主体(main.js)
基本流程
之所以要用网页部署爬虫,是因为网页的界面比较容易设计,在目标网页植入JS之后,我们需要对目标网页进行更改来显示我们的操作界面,然后再进行操作:
爬虫加载完毕,开始执行部署过程
删除目标网页的body,加载自己的body
删除目标网页的样式表和链接,加载自己的样式表
根据需要,删除多余的JS,添加自己的JS
目标网页变成了我们的界面,只有URL是原来的URL
用户交互与执行爬虫任务
删除网页body及样式表等,加载样式表、加载JS
//删除body
document.body.innerHTML='';
//删除css
var styles = document.getElementsByTagName('style');
for(var i = styles.length - 1; i >= 0; i--) {
document.head.removeChild(styles[i]);
}
//删除link
var links = document.getElementsByTagName('link');
for(var i = links.length - 1; i >= 0; i--) {
document.head.removeChild(links[i]);
}
//加载js
loadJs('https://unpkg.com/tesseract.js@2.0.0/dist/tesseract.min.js');
loadJs('https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js');
function heredoc(fn) {
return fn.toString().split('\n').slice(1,-1).join('\n') + '\n'
}
//添加style
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML= heredoc(function(){/*
body h1 {
font-family: Cambria, 'Hoefler Text', 'Liberation Serif', Times, 'Times New Roman', 'serif';
color: aliceblue;
}
body h2 {
font-family: Cambria, 'Hoefler Text', 'Liberation Serif', Times, 'Times New Roman', 'serif';
color: aliceblue;
}
body h7 {
font-family: Cambria, 'Hoefler Text', 'Liberation Serif', Times, 'Times New Roman', 'serif';
}
body h4 {
font-family: Cambria, 'Hoefler Text', 'Liberation Serif', Times, 'Times New Roman', 'serif';
}
*/});
document.head.appendChild(style);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
构建网页主体
//构建网页
document.title = 'coded by AntoniotheFuture';
document.body.innerHTML= heredoc(function(){/*
<body style="width: 100%;height: 100%;padding: 0px;margin: 0px;text-align: center;">
<div id="head" style="width: 100%;background-color: dodgerblue;height: 100px;margin-top: 0px;">
<div id="titlediv" style="text-align: left;width: 90%;max-width:1000px;margin-left: auto;margin-right: auto;">
<h1 style="margin-top: 0px;">Demo</h1>
<h2>爬虫</h2>
</div>
</div>
<div id="main" style="width: 90%;max-width:1000px;border: dodgerblue solid 1px;margin: auto;height:auto;min-height: 700px">
<div style="width: 100%;display: inline-block">
<div id="leftpan" style="width: 100%;float: left">
<div id="options" style="width: 100%;margin-top: 10px;text-align: left;padding-left: 2%;">
<label >设置间隔(毫秒、防止被检测)</label>
<input type="text" style="" title="PasteFrom" id='splittime' value="500">
<label>验证码识别重试次数</label>
<input type="text" style="" id='retrytime' title="PasteFrom" value="3">
<br>
<label>查询方式:</label>
<input type="checkbox" name="types" id='ualificano' value="ualificano"/> 号码
<input type="checkbox" name="types" id='practicecode' value="practicecode" /> 编号
<input type="checkbox" name="types" id='name' value="name" checked="checked" /> 姓名
<input type="checkbox" name="types" id='cardno' value="cardno" checked="checked" /> 身份证后四位
</div>
<hr>
<div id="Res-notice" style="text-align: left;width: 100%;padding-left: 2%;">
<label style="text-align: left;" >在这里粘贴要查询的数据(可直接复制Excel中数据)</label>
</div>
<div style="width: 100%;text-align: center;margin-top: 5px;position:relative">
<div id="sourcetext" contenteditable style="width:auto;min-width: 96%; overflow-x: auto; margin-left: 2%; text-align:left;overflow-y:scroll;min-height: 300px;background-color: transparent;color:darkcyan;border: solid 1px rgba(0,0,0,0.50);;visibility:visible;height: 300px;"></div>
</div>
<div id="Res-notice" style="text-align: left;width: 100%;padding-left: 2%;">
<label style="text-align: left;" >日志</label>
</div>
<div style="width: 100%;text-align: center;margin-top: 5px;position:relative;display: block;">
<div id="logtext" contenteditable style="width:auto;min-width: 96%; overflow-x: auto; margin-left: 2%; text-align:left;overflow-y:scroll;min-height: 150px;background-color: transparent;color:darkcyan;border: solid 1px rgba(0,0,0,0.50);;visibility:visible;height: 150px;"></div>
</div>
<div id="Buttons2" style="width: 100%;margin-top: 10px;text-align: left;padding-left: 2%;">
<input onClick="test();" type="button" style="font-size: 16px;margin: 0px;background-color: aqua;" title="PasteFrom" value="测试">
<input onClick="start();" type="button" style="font-size: 16px;margin: 0px;background-color: aqua;" title="PasteFrom" value="开始执行">
<input onClick="stop();" type="button" style="font-size: 16px;background-color: lightsalmon;" title="PasteFrom" value="停止执行">
<input onClick="ClearInput();" type="button" style="font-size: 16px;" title="PasteFrom" value="清空输入框">
<input onClick="copyresult();" id="ShowFI" type="button" style="font-size: 16px;visibility:visible" title="PasteFrom" value="复制结果">
<input onClick="showlog();" id="ShowFI" type="button" style="font-size: 16px;visibility:visible" title="PasteFrom" value="显示/隐藏日志框">
</div>
<hr>
<div id='status' style="width: 100%;text-align: center;margin-top: 5px;position:relative;display: block;">
这里用于显示验证码图片和识别结果/运行状态
</div>
<div id="Res-notice" style="text-align: left;width: 100%;padding-left: 2%;">
<label style="text-align: left;" >查询结果</label>
</div>
<div style="width: 100%;text-align: center;margin-top: 5px;position:relative">
<div id="resulttext" contenteditable style="font-size: 10px;width:auto;min-width: 96%; overflow-x: auto; margin-left: 2%; text-align:left;overflow-y:scroll;min-height: 300px;background-color: transparent;color:darkcyan;border: solid 1px rgba(0,0,0,0.50);;visibility:visible;height: 300px;"></div>
</div>
<div id="Buttons" style="width: 100%;margin-top: 10px;text-align: left;padding-left: 2%;">
</div>
</div>
</div>
</div>
<hr style="margin-top: 100px;">
<div id="Bottom">
<div style="text-align: left;width: 60%;max-width:1000px;margin-left: auto;margin-right: auto;">
</div>
</div>
</body>
*/});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
为了避免麻烦,我已经隐去了部分信息
我习惯定义公共变量和公共函数
//定义常量
var captchaURL = 'http://somesite/captchacn.svl';
var queryURL = 'http://somesite/do.do';
var defaultst = 500;
var defaultrt = 3;
var OKColor = 'lightgreen';
var ErrColor = 'lightcoral';
var ShowFunctionInfoHint = "FunctionInfo 显示函数信息";
var NotShowFunctionInfoHint = "NoFunctionInfo 不显示函数信息";
var ShowFunctionInfo = true;
var source = []; //元数据
var logmsg = ''; //日志信息
var result = ''; //结果信息
var splittime = 500; //间隔时间
var retrytime = 3;//重试时间
var status = '';//状态信息
var types = [];
var running = false;
//获取参数
function getAttrs(){
var sourcetext = document.getElementById('sourcetext').innerText;
source=sourcetext.split(/[(\r\n)\r\n]+/);
source.forEach((item,index)=>{
if(!item){
source.splice(index,1);//删除空项
}
});
splittime = document.getElementById('splittime').value;
retrytime = document.getElementById('retrytime').value;
if(!isInteger(splittime)){
splittime = defaultst;
}
if(splittime < 0){
splittime = defaultst;
}
if(!isInteger(retrytime)){
retrytime = defaultrt;
}
if(retrytime < 0){
retrytime = defaultrt;
}
types.length = 0;
//types.splice(0,types.length);
var typesoption = document.getElementsByName("types");
for (var i = 0; i < typesoption.length; ++i) {
if(typesoption[i].checked) {
types.push(typesoption[i].value);
}
}
}
//检查参数
function checkAttrs(){
if(splittime == ''){splittime = defaultst}
if(retrytime == ''){retrytime = defaultrt}
if(!source){return '请输入要查询的数据'}
if(types.length < 2){return '至少需要两个查询方式'}
return true;
}
//将信息显示出来
function syncmsg(){
document.getElementById('logtext').innerHTML = logmsg;
document.getElementById('status').innerHTML = status;
document.getElementById('resulttext').innerHTML = result;
}
//判断整型
function isInteger(obj) {
return obj%1 === 0
}
//清空输入框
function ClearInput(){
document.getElementById('sourcetext').innerHTML = '';
}
//清空状态
function chearstatus(){
var el = document.getElementById('status');
var childs = el.childNodes;
for(var i = childs.length - 1; i >= 0; i--) {
el.removeChild(childs[i]);
}
}
//测试图片与识别
function test(){
//获取图片
chearstatus();
var img = new Image();
img.src = captchaURL + '?v=' + Math.random();
img.onload = function(){
document.getElementById('status').appendChild(img);
Tesseract.recognize(img, 'eng')
.then(function(result){
alert(result.text);
});
}
}
//清空日志
function clearlog(){
var el = document.getElementById('logtext');
var childs = el.childNodes;
for(var i = childs.length - 1; i >= 0; i--) {
el.removeChild(childs[i]);
}
}
function addlog(content){
document.getElementById('logtext').appendChild(content);
}
//构建状态语
function showstatus(total,success,trytime,hit,totaltime){
var t = '进度:' + success + '/' + total + ' ' + Number(success/total*100).toFixed() + '%' + '<br>' +
'识别成功率' + Number(hit/trytime*100).toFixed() + '%' + '<br>' +
'总用时:' + totaltime + '秒;平均用时:' + Number(totaltime/total).toFixed();
document.getElementById('status').innerHTML = t;
}
//判断验证码是否符合要求
function checkcap(cap){
if(cap.length != 4){
return false;
}
for (var i in cap) {
var asc = cap.charCodeAt(i);
if (!(asc >= 48 && asc <= 57 || asc >= 65 && asc <= 90 || asc >= 97 && asc <= 122)) {
return false;
}
}
return true;
}
//ajax同步
function fetch(url,querydata) {
const p = new Promise((resolve, reject) => {
$.ajax(url, {
dataType: 'json',
processData: false,
contentType: false,
timeout: 5000,
type : "post",
data: querydata,
dataType : "json",
success: function (data) {
resolve(data);
},
error: function () {
reject(new Error('返回错误'))
}
})
})
return p
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
为了避免麻烦,我已经隐去了部分信息
爬虫主体
基本流程如下:
用户输入要批量查询的信息
加载参数、清空结果、日志区域
目标信息存储为数组、遍历、构建POST/GET请求
获取验证码图片并加载到Canvas进行识别,识别结果加入请求参数
发送请求获得回传、查看是否通过
显示结果到界面
重复三次
yes
no
async function start(){
//构建参数
getAttrs();
checkresult = checkAttrs();
if(checkresult != true){
alert(checkresult);
}
clearlog();
var total = source.length;
var success = 0;
var trytime = 0;
var hit = 0;
var img = new Image();
var querydata = {};
var ishit = false;
var tryc = 0;
var i;
var ii;
var r;
var cap;
var cans = document.createElement("canvas");
cans.style.backgroundColor = "#808080";
var ctrx = cans.getContext('2d');
var t1 = (new Date()).valueOf()/1000;
var worker = new Tesseract.createWorker();
for(i = 0;i<types.length;i++){
querydata[types[i]] = '';
}
querydata['captcha'] = '';
result = '';
for(i = 0;i < total;i++){
var t2 = (new Date()).valueOf()/1000;
showstatus(total,success,trytime,hit,t2 - t1);
s = source[i].split('\t');
var fdata = new FormData();
for(ii = 0;ii < s.length;ii++){
fdata.append(Object.keys(querydata)[ii],s[ii]);
querydata[Object.keys(querydata)[ii]] = s[ii];
}
ishit = false;
tryc = 0;
do {
//todo:加入延时
trytime ++;
tryc ++;
img.src = captchaURL + '?v=' + new Date();
var promise = new Promise((reslove)=>{
img.onload = async function(){
cans.width = img.width;
cans.height = img.height;
ctrx.drawImage(img,0,0);//,img.width+20,img.height+20
//var logimg = new Image();
var newcans = document.createElement("canvas");
newcans.width = img.width;
newcans.height = img.height;
newcans.getContext('2d').drawImage(img,0,0);//,img.width,img.height
addlog(newcans);
await worker.load();
await worker.loadLanguage('eng');
await worker.initialize('eng');
var checkc = false;
var nospace = '';
var cc = 0;
do {
const { data } = await worker.recognize(cans);
//去除空格
nospace = data.text.replace(/\s+/g,"");
checkc = checkcap(nospace);
cc++;
}
while(!checkc && cc < 5);
querydata['captcha'] = nospace.substring(0,4);
fdata.append('captcha',querydata['captcha']);
console.log(JSON.stringify(querydata));
var ele = document.createElement('a');
ele.innerHTML = querydata['captcha'];
addlog(ele);
addlog(document.createElement('br'));
var queryresult = await fetch(queryURL + "?checkcaptch&time="+new Date(),fdata);
if (queryresult.result === "succeed") {
console.log('识别成功');
hit ++;
ishit = true;
if(queryresult.dataGrid.total >= 1){
for(r = 0;r < queryresult.dataGrid.total; r++){
result += source[i] + ',' + JSON.stringify(queryresult.dataGrid.rows[r]) + '<br>';
}
}else{
result += source[i] + ',无信息<br>';
}
} else{
console.log('识别失败');
}
reslove();
}
})
await promise
}
while(!ishit && tryc < retrytime);
if(!ishit){
result += source[i] + ',查询失败<br>';
}
success ++;
document.getElementById('resulttext').innerHTML = result;
}
t2 = (new Date()).valueOf()/1000;
showstatus(total,success,trytime,hit,t2 - t1);
alert('执行完毕');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
完整代码
完整的代码已在上面列出,如需完整文件可到GIthub下载:
github
已知问题
主程序所有异步请求改成了同步,会对性能和效率有一定影响。
使用的第三方库:tesseract.js 似乎不适合用于识别验证码,识别率比较低(20%~50%成功率),如果你发现了更好用的库,请告诉我!!!
也是第三方库的问题,每次部署完成后都需要加载一个有点大的训练包,外国资源,网络不好的需要等待比较长时间:
学艺不精,见笑了
————————————————
版权声明:本文为CSDN博主「Antonio·Future」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_15041159/article/details/104994247