一、验证码识别
1.图形验证码的识别
识别图形验证码需要 tesserocr 库 OCR技术识别(光学字符识别,是指通过扫描字符,然后通过其形状将其翻译成电子文本的过程。)
例如 中国知网注册页面 http://my.cnki.net/elibregister/commonRegister.aspx
tesserocr是Python的一个OCR识别库,但其实是对tesseract做的一层Python API封装,所以它的核心是tesseract
所以在安装tesserocr之前要先安装tesseract
下载地址:
https://digi.bib.uni-mannheim.de/tesseract/
其中带dev的为开发版本 不带dev的为稳定版本 下载好后双击安装
勾选 additional language data 支持语言包
安装tesserocr
pip install tesserocr pillow
如果报错,查看pip支持版本 python命令行下
import pip import pip._internal print(pip._internal.pep425tags.get_supported())
去如下网址下载对应版本 进行安装即可
https://github.com/simonflueckiger/tesserocr-windows_build/releases
测试成功 import tesserocr 不报错表示成功
pip install pillow 测试import PIL
1.1 识别测试
下载到本地一张验证码后 更改其名字为 code.jpg 放在python代码根目录
代码如下:
#识别code.jpg 图片验证码
import tesserocr from PIL import Image image = Image.open(\'code.jpg\') #新建image对象 result = tesserocr.image_to_text(image)#调用imgae_to_text方法 传入image对象 print(result)
另外tesserocr 还有一个更加简单的方法,这个方法可以直接将图片转化为字符串
示例:
import tesserocr print(tesserocr.file_to_text(\'code.jpg\')) #不过此种方法识别效果不如上一种方法好
1.2 验证码处理
重新下载一张图片命名为code1.jpg 重新用以上代码进行测试
可以看到如果图片当中多余的线条干扰会影响图片识别的准确度
对于这种情况我们还要进行进一步的处理 例如 转灰度 二值化等。
可以利用Image对象的 convert()方法传入参数 L 即可将图片转化为灰度图像
示例:
image = image.convert(\'L\') image.show()
传入参数1 即可将图片二值化处理
image = image.convert(\'1\') image.show()
但是此种方法默认阀值是127
并且不能直接转换原图 要先将原图转为灰度图像,然后再指定二值化阀值
示例:
image = image.convert(\'L\') threshold = 80#二值化阀值 table = [] for i in range(256): if i < threshold: table.append(0) else: table.append(1) image = image.point(table,\'1\') image.show()
发现验证码中的线条已经去除 验证码黑白分明 再重新识别验证码
示例:
import tesserocr from PIL import Image image = Image.open(\'code1.jpg\') image = image.convert(\'L\') threshold = 127 table = [] for i in range(256): if i < threshold: table.append(0) else: table.append(1) image = image.point(table, \'1\') image.show() result = tesserocr.image_to_text(image) print(result)
如果针对一些有干扰的图片,我们可以选择做一些灰度和二值化处理 达到提高图片识别的正确率
2. 极验滑动验证码的识别
上面我们说可以利用 tesserocr 来识别简单的图形验证码 但是近几年出现了一些新型验证码
其中比较有代表性的就是极验验证码 它需要拖动拼合滑块才可以完成验证,相对于图形验证码来说
识别难度上升了几个等级 例如 魅族 斗鱼
确保本机安装好了selenium 浏览器为Chrome 并配置ChromeDriver
极验验证码官网 http://www.geetest.com/ 它是一个专注于提供验证安全的系统 主要验证方式是拖动滑块拼合图像
如果图像完全拼合 则验证成功
2.1 极验验证码特点
极验验证码相比图片验证码识别难度更大,对于极验3.0版本 首先要点击按钮进行智能验证,如果验证不通过,则会弹出滑动验证窗口
拖动滑动拼合图像进行验证,之后三个加密参数会生成,通过表单提交到后台,后台还会进行一次验证。
极验验证码还增加了机器学习的方法来识别拖动轨迹 官方网站的安全防护有如下几点说明:
1. 三角防护之防模拟。恶意程序模仿人类行为轨迹对验证码进行识别,针对模拟 极验验证码拥有4000万人机行为样本的海量数据
利用机器学习和神经网络构建线上线下多重静态 动态防御模型 识别模拟轨迹,界定人机边界。
2. 三角防护之防伪造。恶意程序通过伪造设备浏览器环境对验证码进行识别,针对伪造 极验验证码利用设备基因技术,深度分析
浏览器的实际性能来识别伪造信息,同时根据伪造时间不断更新黑名单,大幅度提高防伪造能力。
3. 三角防护之防暴力。 恶意程序短时间内进行密集攻击,对验证码进行暴力识别,针对暴力识别 极验验证码拥有多种验证形态,
每一种验证形态都利用神经网络生成海量图库储备,每一张图片都是独一无二的,且图库不断更新,极大程度提高了暴力识别的成本。
另外 相比普通验证方式 极验更加方便 体验更加友好:
1. 点击验证只需0.4秒
2. 全平台兼容
3. 面向未来
相比一般验证码 极验验证码的安全性和易用性有了非常大的提高。
2.2 识别思路
对于应用了极验验证码的网站,如果直接模拟表单提交 加密参数的构造是个问题 需要分析它加密和校验逻辑 相对繁琐
所以采用直接模拟浏览器动作的方式来完成验证 此验证成本相比直接去识别加密算法少很多
示例: 中国保温网
http://www.cnbaowen.net/api/geetest/
识别验证只需要完成如下三步:
1.模拟点击验证按钮
2.识别滑动缺口的位置
3.模拟拖动滑块
第一步相对简单 可以直接用selenium 模拟点击操作
第二步识别缺口位置比较关键 需要用到图像相关的处理方法 首先观察缺口的样子 缺口四周边缘有明显的断裂边缘 边缘和边缘周围有明显的区别
可以实现一个边缘检测算法找出缺口的位置。
第三步看似简单 其中的坑比较多 极验验证码增加了机器轨迹识别,匀速移动 随机速度移动等方法都不能通过验证,只有完全模拟人的移动轨迹才可以通过验证
人的移动轨迹一般是先加速后减速 需要模拟这个过程才能通过验证。
2.3 代码实现
初始化 测试链接
http://www.cnbaowen.net/api/geetest/
from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait # 等待元素加载的 from selenium.webdriver.common.action_chains import ActionChains #拖拽 from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, NoSuchElementException from selenium.webdriver.common.by import By from PIL import Image import requests import time import re import random from io import BytesIO def merge_image(image_file,location_list): """ 拼接图片 :param image_file: :param location_list: :return: """ im = Image.open(image_file) im.save(\'Code.jpg\') new_im = Image.new(\'RGB\',(260,116)) # 把无序的图片 切成52张小图片 im_list_upper = [] im_list_down = [] # print(location_list) for location in location_list: # print(location[\'y\']) if location[\'y\'] == -58: # 上半边 im_list_upper.append(im.crop((abs(location[\'x\']),58,abs(location[\'x\'])+10,116))) if location[\'y\'] == 0: # 下半边 im_list_down.append(im.crop((abs(location[\'x\']),0,abs(location[\'x\'])+10,58))) x_offset = 0 for im in im_list_upper: new_im.paste(im,(x_offset,0)) # 把小图片放到 新的空白图片上 x_offset += im.size[0] x_offset = 0 for im in im_list_down: new_im.paste(im,(x_offset,58)) x_offset += im.size[0] new_im.show() return new_im def get_image(driver,div_path): \'\'\' 下载无序的图片 然后进行拼接 获得完整的图片 :param driver: :param div_path: :return: \'\'\' time.sleep(2) background_images = driver.find_elements_by_xpath(div_path) location_list = [] for background_image in background_images: location = {} result = re.findall(\'background-image: url\("(.*?)"\); background-position: (.*?)px (.*?)px;\',background_image.get_attribute(\'style\')) # print(result) location[\'x\'] = int(result[0][1]) location[\'y\'] = int(result[0][2]) image_url = result[0][0] location_list.append(location) print(\'==================================\') image_url = image_url.replace(\'webp\',\'jpg\') # \'替换url http://static.geetest.com/pictures/gt/579066de6/579066de6.webp\' image_result = requests.get(image_url).content # with open(\'1.jpg\',\'wb\') as f: # f.write(image_result) image_file = BytesIO(image_result) # 是一张无序的图片 image = merge_image(image_file,location_list) return image def get_track(distance): \'\'\' 拿到移动轨迹,模仿人的滑动行为,先匀加速后匀减速 匀变速运动基本公式: ①v=v0+at ②s=v0t+(1/2)at² ③v²-v0²=2as :param distance: 需要移动的距离 :return: 存放每0.2秒移动的距离 \'\'\' # 初速度 v=0 # 单位时间为0.2s来统计轨迹,轨迹即0.2内的位移 t=0.2 # 位移/轨迹列表,列表内的一个元素代表0.2s的位移 tracks=[] # 当前的位移 current=0 # 到达mid值开始减速 mid=distance * 7/8 distance += 10 # 先滑过一点,最后再反着滑动回来 # a = random.randint(1,3) while current < distance: if current < mid: # 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细 a = random.randint(2,4) # 加速运动 else: a = -random.randint(3,5) # 减速运动 # 初速度 v0 = v # 0.2秒时间内的位移 s = v0*t+0.5*a*(t**2) # 当前的位置 current += s # 添加到轨迹列表 tracks.append(round(s)) # 速度已经达到v,该速度作为下次的初速度 v= v0+a*t # 反着滑动到大概准确位置 for i in range(4): tracks.append(-random.randint(2,3)) for i in range(4): tracks.append(-random.randint(1,3)) return tracks def get_distance(image1,image2): \'\'\' 拿到滑动验证码需要移动的距离 :param image1:没有缺口的图片对象 :param image2:带缺口的图片对象 :return:需要移动的距离 \'\'\' # print(\'size\', image1.size) threshold = 60 for i in range(0,image1.size[0]): # 260 for j in range(0,image1.size[1]): # 160 pixel1 = image1.getpixel((i,j)) pixel2 = image2.getpixel((i,j)) res_R = abs(pixel1[0]-pixel2[0]) # 计算RGB差 res_G = abs(pixel1[1] - pixel2[1]) # 计算RGB差 res_B = abs(pixel1[2] - pixel2[2]) # 计算RGB差 if res_R > threshold and res_G > threshold and res_B > threshold: return i # 需要移动的距离 def main_check_code(driver, element): """ 拖动识别验证码 :param driver: :param element: :return: """ image1 = get_image(driver, \'//div[@class="gt_cut_bg gt_show"]/div\') image2 = get_image(driver, \'//div[@class="gt_cut_fullbg gt_show"]/div\') # 图片上 缺口的位置的x坐标 # 2 对比两张图片的所有RBG像素点,得到不一样像素点的x值,即要移动的距离 l = get_distance(image1, image2) print(\'l=\',l) # 3 获得移动轨迹 track_list = get_track(l) print(\'第一步,点击滑动按钮\') ActionChains(driver).click_and_hold(on_element=element).perform() # 点击鼠标左键,按住不放 time.sleep(2) print(\'第二步,拖动元素\') for track in track_list: ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform() # 鼠标移动到距离当前位置(x,y) time.sleep(0.002) ActionChains(driver).move_by_offset(xoffset=-random.randint(2,5), yoffset=0).perform() time.sleep(2) print(\'第三步,释放鼠标\') ActionChains(driver).release(on_element=element).perform() time.sleep(5) def main_check_slider(driver): """ 检查滑动按钮是否加载 :param driver: :return: """ while True: try : driver.get(\'http://www.cnbaowen.net/api/geetest/\') element = WebDriverWait(driver, 30, 0.5).until(EC.element_to_be_clickable((By.CLASS_NAME, \'gt_slider_knob\'))) if element: return element except TimeoutException as e: print(\'超时错误,继续\') time.sleep(5) if __name__ == \'__main__\': try: count = 6 # 最多识别6次 driver = webdriver.Chrome() # 等待滑动按钮加载完成 element = main_check_slider(driver) while count > 0: main_check_code(driver,element) time.sleep(2) try: success_element = (By.CSS_SELECTOR, \'.gt_holder .gt_ajax_tip.gt_success\') # 得到成功标志 print(\'suc=\',driver.find_element_by_css_selector(\'.gt_holder .gt_ajax_tip.gt_success\')) success_images = WebDriverWait(driver, 20).until(EC.presence_of_element_located(success_element)) if success_images: print(\'成功识别\') count = 0 break except NoSuchElementException as e: print(\'识别错误,继续识别\') ount -= 1 time.sleep(2) else: print(\'too many attempt check code \') exit(\'退出程序\') finally: driver.close()
3. 点触验证码的识别
除了极验验证码之外,还有一个常见且比较广泛的验证码,既点触验证码 例如12306
直接点击图中符合要求的图 答案全部正确验证才会成功 有一个错误验证就会失败
示例:
https://www.jianshu.com/sign_in
识别思路 :
如果依靠图像识别验证码 识别难度非常大 第一点是文字识别 第二点是图像识别 图像背景会干扰 导致ORC几乎识别不出结果
如果直接识别白色文字 换一张验证码 颜色又变了
借助打码平台:
http://www.chaojiying.com/user/reg/
注册账户
进入用户中心 申请软件ID
关注微信或者购买题分
http://www.chaojiying.com/api-14.html
下载打码平台的api
示例如下:
#!/usr/bin/env python # coding:utf-8 import requests from hashlib import md5 class Chaojiying_Client(object): def __init__(self, username, password, soft_id): self.username = username password = password.encode(\'utf8\') self.password = md5(password).hexdigest() self.soft_id = soft_id self.base_params = { \'user\': self.username, \'pass2\': self.password, \'softid\': self.soft_id, } self.headers = { \'Connection\': \'Keep-Alive\', \'User-Agent\': \'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)\', } def PostPic(self, im, codetype): """ im: 图片字节 codetype: 题目类型 参考 http://www.chaojiying.com/price.html """ params = { \'codetype\': codetype, } params.update(self.base_params) files = {\'userfile\': (\'ccc.jpg\', im)} r = requests.post(\'http://upload.chaojiying.net/Upload/Processing.php\', data=params, files=files, headers=self.headers) return r.json() def ReportError(self, im_id): """ im_id:报错题目的图片ID """ params = { \'id\': im_id, } params.update(self.base_params) r = requests.post(\'http://upload.chaojiying.net/Upload/ReportError.php\', data=params, headers=self.headers) return r.json() if __name__ == \'__main__\': chaojiying = Chaojiying_Client(\'超级鹰用户名\', \'超级鹰用户名的密码\', \'96001\') #用户中心>>软件ID 生成一个替换 96001 im = open(\'a.jpg\', \'rb\').read() #本地图片文件路径 来替换 a.jpg 有时WIN系统须要// print chaojiying.PostPic(im, 1902) #1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加()
这里定义了一个Chaojiying_Client类 其构造函数接收三个参数 分别是超级鹰用户名,超级鹰用户名的密码,软件ID
最重要的一个方法叫做PostPic,需要传入图片对象和验证码的代号,该方法会将图片对象的相关信息 发送个超级鹰后台进行识别,
然后将识别成功的JSON返回。
ReportError方法 发生错误的时候回调 如果验证码识别错误,调用此方法会返回相应的题分。
初始化
import time from PIL import Image from selenium import webdriver from selenium.webdriver import ActionChains from chaojiying import Chaojiying def crack(): # 保存网页截图 browser.save_screenshot(\'222.jpg\') # 获取 验证码确定按钮 button = browser.find_element_by_xpath(xpath=\'//div[@class="geetest_panel"]/a/div\') # 获取 验证码图片的 位置信息 img1 = browser.find_element_by_xpath(xpath=\'//div[@class="geetest_widget"]\') location = img1.location size = img1.size top, bottom, left, right = location[\'y\'], location[\'y\'] + size[\'height\'], location[\'x\'], location[\'x\'] + size[ \'width\'] print(\'图片的宽:\', img1.size[\'width\']) print(top, bottom, left, right) # 根据获取的验证码位置信息和网页图片 对验证码图片进行裁剪 保存 img_1 = Image.open(\'222.jpg\') capcha1 = img_1.crop((left, top, right, bottom - 54)) capcha1.save(\'tu1-1.png\') # 接入超级鹰 API 获取图片中的一些参数 (返回的是一个字典) cjy = Chaojiying(\'liuxiaosong\', \'123456\', \'898237\') im = open(\'tu1-1.png\', \'rb\').read() content = cjy.post_pic(im, 9004) print(content) # 将图片中汉字的坐标位置 提取出来 positions = content.get(\'pic_str\').split(\'|\') locations = [[int(number) for number in group.split(",")] for group in positions] print(positions) print(locations) # 根据获取的坐标信息 模仿鼠标点击验证码图片 for location1 in locations: print(location1) ActionChains(browser).move_to_element_with_offset(img1, location1[0], location1[1]).click().perform() time.sleep(1) button.click() time.sleep(1) # 失败后重试 lower = browser.find_element_by_xpath(\'//div[@class="geetest_table_box"]/div[2]\').text print(\'判断\', lower) if lower != \'验证失败 请按提示重新操作\' and lower != None: print(\'登录成功\') time.sleep(3) else: time.sleep(3) print(\'登录失败\') # 登录失败后 , 调用 该函数 , 后台 则对该次判断不做扣分处理 pic_id = content.get(\'pic_id\') print(\'图片id为:\', pic_id) cjy = Chaojiying(\'liuxiaosong\', \'123456\', \'898237\') cjy.report_error(pic_id) crack() if __name__ == \'__main__\': browser = webdriver.Chrome() browser.get(\'https://www.jianshu.com/sign_in\') browser.save_screenshot(\'login.png\') # 填写from表单 点击登陆 获取验证码 的网页截图 login = browser.find_element_by_id(\'sign-in-form-submit-btn\') username = browser.find_element_by_id(\'session_email_or_mobile_number\') password = browser.find_element_by_id(\'session_password\') username.send_keys(\'15221742895\') time.sleep(1) password.send_keys(\'123456\') time.sleep(2) login.click() time.sleep(10) crack()
二、代理的使用
前面介绍了多种请求库 requests urllib selenium等
1.获取代理
网上有很多免费代理 例如 西刺 http://www.xicidaili.com/ 但是免费代理大多数是不好用的 最靠谱的方法是购买付费代理
如果本机有代理软件的话 软件一般会在本机创建HTTP和SOCKS代理服务 本机直接使用代理也可以
示例: (也可以替换成自己的可用代理 设置代理后测试网址是http://httpbin.org/get 访问该网站可以得到请求信息 其中origin字段就是客户端的ip)
2.urllib
from urllib.error import URLError from urllib.request import ProxyHandler,build_opener proxy = \'127.0.0.1:14155\' proxy_handler = ProxyHandler({ \'http\':\'http://\' + proxy, \'https\':\'https://\' + proxy }) opener = build_opener(proxy_handler) try: response = opener.open(\'http://httpbin.org/get\') print(response.read().decode(\'utf-8\')) except URLError as e: print(e.reason)
这里借助ProxyHandler 设置代理 参数是字典 键名为协议 键值为代理
创建完ProxyHandler对象后 调用 build_opener()方法传入该对象来创建一个opener对象
如果需要认证 可以改变proxy 变量 只需要在代理前面加入代理认证的用户名密码即可 例如 username:password@127.0.0.1:14155
3.requests
代理设置相对urllib简单 传入参数proxies
import requests proxy = \'127.0.0.1:14155\' proxies = { \'http\': \'http://\' + proxy, \'https\': \'https://\' + proxy, } try: response = requests.get(\'http://httpbin.org/get\', proxies=proxies) print(response.text) except requests.exceptions.ConnectionError as e: print(\'Error\', e.args)
需要认证的话同理 proxy = \'username:password@127.0.0.1:9743\'
4. selenium
from selenium import webdriver proxy = \'127.0.0.1:14155\' chrome_options = webdriver.ChromeOptions() chrome_options.add_argument(\'--proxy-server=http://\' + proxy) chrome = webdriver.Chrome(chrome_options=chrome_options) chrome.get(\'http://httpbin.org/get\')
如果是认证的相对比较麻烦
from selenium import webdriver from selenium.webdriver.chrome.options import Options import zipfile ip = \'127.0.0.1\' port = 14155 username = \'liuxiaosong\' password = \'123456\' manifest_json = """ { "version": "1.0.0", "manifest_version": 2, "name": "Chrome Proxy", "permissions": [ "proxy", "tabs", "unlimitedStorage", "storage", "<all_urls>", "webRequest", "webRequestBlocking" ], "background": { "scripts": ["background.js"] } } """ background_js = """ var config = { mode: "fixed_servers", rules: { singleProxy: { scheme: "http", host: "%(ip)s", port: %(port)s } } } chrome.proxy.settings.set({value: config, scope: "regular"}, function() {}); function callbackFn(details) { return { authCredentials: { username: "%(username)s", password: "%(password)s" } } } chrome.webRequest.onAuthRequired.addListener( callbackFn, {urls: ["<all_urls>"]}, [\'blocking\'] ) """ % {\'ip\': ip, \'port\': port, \'username\': username, \'password\': password} plugin_file = \'proxy_auth_plugin.zip\' with zipfile.ZipFile(plugin_file, \'w\') as zp: zp.writestr("manifest.json", manifest_json) zp.writestr("background.js", background_js) chrome_options = Options() chrome_options.add_argument("--start-maximized") chrome_options.add_extension(plugin_file) browser = webdriver.Chrome(chrome_options=chrome_options) browser.get(\'http://httpbin.org/get\')
需要本地创建一个manifest.json配置文件 background.js 脚本设置代理 运行之后本地会生成一个 proxy_auth_plugin.zip 文件保存当前设置
5.phantomjs
需要安装 下载地址 http://phantomjs.org/download 选择对应平台下载即可
下载后 解压文件 复制在bin目录下phantomjs.exe 到python目录下的script目录下 或者单独添加环境变量
cmd 运行 phantomjs 进入到phantomjs命令行表示配置成功
在selenium中使用的话 只需要将Chrome切换为PhantomJS即可
from selenium import webdriver browser = webdriver.PhantomJS() browser.get(\'https://www.baidu.com\') print(browser.current_url)
会报警告 selenium 3.X版本 已经弃用PhantomJS 两种方式 使用Chrome无界面 headless 或者降低selenium版本 推荐第一种
示例:
from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument(\'--headless\') chrome_options.add_argument(\'--disable-gpu\')#上面三行代码就是为了将Chrome不弹出界面,实现无界面爬取 browser = webdriver.Chrome(chrome_options=chrome_options)
PhantomJS示例:
from selenium import webdriver service_args = [ \'--proxy=127.0.0.1:9743\', \'--proxy-type=http\' ] browser = webdriver.PhantomJS(service_args=service_args) browser.get(\'http://httpbin.org/get\') print(browser.page_source)
如果加认证
from selenium import webdriver service_args = [ \'--proxy=127.0.0.1:9743\', \'--proxy-type=http\', \'--proxy-auth=username:password\' ] browser = webdriver.PhantomJS(service_args=service_args) browser.get(\'http://httpbin.org/get\') print(browser.page_source)