一、前言
大家好,不知不觉的我来csdn已经又一周年了,在这一年里,我收获了很多东西,我是2022年2月22日入驻CSDN的,一开始只是为了方便浏览文章的,后来,我也有事没事发发文章,创作了100多篇文章,有近三分之一是高质量文章,在这个不到一年里,我收获了1066位粉丝,其实,我写文章不是为了粉丝数量,只是在这个平台把自己的知识分享给别人。在新的一年里,我可以继续努力,日出万物生,日落满天星。新的一年依然记得仰望星空。2022年6月16日,那时候我才2个粉丝。虽然现在的粉丝不多,2000都没有。但是,这些都是次要的,主要是我在这里学到了很多东西。
今天借这个机会表达一下,我在csdn一周年的纪念。为了回馈粉丝长久以来的支持,博主决定开始给大家送福利了。在爬虫时,网上的免费代理IP不好用,怎么办?不要慌,我给大家争取到了一个福利,点击下面链接即可免费领取七天测试
http://suo.nz/2zmKBG
白嫖不要不要的
二、引文
目前,许多网站采取各种各样的措施来反爬虫,其中一个措施便是使用验证码。随着技术的发展,验证码的花样越来越多。验证码最初是几个数字组合的简单的图形验证码,后来加入了英文字母和混淆曲线。有的网站还可能看到中文字符的验证码,这使得识别愈发困难。
后来 12306 验证码的出现使得行为验证码开始发展起来,用过 12306 的用户肯定多少为它的验证码头疼过。我们需要识别文字,点击与文字描述相符的图片,验证码完全正确,验证才能通过。现在这种交互式验证码越来越多,如极验滑动验证码需要滑动拼合滑块才可以完成验证,点触验证码需要完全点击正确结果才可以完成验证,另外还有滑动宫格验证码、计算题验证码等。
验证码变得越来越复杂,爬虫的工作也变得愈发艰难。有时候我们必须通过验证码的验证才可以访问页面。本章就专门针对验证码的识别做统一讲解。
接下来会涉及的验证码有普通图形验证码、极验滑动验证码、点触验证码、微博宫格验证码,这些验证码识别的方式和思路各有不同。了解这几个验证码的识别方式之后,我们可以举一反三,用类似的方法识别其他类型验证码。
三、点触验证码识别
除了极验验证码,还有另一种常见且应用广泛的验证码,即点触验证码。
可能你对这个名字比较陌生,但是肯定见过类似的验证码,比如 12306 就是典型的点触验证码,直接点击图中符合要求的图。所有答案均正确,验证才会成功。如果有一个答案错误,验证就会失败。这种验证码就称为点触验证码。
3.1. 本节目标
我们的目标是用程序来识别并通过点触验证码的验证。
3.2. 准备工作
我们使用的 Python 库是 Selenium,使用的浏览器为 Chrome。请确保已经正确安装好 Selenium 库、Chrome 浏览器,并配置好 ChromeDriver,相关流程可以参考我其他的博文。
3.3. 了解点触验证码
与 12306 站点相似,不过这次是点击图片中的文字而非图片。点触验证码有很多种,它们的交互形式略有不同,但其基本原理都是类似的。
3.4. 识别思路
第一步:如果依靠图像识别点触验证码,则识别难度非常大。例如,某网站的识别难点有两点,第一点是文字识别,第二步:图像的识别。将图像重新转化文字,可以借助各种识图接口,但识别的准确率非常低,经常会出现匹配不正确或无法匹配的情况。而且图片清晰度不够,识别难度也会更大。
3.5. 解决办法
此类验证码该如何识别?互联网上有很多验证码服务平台,平台 7×24 小时提供验证码识别服务,一张图片几秒就会获得识别结果,准确率可达 90% 以上 ,例如超级鹰平台:
超级鹰平台提供了如下一些服务:
- 1.英文数字:提供最多 20 位英文数字的混合识别
- 2.中文汉字:提供最多 7 个汉字的识别
- 3.纯英文:提供最多 12 位的英文的识别
- 4.纯数字:提供最多 11 位的数字的识别
- 5.任意特殊字符:提供不定长汉字英文数字、拼音首字母、计算题、成语混合、 集装箱号等字符的识别
- 6.坐标选择识别:如复杂计算题、选择题四选一、问答题、点击相同的字、物品、动物等返回多个坐标的识别
这里我们需要处理的就是坐标多选识别的情况。我们先将验证码图片提交给平台,平台会返回识别结果在图片中的坐标位置,然后我们再解析坐标模拟点击即可
3.6. 获取 API
在官方网站下载对应的 Python API,链接为:https://www.chaojiying.com/api-14.html。此 API 是 Python 2 版本的,是用 requests 库来实现的。我们可以简单更改几个地方,即可将其修改为 Python 3 版本。
修改之后的 API 如下所示:
import requests
from hashlib import md5class Chaojiying(object):def __init__(self, username, password, soft_id):self.username = usernameself.password = md5(password.encode('utf-8')).hexdigest()self.soft_id = soft_idself.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 post_pic(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 report_error(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()
这里定义了一个 Chaojiying 类,其构造函数接收三个参数,分别是超级鹰的用户名、密码以及软件 ID,保存以备使用。
最重要的一个方法叫作 post_pic(),它需要传入图片对象和验证码类型的代号。该方法会将图片对象和相关信息发给超级鹰的后台进行识别,然后将识别成功的 JSON 返回。
另一个方法叫作 report_error(),它是发生错误的时候的回调。如果验证码识别错误,调用此方法会返回相应的题分。
3.7.模拟登陆
利用selenium模拟以账号登陆方式模拟登陆某网站。
from chaojiying import Chaojiying
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import time
from io import BytesIO
# from urllib import request
from PIL import Image
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s-%(levelname)s-%(message)s')
logger = logging.getLogger('spider')class Jianshu():def __init__(self,cjy_username,cjy_password,cjy_softid,cjy_kind):self.url = 'https://www.jianshu.com/sign_in'# self.browser = webdriver.Chrome()self.browser = webdriver.PhantomJS()self.wait = WebDriverWait(self.browser,20)self.cjy_kind = cjy_kindself.chaojiying = Chaojiying(cjy_username,cjy_password,cjy_softid)def __del__(self):self.browser.close()def open(self,js_username,js_password):"""打开网页输入用户名密码:return: None"""self.browser.maximize_window()self.browser.get(self.url)self.browser.implicitly_wait(10)self.browser.find_element_by_link_text('登录').click()login_name = self.wait.until(EC.presence_of_element_located((By.XPATH,"//input[@type='text' and @id='session_email_or_mobile_number']")))login_name.send_keys(js_username)login_password = self.browser.find_element_by_xpath('//input[@type="password" and @id="session_password"]')login_password.send_keys(js_password)def get_button(self):button = self.browser.find_element_by_class_name('sign-in-button')return buttondef get_element(self,name='captch.png'):element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'.geetest_widget')))self.element = elementlocation = element.locationsize = element.size#获取验证码左上角位置,以及验证码的宽和高(取它的位置和宽高,随后返回其左上角和右下角的坐标)left_y, right_y, left_x, right_x = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width']# print(left_x,right_x,left_y,right_y)#获取屏幕截图,以二进制形式存入内存中scrsnap = self.browser.get_screenshot_as_png()scrsnap = Image.open(BytesIO(scrsnap))#裁剪图片crop方法传入参数依次为(图片左上角x,图片左上角y,图片右下角x,图片右下角y)captcha = scrsnap.crop((left_x,left_y,right_x,right_y))captcha.save(name)return captchadef get_point(self,result):# result = {'err_no': 0, 'err_str': 'OK', 'pic_id': '3108110014436000003', 'pic_str': '47,127|56,124', 'md5': '6e5164aa4f99e6f25dfd95fd12e30e1c'}#pic_str依次为需要识别的文字的坐标,是以字符串形式返回的,每个坐标都以|分隔pic_strs = result.get('pic_str').split('|') #['47,127', '56,124']# locations = [[int(number) for number in group.split(',')] for group in pic_str]locations = []for pic_str in pic_strs:location = [int(number) for number in pic_str.split(',')]# print(locations) [[47, 127], [56, 124]]locations.append(location)return(locations,len(locations))def click_action(self,locations):for location in locations:print(location)#调用动作链move_to_element_with_offset(o_element,xoffset,yoffset)方法,移动到某个元素的某个坐标上ActionChains(self.browser).move_to_element_with_offset(self.element,location[0],location[1]).click().perform()time.sleep(1)try:self.browser.find_element_by_class_name('geetest_commit').click()except Exception as error:print(error)def run_task(self,js_username,js_password):self.open(js_username,js_password)button = self.get_button()button.click()image = self.get_element()#创建操作二进制的内存流byte_flow = BytesIO() #<class '_io.BytesIO'>#将image对象以png格式存入byte_flow文件流中image.save(byte_flow,format='PNG')result = {'err_no': 0, 'err_str': 'OK', 'pic_id': '3108110014436000003', 'pic_str': '116,173|47,127|56,124','md5': '6e5164aa4f99e6f25dfd95fd12e30e1c'}# result = self.chaojiying.post_pic(byte_flow.getvalue(),self.cjy_kind)locations,length = self.get_point(result)self.click_action(locations)login_status = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#q")))if login_status:logger.info("登录成功")else:logger.error("登录失败")if __name__ == '__main__':#超级鹰用户名、密码、软件 ID、验证码类型cjy_username = ''cjy_password = ''cjy_softid = ''cjy_kind = js_username = ''js_password = ''Jianshu(cjy_username,cjy_password,cjy_softid,cjy_kind).run_task(js_username,js_password)