title: 登录爬虫の趣事
date: 2019-06-23 17:51:50
tags:
曾经有这么一段斗争
很久很久以前……
第一话
小莫想要某站上所有的电影,写了标准的爬虫(基于HttpClient库),不断地遍历某站的电影列表页面,根据 Html分析电影名字存进自己的数据库。这个站点的运维小黎发现某个时间段请求量陡增,分析日志发现都是IP(xxx.xxx.xxx.xxx)这个用户,并且user-agent还是Python-urllib/2.7,基于这两点判断非人类后直接在服务器上封杀。
第二话
小莫电影只爬了一半,于是也针对性的变换了下策略:
- user-agent模仿百度("Baiduspider...")
- IP每爬半个小时就换一个IP代理。
小黎也发现了对应的变化,于是在服务器上设置了一个频率限制,每分钟超过120次请求的再屏蔽IP。
同时考虑到百度家的爬虫有可能会被误伤,想想市场部门每月几十万的投放,于是写了个脚本,通过 hostname 检查下这个 ip是不是真的百度家的,对这些 ip 设置一个白名单。
第三话
小莫发现了新的限制后,想着我也不急着要这些数据,留给服务器慢慢爬吧,于是修改了代码,随机1-3秒爬一次,爬10次休息10秒,每天只在8-12,18-20点爬,隔几天还休息一下。
小黎看着新的日志头都大了,再设定规则不小心会误伤真实用户,于是准备换了一个思路,当3个小时的总请求超过50次的时候弹出一个验证码弹框,没有正确输入的话就把IP 记录进黑名单。
第四话
小莫看到验证码有些傻脸了,不过也不是没有办法,先去学习了图像识别(关键词PIL,tesseract),再对验证码进行了二值化,分词,模式训练之后,总之最后识别了小黎的验证码(关于验证码,验证码的识别,验证码的反识别也是一个恢弘壮丽的斗争史...),之后爬虫又跑了起来。
第五话
小黎是个不折不挠的好同学,看到验证码被攻破后,和开发同学商量了变化下开发模式,数据并不再直接渲染,而是由前端同学异步获取,并且通过JavaScript 的加密库生成动态的 token,同时加密库再进行混淆。
混淆过的加密库就没有办法了么?当然不是,可以慢慢调试,找到加密原理,不过小莫不准备用这么耗时耗力的方法,他放弃了基于HttpClient的爬虫,选择了内置浏览器引擎的爬虫(关键词:PhantomJS,Selenium),在浏览器引擎运行页面,直接获取了正确的结果,又一次拿到了对方的数据。
第六话
小黎:……
故事到这里就结束了,我的故事将此也开始了
1、简单的登录
简洁而有效的代码是令人向往的。
import requests
from urllib.request import urlopen
import xml.etree.ElementTree as ET
def login():
url = 'http://1.1.1.1/login.action?logout=true'
data = {
'login': "Log+in",
'os_username': 'maskingtime',
'os_password': '123456',
}
response = requests.post(url,data)
# print(response.text)
cookie = response.cookies.get_dict()
print(cookie)
url2 ="http://1.1.1.1/pages/viewpage.action?pageId=15136981"
response2 = requests.get(url2,cookies=cookie)
print(response2.text)
login()
然而……
2.识别的痛苦第一季
关于验证码由不得不说的故事
tesserocr.image_to_text(image)
这样,如果Google爸爸不支持的话,那只能继续这样:
import tesserocr
from PIL import Image
import urllib.request
import pytesseract
url = "http://1.1.1.1/forlogin/img?width=80&height=30&"
dir = '/home/Documents/out_img/ttt.jpg' # 当前工作目录。
#urllib.request.urlretrieve(url, dir) # 下载图片。
#image = Image.open('/home/Documents/test.jpg')
def convert_image(imageName):
img = Image.open(imageName)
image = img.convert("L")
threshold = 100 # 设置二值的阈值100
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
image.show()
image = Image.open(imageName)
image = image.convert('L')
image2 = Image.new('L', image.size, 255)
for x in range(image.size[0]):
for y in range(image.size[1]):
pix = image.getpixel((x, y))
if pix < 120:
image2.putpixel((x, y), 0)
return img
image=convert_image(dir)
image.show()
print(pytesseract.image_to_string(image,lang = 'eng'))
print(tesserocr.image_to_text(image))
这样如果还不行的的话,就稍微痛苦一哈,然后进入下一季
3.识别的痛苦第二季
这样怕不怕呢?
import tesserocr
from PIL import Image
import cv2
# 自适应阀值二值化
def _get_dynamic_binary_image(filedir, img_name):
filename = '/home/Documents/out_img/' + img_name.split('.')[0] + '-binary.jpg'
print(filename)
img_name = filedir + '/' + img_name
print('.....' + img_name)
im = cv2.imread(img_name)
im = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY) #灰值化
# 二值化
th1 = cv2.adaptiveThreshold(im, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 1)
cv2.imwrite(filename,th1)
return th1
# 干扰线降噪
def interference_line(img, img_name):
filename = '/home/Documents/out_img/' + img_name.split('.')[0] + '-interferenceline.jpg'
h, w = img.shape[:2]
# !!!opencv矩阵点是反的
# img[1,2] 1:图片的高度,2:图片的宽度
for y in range(1, w - 1):
for x in range(1, h - 1):
count = 0
if img[x, y - 1] > 245:
count = count + 1
if img[x, y + 1] > 245:
count = count + 1
if img[x - 1, y] > 245:
count = count + 1
if img[x + 1, y] > 245:
count = count + 1
if count > 2:
img[x, y] = 255
cv2.imwrite(filename,img)
return img
# 点降噪
def interference_point(img,img_name, x = 0, y = 0):
"""
9邻域框,以当前点为中心的田字框,黑点个数
:param x:
:param y:
:return:
"""
filename = '/home/Documents/out_img/' + img_name.split('.')[0] + '-interferencePoint.jpg'
# todo 判断图片的长宽度下限
cur_pixel = img[x,y]# 当前像素点的值
height,width = img.shape[:2]
for y in range(0, width - 1):
for x in range(0, height - 1):
if y == 0: # 第一行
if x == 0: # 左上顶点,4邻域
# 中心点旁边3个点
sum = int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum <= 2 * 245:
img[x, y] = 0
elif x == height - 1: # 右上顶点
sum = int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1])
if sum <= 2 * 245:
img[x, y] = 0
else: # 最上非顶点,6邻域
sum = int(img[x - 1, y]) \
+ int(img[x - 1, y + 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum <= 3 * 245:
img[x, y] = 0
elif y == width - 1: # 最下面一行
if x == 0: # 左下顶点
# 中心点旁边3个点
sum = int(cur_pixel) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x, y - 1])
if sum <= 2 * 245:
img[x, y] = 0
elif x == height - 1: # 右下顶点
sum = int(cur_pixel) \
+ int(img[x, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y - 1])
if sum <= 2 * 245:
img[x, y] = 0
else: # 最下非顶点,6邻域
sum = int(cur_pixel) \
+ int(img[x - 1, y]) \
+ int(img[x + 1, y]) \
+ int(img[x, y - 1]) \
+ int(img[x - 1, y - 1]) \
+ int(img[x + 1, y - 1])
if sum <= 3 * 245:
img[x, y] = 0
else: # y不在边界
if x == 0: # 左边非顶点
sum = int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum <= 3 * 245:
img[x, y] = 0
elif x == height - 1: # 右边非顶点
sum = int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x - 1, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1])
if sum <= 3 * 245:
img[x, y] = 0
else: # 具备9领域条件的
sum = int(img[x - 1, y - 1]) \
+ int(img[x - 1, y]) \
+ int(img[x - 1, y + 1]) \
+ int(img[x, y - 1]) \
+ int(cur_pixel) \
+ int(img[x, y + 1]) \
+ int(img[x + 1, y - 1]) \
+ int(img[x + 1, y]) \
+ int(img[x + 1, y + 1])
if sum <= 4 * 245:
img[x, y] = 0
cv2.imwrite(filename,img)
return img
dir = '/home/Documents/1.jpg'
image = Image.open(dir)
image.show()
# image=
#image=interference_point(interference_line(_get_dynamic_binary_image('/home/Documents','1.jpg'),'1.jpg'),'1.jpg')
#image=interference_line(interference_point(_get_dynamic_binary_image('/home/Documents','1.jpg'),'1.jpg'),'1.jpg')
image=interference_point(interference_line(interference_point(_get_dynamic_binary_image('/home/Documents/out_img','ttt.jpg'),'ttt.jpg'),'ttt.jpg'),'ttt.jpg')
#print(tesserocr.image_to_text(image))
效果是:
原图
加工后
在加工
可是还是不行呢?稍微痛苦下,然后进入下一季
4.识别的痛苦第三季
此时思路明晰,不为外物所动,由于验证码都是数字,这可简单的很呢!
分类
然后大大说,等等,可以这样干嘞!啊哈,为啥不早说~
5.观赏环节
这位大兄弟写的jsp部分赏析~
观赏1 观赏2
request请求起来这可太折磨人了!
为何不使用jsp?
1.之前就说,java程序员的艺术基因表现形式就一句话,“我觉得挺好看的啊!”所以第一个不是使用jsp的原因我认为是难看!
2.动态资源和静态资源全部耦合在一起,服务器压力大,因为服务器会收到各种http请求,例如css的http请求,js的,图片的等等。一旦服务器出现状况,前后台一起玩完,用户体验极差。
3.如果jsp中的内容很多,页面响应会很慢,因为是同步加载。
4.需要前端工程师使用java的ide,以及需要配置各种后端的开发环境,你们有考虑过前端工程师的感受吗?
以及等等等等
所谓术业有专攻,随着数据量的攀升,jsp这锅乱炖给服务器造成的压力越来越高,维护人员更是叫苦不迭。时代的呼喊,新的框架和设计理念终于把前后端这一块硬石头给劈开了。
离开的原始社会
后端程序员说:
mongodb,http/tcp,多线程,分布式架构(dubbo,dubbox,spring cloud),弹性计算架构,微服务架构(springboot+zookeeper+docker+jenkins),java性能优化。了解一下~
前端程序员说:
html5,css3,jquery,angularjs,bootstrap,reactjs,vuejs,webpack,less/sass,gulp,nodejs,Google V8引擎,javascript多线程,模块化,面向切面编程,设计模式,浏览器兼容性,性能优化了解一下。
舒服了舒服了,题外话截至!
5.使用selenium结束罪恶的一生
import sys
from selenium import webdriver
from bs4 import BeautifulSoup
import time
class login(object):
def __init__(self, name,usename):
self.name=name
self.usename = usename
def loginPoc(self):
option = webdriver.ChromeOptions()
option.add_argument('headless')
chrome_driver = "/home/Documents/chromedriver"
browser = webdriver.Chrome(chrome_driver, chrome_options=option)
# browser1 = webdriver.Chrome(chrome_driver)
browser.get('http://1.1.1.1/index.jsp')
try:
# if lock.acquire(1):
print(browser.title)
print(str(self.usename))
browser.find_element_by_id('u').send_keys(str(self.usename))
browser.find_element_by_id('p').send_keys('test2019!')
h1 = browser.current_window_handle
js = "window.open('1.1.1.1/users/forlogin/')"
browser.execute_script(js)
handles = browser.window_handles
h2 = None
for handle in handles:
if handle != h1:
h2 = handle
browser.switch_to.window(h2)
bucket_text = browser.page_source
soup = BeautifulSoup(bucket_text, "html.parser")
v = soup.find("challenge").getText()
browser.switch_to.window(h1)
browser.find_element_by_id('verifycode').send_keys(v)
global s_time
global first
global fre
if(first>0):
s_time = time.time()
first=0
browser.find_element_by_id('login_btn').click()
if ("验证成功" in browser.page_source):
fre+=1
print(self.name + " 登录成功")
else:
print(self.name + " 登录失败")
e_time = time.time()
print("use {:.5}s".format(e_time - s_time))
finally:
browser.close()
于是故事就这样结束了~
网友评论