目录
一、爬虫之验证码
1、输入式验证码
2、滑动式验证码
3、点击式验证码
二、爬虫之动态加载数据处理——selenium模块
1、基本介绍
2、使用流程
3、定位元素的方式
4、实例
三、 Scrapy框架
1、概述
2、基本构成
3、基本流程
当我们在遇到需要登陆或者注册的网站时就会遇到验证码,验证码的出现就是为了区分人和机器,但是随着现在人工智能的发展,这种区分已经不明确了,在python中有PIL库进行图像处理、机器学习也能更好地解决验证码的问题,下面结合实例列举爬虫如何让应对验证码问题;
类型:用户根据图片输入相应的数字和字母
应对:用Python的第三方库Tesserocr-OCR,对网页上的验证码图片进行识别
from PIL import Image
import tesserocr
image = Image.open('./1.png')
result = tesserocr.image_to_text(image)
print(result)
当该图片的背景对图上数字形成干扰易造成识别误差时应对图片转灰度再进行二值化处理,以此提高识别率:
image = Image.open('./1.png')
image.show()
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)
如Bilibili视频网站用户登录界面,在输入完密码后需要滑动进行拼图验证。解决思路是存三张图片,分别是完整的图、有缺口的图和缺口图。首先识别缺口在图中的位置,然后计算滑动的距离和轨迹。最后用selenium进行模拟操作。
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
import time
import random
from PIL import Image
web='http://literallycanvas.com/'
#初始化
def init():
#定义全局变量
global url, browser, username, password, wait
url = 'https://passport.bilibili.com/login'
browser = webdriver.Chrome()
username = '************'
password = '************'
wait = WebDriverWait(browser, 20)
#登录
def login():
browser.get(url)
user = wait.until(EC.presence_of_element_located((By.ID, 'login-username')))
passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))
user.send_keys(username)
passwd.send_keys(password)
#通过输入回车键模仿用户登录
#passwd.send_keys(Keys.ENTER)
login_btn=wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'a.btn.btn-login')))
#随机延时点击
time.sleep(random.random()*3)
login_btn.click()
#设置元素的可见性用于截图
def show_element(element):
browser.execute_script("arguments[0].style = arguments[1]", element, "display: block;")
def hide_element(element):
browser.execute_script("arguments[0].style = arguments[1]", element, "display: none;")
#截图
def save_pic(obj, name):
try:
pic_url = browser.save_screenshot('.\\bilibili.png')
#开始获取元素位置信息
left = obj.location['x']
top = obj.location['y']
right = left + obj.size['width']
bottom = top + obj.size['height']
im = Image.open('.\\bilibili.png')
im = im.crop((left, top, right, bottom))
file_name = 'bili' + name + '.png'
im.save(file_name)
except BaseException as msg:
print("截图失败:%s" % msg)
def cut():
c_background = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute')))
c_slice = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'canvas.geetest_canvas_slice.geetest_absolute')))
c_full_bg = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute')))
hide_element(c_slice)
save_pic(c_background, 'back')
show_element(c_slice)
save_pic(c_slice, 'slice')
show_element(c_full_bg)
save_pic(c_full_bg, 'full')
#判断元素是否相同
def is_pixel_equal(bg_image, fullbg_image, x, y):
#bg_image是缺口的图片
#fullbg_image是完整图片
bg_pixel = bg_image.load()[x, y]
fullbg_pixel = fullbg_image.load()[x, y]
threshold = 60
if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs(bg_pixel[2] - fullbg_pixel[2] < threshold)):
return True
else:
return False
#计算滑块移动的距离
def get_distance(bg_image, fullbg_image):
distance = 57
for i in range(distance, fullbg_image.size[0]):
for j in range(fullbg_image.size[1]):
if not is_pixel_equal(fullbg_image, bg_image, i, j):
return i
#构造滑动轨迹
def get_trace(distance):
#distance是缺口离滑块的距离
trace = []
faster_distance = distance*(4/5)
start, v0, t = 0, 0, 0.2
while start < distance:
if start < faster_distance:
a = 1.5
else:
a = -3
move = v0 * t + 1 / 2 * a * t * t
v = v0 + a * t
v0 = v
start += move
trace.append(round(move))
return trace
#模拟拖动
def move_to_gap(trace):
slider=wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'div.geetest_slider_button')))
# 使用click_and_hold()方法悬停在滑块上,perform()方法用于执行
ActionChains(browser).click_and_hold(slider).perform()
for x in trace:
# 使用move_by_offset()方法拖动滑块,perform()方法用于执行
ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
time.sleep(0.5)
ActionChains(browser).release().perform()
def slide():
distance=get_distance(Image.open('.\\bili_back.png'),Image.open('.\\bili_full.png'))
trace = get_trace(distance-5)
move_to_gap(trace)
time.sleep(3)
init()
login()
cut()
slide()
类型:按照给定的顺序依次点击完成验证
应对:调用第三方识别库——获取第三方返回的坐标——用selenium模拟用户点击。如付费软件超级鹰。代码分为两个部分,一个是超级鹰API接口,另一个是上述操作。
import time
from PIL import Image
from selenium import webdriver
from selenium.webdriver import ActionChains
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('*********', '************', '900751')
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('********', '**********', '900751')
cjy.report_error(pic_id)
crack()
if __name__ == '__main__':
patn = 'chromedriver.exe'
browser = webdriver.Chrome(patn)
browser.get('https://www.jianshu.com/sign_in')
browser.save_screenshot('lodin.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('***********')
password.send_keys('***********')
login.click()
time.sleep(5)
crack()
selenium主要是用来做自动化测试,支持多种浏览器,爬虫中主要用来解决JavaScript渲染问题。模拟浏览器进行网页加载,当requests,urllib无法正常获取网页内容的时候,是网页自动化测试工具,可以自动化的操作浏览器。如果需要操作哪个浏览器需要安装对应的driver,比如你需要通过selenium操作chrome,那必须安装chromedriver,而且版本与chrome保持一致。
- 下载一个浏览器的驱动程序
- 实例化一个浏览器对象
- 编写基于浏览器自动化的操作代码
- 发起请求:get(url)
- 标签定位:find系列的方法
- 标签交互:send_keys('xxx')
- 执行js程序:excute_script('jsCode')
- 前进,后退:back(),forward()
- 关闭浏览器:quit()
如 我们需要通过前端工具来查看网页元素属性:
定位方法:
1、通过id定位:
dr.find_element_by_id("kw")
2、通过name定位:
dr.find_element_by_name("wd")
3、通过class name定位:
dr.find_element_by_class_name("s_ipt")
4、通过tag name定位:
dr.find_element_by_tag_name("input")
5、通过xpath定位,xpath定位有N种写法,这里列几个常用写法:
dr.find_element_by_xpath("//*[@id='kw']")
dr.find_element_by_xpath("//*[@name='wd']")
dr.find_element_by_xpath("//input[@class='s_ipt']")
dr.find_element_by_xpath("/html/body/form/span/input")
dr.find_element_by_xpath("//span[@class='soutu-btn']/input")
dr.find_element_by_xpath("//form[@id='form']/span/input")
dr.find_element_by_xpath("//input[@id='kw' and @name='wd']")
什么是框架?
- 就是一个集成了很多功能并且具有很强通用性的一个项目模板。
Scrapy,Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 后台也应用在获取API所返回的数据或者通用的网络爬虫。Scrapy吸引人的地方在于它是一个框架,任何人都可以根据需求方便的修改。它也提供了多种类型爬虫的基类,如BaseSpider、sitemap爬虫等,最新版本又提供了web2.0爬虫的支持。
Scrapy框架主要由五大组件组成,它们分别是调度器(Scheduler)、下载器(Downloader)、爬虫(Spider)和实体管道(Item Pipeline)、Scrapy引擎(Scrapy Engine)。下面我们分别介绍各个组件的作用。
(1)、调度器(Scheduler):
调度器, 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址说白了把它假设成为一个URL(抓取网页的网址或者说是链接)的优先队列,由它来决定下一个要抓取的网址是 什么,同时去除重复的网址(不做无用功)。用户可以自己的需求定制调度器。
(2)、下载器(Downloader):
下载器,是所有组件中负担最大的,它用于高速地下载网络上的资源。Scrapy的下载器代码不会太复杂,但效率高,主要的原因是Scrapy下载器是建立在twisted这个高效的异步模型上的(其实整个框架都在建立在这个模型上的)。
(3)、 爬虫(Spider):
爬虫,是用户最关心的部份。用户定制自己的爬虫(通过定制正则表达式等语法),用于从特定的网页中提取自己需要的信息,即所谓的实体(Item)。 用户也可以从中提取出链接,让Scrapy继续抓取下一个页面。
(4)、 实体管道(Item Pipeline):
实体管道,用于处理爬虫(spider)提取的实体。主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。
(5)、Scrapy引擎(Scrapy Engine):
Scrapy引擎是整个框架的核心.它用来控制调试器、下载器、爬虫。实际上,引擎相当于计算机的CPU,它控制着整个流程。
(1)生成Scrary项目
(2)修改setting;
修改三项内容,第一个是不遵循机器人协议,第二个是下载间隙,由于下面的程序要下载多个页面,所以需要给一个间隙(不给也可以,只是很容易被侦测到),第三个是请求头,添加一个User-Agent,第四个是打开一个管道:
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 1
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent':'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36'
}
ITEM_PIPELINES = {
'TXmovies.pipelines.TxmoviesPipeline': 300,
}
(3) 确认要提取的数据,item项
item定义你要提取的内容(定义数据结构),比如我提取的内容为电影名和电影描述,我就创建两个变量。Field方法实际上的做法是创建一个字典,给字典添加一个建,暂时不赋值,等待提取数据后再赋值。下面item的结构可以表示为:{'name':'','descripition':''}。
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class TxmoviesItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
name = scrapy.Field()
description = scrapy.Field()
(4) 写爬虫程序:
要写的部分是parse方法里的内容,重点在于如何写xpath 。item里面创建的变量就是字典的键值,可以直接进行赋值。赋值后交给管道处理。
yield
程序里一共有两个yield,我比较喜欢叫它中断,当然中断只在CPU中发生,它的作用是移交控制权,在本程序中,我们对item封装数据后,就调用yield把控制权给管道,管道拿到处理后return返回,又回到该程序。这是对第一个yield的解释。
第二个yield稍微复杂点,这条程序里利用了一个回调机制,即callback,回调的对象是parse,也就是当前方法,通过不断的回调,程序将陷入循环,如果不给程序加条件,就会陷入死循环,如本程序我把if去掉,那就是死循环了。
yield scrapy.Request(url=url,callback=self.parse)
xpath
还有一个要注意的是如何提取xpathl里的数据,我们的写法有四种,第一种写法拿到selector选择器,也就是原数据,里面有一些我们用不到的东西。第二个extract(),将选择器序列号为字符串。第三个和第四个一样,拿到字符串里的第一个数据,也就是我们要的数据。
items['name']=i.xpath('./a/@title')[0]
items['name']=i.xpath('./a/@title').extract()
items['name']=i.xpath('./a/@title').extract_first()
items['name']=i.xpath('./a/@title').get()
# -*- coding: utf-8 -*-
import scrapy
from ..items import TxmoviesItem
class TxmsSpider(scrapy.Spider):
name = 'txms'
allowed_domains = ['v.qq.com']
start_urls = ['https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset=0&pagesize=30']
offset=0
def parse(self, response):
items=TxmoviesItem()
lists=response.xpath('//div[@class="list_item"]')
for i in lists:
items['name']=i.xpath('./a/@title').get()
items['description']=i.xpath('./div/div/@title').get()
yield items
if self.offset < 120:
self.offset += 30
url = 'https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset={}&pagesize=30'.format(
str(self.offset))
yield scrapy.Request(url=url,callback=self.parse)
(5)交给管道输出 :
管道可以处理提取的数据,如存数据库。我们这里仅输出。
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
class TxmoviesPipeline(object):
def process_item(self, item, spider):
print(item)
return item
最后执行该项目。
总结为:
新建项目-》进入项目-》新建爬虫文件-》明确抓取的内容,写item-》写爬虫程序,爬取数据-》交给管道处理数据-》调整全局配置setting-》执行爬虫程序,可以通过终端或者在程序里写一个run程序