看来是时候拿出我们压箱底多年的老干妈了,哦不,老干货了。不吓屎你们这群小学生我就不在6年级混了。
先贴一个logo让大家跪拜一下
好了,大家平身吧,咱们马上就正式开始了,想上厕所的赶紧去,不然看完这篇文章估计你就忘了怎么上厕所了。
正式开始之前,先插个广告:如果土豪朋友不想写代码或者中途看不下去的,我们将以下代码已经打包成一个完整的应用,大家进入神箭手的云市场搜索百度指数(http://www.shenjianshou.cn/index.php?r=market/product&product_id=500036)就可以看到应用,直接调用既可。
果然百度老司机不会让我们那么开心的。没事没事,不就是登录吗,也不是没做过登录,抓包研究下请求应该不难。我们先找一个账号登录看下。登录之后继续输入神箭手:
出来了。哈哈,不难嘛,这不就直接显示了。然后就按照以前的爬虫的教程,用XPATH获取一下数字就可以了,哈哈哈…哈哈..哈……..
什么?这是图!!!!什么?这还是拼图!!!!什么?这货居然是异步的拼图!!!!
怎么样,感受到天坑的深度没有?
开始具体的代码之前,我们先在神箭手后台新建三个应用,分别是百度指数API,百度登录爬虫,百度指数图片识别AI。
第一章 登录应用
第一节:咱先搞定登录
这个不错,逻辑清晰,代码干净,万能的github果然不辜负我的重望。我们steal到神箭手平台上来。
好了,这中间很麻烦的两个地方是
var codeUrl = "https://passport.baidu.com/cgi-bin/genimage?" + codeString;
var codeReg = getCaptcha(71, codeUrl);
var imgCaptchaData = JSON.parse(codeReg);
if (imgCaptchaData && imgCaptchaData.ret > 0) {
var result = imgCaptchaData.result;
verifycode = encodeURI(result,"UTF-8");
tt = (new Date()).getTime();
var codeCheck = site.requestUrl("https://passport.baidu.com/v2/?checkvcode&token=" + token + "&tpl=mn&apiver=v3&tt=" + tt + "&verifycode=" + verifycode + "&codestring=" + codeString + "&callback=");
var checkInfo = JSON.parse(codeCheck);
if (checkInfo.errInfo.no != "0") {
console.log("验证码识别错误");
}
continue;
} else {
console.log("验证码识别失败");
continue;
}
2. RSA加密
var pubkeyJson = site.requestUrl("https://passport.baidu.com/v2/getpublickey?token=" + token + "&tpl=mn&apiver=v3&tt=" + tt + "&gid=" + gid + "&callback=");
var pubkeyInfo = JSON.parse(pubkeyJson.replace(/'/g, "\""));
var pubkey = pubkeyInfo.pubkey;
var rsakey = pubkeyInfo.key;
var crypttype = "";
var rsaPassword="";
if (rsakey != "") {
crypttype = "12";
//加密密码
pubkey = pubkey.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").trim();
rsaPassword = (RSAEncode(password, pubkey));
}
第二节:疯狂登录
var configs = {
shareUserWithKey:"__bindex__",
};
configs.onUserAdded = function (use, psw, site) {
var loginResult = login(use, psw, site);
if( loginResult !="success") {
return false;
}
return true;
}
site.addUser(user, password);
通过这种形式我们就可以建立一个可共享的Cookie池。然后我们在百度指数API应用(下一章会详细介绍)里通过设置以下代码来共享这个Cookie池:
var configs = {
shareUserWithKey:"__bindex__",
multiUser: true
}
第三节:问题来了,帐号从哪来呢?
第二章:获取指数图片API
第一节:异步请求数据
貌似不难找,不过看这个URL看着就头大,感觉已经被百度登录伤害过一次之后真的无力再一个一个参数分析,我们直接使用神箭手提供的js渲染页面的接口,直接把页面渲染出来把:
var configs = {
domains: ["index.baidu.com"],
scanUrls: ["http://index.baidu.com/?tpl=trend&word=" + encodeURI(keyword, "GBK")],
enableJS:true
}
第二节:渲染数据成图片
<span class="imgval" style="width:6px;">
<div class="imgtxt" style="margin-left:-8px;"></div>
</span>
<span class="imgval" style="width:18px;">
<div class="imgtxt" style="margin-left:-62px;"></div>
</span>
我们可以看到这里有两个imgval标签,imgval是用来当蒙版的,可以从一张背景图中抠出需要的部分,而imgtxt则是显示图片的,这里又有一个margin-left用来具体调整整张图要显示的位置。
而最最最变态的是,两个标签并不是两个数字,而是三个数字!这就说明我们不可能一个一个数字去识别,必须作为一个整体图片来识别了。
我们知道PhantomJS这类Headless的浏览器都有渲染Html代码成图片的功能,神箭手渲染JS基于PhantomJS当然也支持这个功能,而且我们的调用接口更简单只需要调用site.renderImage方法既可实现将代码渲染成图片的功能,
下面是结合第一节的完整代码如下:
var sevenReg = /class="mtable profWagv">(.+?)<\/table>/;
var sevenMatch = sevenReg.exec(indexInfo);
if(sevenMatch) {
var sevenInfo = extractList(sevenMatch[1],"//*[@class='ftlwhf enc2imgVal']");
var styleReg = /(<style>.+?<\/style>)/;
var styleMatch = styleReg.exec(sevenMatch[1]);
var sevenHtml = '<html><head><base href="http://index.baidu.com/" /></head><body style="background:#fff;"><style>.imgval .imgtxt {display: block;width: 19200px;height: 25px;margin-top: -2px;} .imgval {display: inline-block;height: 12px;margin-bottom: -2px;overflow: hidden;}</style><div class="profWagv">'
if(styleMatch) {
var html = sevenHtml + sevenInfo[0].substring(0,sevenInfo[0].length-7) +styleMatch[1] + "</div></body></html>";
var base64 = site.renderImage(html, 120, 25);
index.week_all_index = base64ToImg(base64,site);
var html2 = sevenHtml + sevenInfo[1].substring(0,sevenInfo[1].length-7) +styleMatch[1] + "</div></body></html>";
var base641 = site.renderImage(html2, 120, 25);
index.week_mobile_index = base64ToImg(base641,site);
}
} else {
system.exit("关键字不存在");
}
这里只展示一下对于7天指数的渲染,30天的几乎一样。可以看到代码中添加了一些定义的Css这是为了渲染这段代码时能够补上网页中本来相关的Css,而不至于显示不正常,我们看一下渲染出来的图片的base64。
iVBORw0KGgoAAAANSUhEUgAAAHgAAAAaCAYAAAB8WJiDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAxklEQVRoge3aWwqDMBBG4Vq6vcxis8HxqVAtmYxtNPhzvjevFA5JVLq4uz8g6zn7B+BcBBZHYHEEFkdgcQQWR2BxBBb3yp5oZpvtWmvznOhYdD1O4AmllO6+z+3oWLQP4w2Zos1sMyJrrV8jFnOkAjOd3ld6DX6L1tkWRvQ8hwLvp+Ij9tcR/BrpNfifuJgnFZi4N5Z51M680kSvSb/cD2Ms7v1/dLTWy9a62vvQwWxwnVRg3BffosURWByBxRFYHIHFEVjcCuhz388wTpcGAAAAAElFTkSuQmCC
把base64转换成图片之后如下图,没毛病。
第三章:识别指数图片
显然这张渲染出来的图片并不比验证码更复杂,那么干脆我们用验证码识别吧。不过我要提醒同学们,神箭手的验证码识别是要收费的哦。当然啦,土豪无所谓。
对于非土豪的同学继续往下看,这张图片看着并不复杂,甚至简单到我们可以切割后直接对比文件数据的方式来去判断图片,当然这太low了,而且代码写起来其实也不太轻松。有什么其他更有逼格的手段呢?
神箭手作为一个重视逼格胜过重视功能的平台,当然不会只给你这个方案。现在是祭出神箭手最新黑科技-TensorFlow的时候了。不过话说回来了,用TensorFlow来识别这张图片,有点大炮打蚊子的意思。不过本着只选难的,不选烦的工程师本性。
我宁愿写TensorFlow,也不想写像素点对比。当然最重要的是因为我们自己在研发的验证码识别的代码可以直接拿来改改用,那今天就拿出来跟大家共享一下。
第二节:编写机器学习代码
我们打开百度指数图片识别AI这个应用,之前接触过神箭手的同学们肯定对神箭手云爬虫应用不陌生,但是对于这个神箭手AI应用(也就是神箭手封装的TensorFlow)肯定是一头雾水。
首先与神箭手爬虫代码不一样,TensorFlow是Python的(来,咱们再学一个新语言)。但类似于神箭手爬虫,我们依然可以通过自定义输入项来定义输入参数(请求的时候,需要定义一个输入项来接受图片的base64编码)。
后面我们会特别开神箭手TensorFlow的课程(你们猜对了,老师还在学),这一次咱们先直接上代码:
content="" #@input(content,请求数据)
from PIL import Image
import tensorflow as tf
import numpy as np
import random
import shenjian as sj
import base64
from io import BytesIO
import re
import json
import urllib.request as client
number = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z']
ALPHABET = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
PADING = [' ']
def convert2gray(img):
if len(img.shape) > 2:
gray = np.mean(img, -1)
return gray
else:
return img
def load_data(file_name):
file = open(file_name)
content = file.read()
content = content.replace("\n","")
imgs = re.findall(r'content.*?\}', content)
result = []
for img in imgs:
img_x = re.findall(r'content:.*?result:', img)
img_y = re.findall(r'result:.*?\}', img)
img_x = img_x[0].replace("content:", "").replace("result:", "")
img_y = json.loads(img_y[0].replace("result:", ""))
if 'result' in img_y:
if len(img_y['result']) == 4 and re.search(r'[0-9|a-z|A-Z]+$', img_y['result']):
result.append((img_x, img_y['result']))
return result
url = 'http://demo.shenjianshou.cn/tensor/baidu/'
def request():
content = client.urlopen(url=url).read()
content = content.decode('utf-8')
data = json.loads(content)
data['value'] = str(data['value'])
size = len(data['value'])
for i in range(size, 10):
data['value'] += ' '
return data['content'], data['value']
class Model(object):
def __init__(self, text_set=number+PADING, captcha_size=10, width=120, height=26):
self.text_set = text_set
self.captcha_size = captcha_size
self.width = width
self.height = height
self.captcha_len = len(text_set)
self.X = tf.placeholder(tf.float32, [None, self.width*self.height])
self.Y = tf.placeholder(tf.float32, [None, self.captcha_size*self.captcha_len])
self.keep_prob = tf.placeholder(tf.float32)
self.x = tf.reshape(self.X, shape=[-1, self.height, self.width, 1])
self.w_alpha = 0.01
self.b_alpha = 0.1
#定义三层卷积层
self.w_c1 = tf.Variable(self.w_alpha * tf.random_normal([3, 3, 1, 32]))
self.b_c1 = tf.Variable(self.b_alpha * tf.random_normal([32]))
self.conv1_a = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(self.x, self.w_c1, strides=[1, 1, 1, 1], padding='SAME'), self.b_c1))
self.conv1_b = tf.nn.max_pool(self.conv1_a, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
self.conv1 = tf.nn.dropout(self.conv1_b, self.keep_prob)
self.w_c2 = tf.Variable(self.w_alpha*tf.random_normal([3, 3, 32, 64]))
self.b_c2 = tf.Variable(self.b_alpha*tf.random_normal([64]))
self.conv2_a = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(self.conv1, self.w_c2, strides=[1, 1, 1, 1], padding='SAME'), self.b_c2))
self.conv2_b = tf.nn.max_pool(self.conv2_a, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
self.conv2 = tf.nn.dropout(self.conv2_b, self.keep_prob)
self.w_c3 = tf.Variable(self.w_alpha*tf.random_normal([3, 3, 64, 64]))
self.b_c3 = tf.Variable(self.b_alpha*tf.random_normal([64]))
self.conv3_a = tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(self.conv2, self.w_c3, strides=[1, 1, 1, 1], padding='SAME'), self.b_c3))
self.conv3_b = tf.nn.max_pool(self.conv3_a, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
self.conv3 = tf.nn.dropout(self.conv3_b, self.keep_prob)
#全连接层
self.w_d = tf.Variable(self.w_alpha*tf.random_normal([15*4*64, 1024]))
self.b_d = tf.Variable(self.b_alpha*tf.random_normal([1024]))
self.dense = tf.reshape(self.conv3, [-1, self.w_d.get_shape().as_list()[0]])
self.dense = tf.nn.relu(tf.add(tf.matmul(self.dense, self.w_d), self.b_d))
self.dense = tf.nn.dropout(self.dense, self.keep_prob)
self.w_out = tf.Variable(self.w_alpha*tf.random_normal([1024, self.captcha_size*self.captcha_len]))
self.b_out = tf.Variable(self.b_alpha*tf.random_normal([self.captcha_size*self.captcha_len]))
self.out = tf.add(tf.matmul(self.dense, self.w_out), self.b_out)
self.loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=self.Y, logits=self.out))
self.optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(self.loss)
self.predict = tf.reshape(self.out, [-1, self.captcha_size, self.captcha_len])
self.max_idx_p = tf.argmax(self.predict, 2)
self.max_idx_l = tf.argmax(tf.reshape(self.Y, [-1, self.captcha_size, self.captcha_len]), 2)
self.correct_pred = tf.equal(self.max_idx_p, self.max_idx_l)
self.accuracy = tf.reduce_mean(tf.cast(self.correct_pred, tf.float32))
#self.data = load_data("data")
#self.test = load_data("test")
self.data_index = 0
self.test_index = 0
self.session = sj.Session(auto_init=True)
def random_captcha_text(self):
captcha_text = []
for i in range(self.captcha_size):
c = random.choice(self.text_set)
captcha_text.append(c)
return captcha_text
def gen_captcha_text_and_image(self):
img_x, img_y = request()
imgdata = base64.b64decode(img_x)
img_x = Image.open(BytesIO(imgdata))
if img_x.width != self.width or img_x.height != self.height:
img_x = img_x.resize((self.width, self.height), Image.ANTIALIAS)
img_x = img_x.point(lambda x: 255 if x > 125 else 0).convert('RGB')
img_x = np.array(img_x)
self.data_index += 1
return img_y, img_x
def gen_captcha_text_and_image_test(self):
img_x, img_y = request()
imgdata = base64.b64decode(img_x)
img_x = Image.open(BytesIO(imgdata))
if img_x.width != self.width or img_x.height != self.height:
img_x = img_x.resize((self.width, self.height), Image.ANTIALIAS)
img_x = img_x.point(lambda x: 255 if x > 125 else 0).convert('RGB')
img_x = np.array(img_x)
self.test_index += 1
return img_y, img_x
def text2vec(self, text):
text_len = len(text)
if text_len != self.captcha_size:
raise ValueError("验证码长度不匹配")
vector = np.zeros(self.captcha_len * self.captcha_size)
def char2pos(c):
if c == ' ':
k = 10
return k
k = ord(c)-48
if k > 9:
k = ord(c)-55
if k > 35:
k = ord(c) - 61
if k > 61:
raise ValueError('No Map '+c)
return k
for i, c in enumerate(text):
idx = i*self.captcha_len+char2pos(c)
vector[idx] = 1
return vector
def vec2text(self, vec):
char_pos = vec.nonzero()[0]
text = []
for i, c in enumerate(char_pos):
char_idx = c % self.captcha_len
if char_idx < 10:
char_code = char_idx + ord('0')
elif char_idx == 10:
char_code = ord(' ')
elif char_idx < 36:
char_code = char_idx - 10 + ord('A')
elif char_idx < 62:
char_code = char_idx - 36 + ord('a')
elif char_idx == 62:
char_code = ord('_')
else:
raise ValueError('error')
text.append(chr(char_code))
return "".join(text)
def get_next_batch(self, batch_size=128):
batch_x = np.zeros([batch_size, self.width*self.height])
batch_y = np.zeros([batch_size, self.captcha_len * self.captcha_size])
for i in range(batch_size):
text, image = self.gen_captcha_text_and_image()
image = convert2gray(image)
batch_x[i, :] = image.flatten() / 255
batch_y[i, :] = self.text2vec(text)
return batch_x, batch_y
def get_next_batch_test(self, batch_size=128):
batch_x = np.zeros([batch_size, self.width*self.height])
batch_y = np.zeros([batch_size, self.captcha_len * self.captcha_size])
for i in range(batch_size):
text, image = self.gen_captcha_text_and_image_test()
image = convert2gray(image)
batch_x[i, :] = image.flatten() / 255
batch_y[i, :] = self.text2vec(text)
return batch_x, batch_y
def train(self):
step = 0
for i in range(10000):
batch_x, batch_y = self.get_next_batch_test(64)
_, loss_ = self.session.run([self.optimizer, self.loss], feed_dict={self.X: batch_x, self.Y: batch_y, self.keep_prob: 0.75})
step += 1
print(step, loss_)
if step % 10 == 0:
batch_x_test, batch_y_test = self.get_next_batch(100)
acc = self.session.run(self.accuracy, feed_dict={self.X: batch_x_test, self.Y: batch_y_test, self.keep_prob: 1.0})
print(acc)
def serve(self):
img_data = base64.b64decode(content)
img_x = Image.open(BytesIO(img_data))
if img_x.width != self.width or img_x.height != self.height:
img_x = img_x.resize((self.width, self.height), Image.ANTIALIAS)
img_x = img_x.point(lambda x: 255 if x > 125 else 0).convert('RGB')
img_x = np.array(img_x)
img_x = convert2gray(img_x)
img_x = img_x.flatten() / 255
predict = tf.argmax(tf.reshape(self.out, [-1, self.captcha_size, self.captcha_len]), 2)
text_list = self.session.run(predict, feed_dict={self.X: [img_x], self.keep_prob: 1})
text = text_list[0].tolist()
vector = np.zeros(self.captcha_size*self.captcha_len)
print(text)
i = 0
for n in text:
vector[i*self.captcha_len + n] = 1
i += 1
result = self.vec2text(vector)
print(result)
return str(result)
m = Model()
sj.run(m.train, m.serve)
简单说一下这段代码,这段代码是我们从验证码识别的代码中临时改过来的,有很多的不必要的代码,比如大小写字符这里是不需要的,我们暂且忽略。
思路是一致,咱们通过一个三层神经网络,输入一张图和一个One-hot Encoding之后的识别结果,因为我们只需要识别数字(直接忽略逗号),因此这段代码相对比较简单。像是百度登录的验证码会出现中文的情况就会复杂不少。
sj.run(m.train, m.serve)
第三节:生成训练样本
有没有听过这样一句话:人工智能就是有多少人工就有多少智能。特别是在深度学习中,虽然我们的训练代码可以逐步做到通用,但我们需要大量的数据来训练才能让训练模型达到更高准确度。
那问题又来了,数据怎么来呢? 难不成我们自己去截图标注吗,那岂不是要累死自己的节奏吗?作为善于偷懒的程序员,我们当然不能这么干,因为百度指数的数字很标准,并没有什么变化,我们把10个数字+逗号截图出来,动态生成图片以及对应的真实数字既可,我们看一下生成样本的PHP代码:
<?php
class Verify
{
protected $config = array(
'expire' => 1800, // 验证码过期时间(s)
'useImgBg' => false, // 使用背景图片
'fontSize' => 25, // 验证码字体大小(px)
'imageH' => 160, // 验证码图片高度
'imageW' => 60, // 验证码图片宽度
'length' => 5, // 验证码位数
'fontttf' => '', // 验证码字体,不设置随机获取
'bg' => array(255, 255, 255), // 背景颜色
'reset' => true, // 验证成功后是否重置
);
private $imageW = 120;
private $imageH = 26;
private $_image = null; // 验证码图片实例
/**
* 架构方法 设置参数
* @access public
* @param array $config 配置参数
*/
public function __construct($config = array())
{
$this->config = array_merge($this->config, $config);
}
/**
* 使用 $this->name 获取配置
* @access public
* @param string $name 配置名称
* @return multitype 配置值
*/
public function __get($name)
{
return $this->config[$name];
}
/**
* 设置验证码配置
* @access public
* @param string $name 配置名称
* @param string $value 配置值
* @return void
*/
public function __set($name, $value)
{
if (isset($this->config[$name])) {
$this->config[$name] = $value;
}
}
/**
* 检查配置
* @access public
* @param string $name 配置名称
* @return bool
*/
public function __isset($name)
{
return isset($this->config[$name]);
}
/**
* 输出验证码并把验证码的值保存的session中
* 验证码保存到session的格式为: array('verify_code' => '验证码值', 'verify_time' => '验证码创建时间');
* @access public
* @return string
*/
public function entry()
{
// 建立一幅 $this->imageW x $this->imageH 的图像
$this->_image = imagecreatetruecolor($this->imageW, $this->imageH);
// 设置背景
//imagecolorallocate($this->_image, $this->bg[0], $this->bg[1], $this->bg[2]);
$background_color=imagecolorallocate($this->_image,255,255,255);
imagefill($this->_image,0,0,$background_color);
$digits_number = rand(1,9);
$max = pow(10, $digits_number);
$min = $max/10;
$max = $max-1;
$number = rand($min, $max);
//$number = 10415;
$code = number_format($number); // 验证码
//$code = "$";
$len = strlen($code);
for($i=0;$i<$len;$i++){
$char = $code[$i];
if($char == ","){
$char = "comma";
}else if($char == "$"){
$image = imagecreatefrompng("template.png");
imagecopy($this->_image,$image,0,0,0,0,120,26);
continue;
}
$image = imagecreatefrompng($char.".png");
imagecopy($this->_image,$image,$i*8+8,8,0,0,8,12);
}
imagepng($this->_image,'./code.png');
$img = array(
'content' => base64_encode(file_get_contents('./code.png')),
'value' => $number
);
unlink('./code.png');
echo json_encode($img);exit;
}
}
$code = new Verify();
$code->entry();
?>
你们猜对了,这又是一个临时改过来的代码,有大量的无用代码(还有一些测试代码..)。
不过没关系,咱们就是要训练一个能用的模型,不需要在意这些细节。中间那几张图片是我事先截图的,不过因为这个Url我们线上直接公布了,所以其实大家不用写了。
第四节:训练样本生成服务API
好了,可以看到在第二节贴的训练的代码中我们已经使用了生成样本的URL,我们点击右上角的训练按钮。
通过不断训练新生成的样本,再加上百度指数图片本身的识别难度实在太低,我们很快就可以得到我们想要的准确率。训练完成之后,神箭手会自动存储这个模型并生成一个服务的接口来供我们调用这个模型:
最终章:完成百度指数API
我们返回第二章创建的百度指数API中,因为renderImage方法返回的是图片的base64字符串,而TensorFlow中我们也是读取的base64字符串,
因此我们直接调用我们TensorFlow的应用生成的服务接口,实现最终指数图片的识别:
var base64ToImg = function (base64,site) {
console.log("base64:"+base64)
var tryTimes = 3;
var result;
var data;
while(tryTimes-- > 0) {
data = site.requestUrl("http://ai.shenjianshou.cn/?appid=<你自己的TensorFlow应用id>&content="+ encodeURI(base64),{noCookies:true,noUser:true});
if(data) {
data = JSON.parse(data);
if(data && data.error_code == 0) {
return data.data;
}
}
}
system.exit("调用失败");
}
好了,小同志快醒醒,延安就要到了!
结语
感谢大家耐心看完天坑系列教程第一课,能看到这里的绝对都是真爱。所以就在啰嗦两句:首先由于本人水平所限,教程中难免可能有一些粗陋的地方,如有bug,欢迎指正。
填这个坑我们用到了验证码识别,RSA加密,Cookie池,JS渲染,HTML图片渲染,Php生成图片,TensorFlow训练。
需要用到神箭手(www.shenjianshou.cn)三种不同类型的应用相互协同。教程中的语言包括Js,Python,Php,Html,Css。大家如果中间哪里有不懂的地方,欢迎自行百度~