ghpython语法去年看了一些,一直没有应用到实践中。
手头有些数据,都在行业内部网站上查询,网站使用吧safeKey加密狗,只能使用ie来认证加密。过去我认为这种网站是没用办法爬取数据的。研究了selenium后才明白,原来,selenium是模拟浏览器,只要是人工可以登录的,它都可以爬取。
这里只是折腾的记录和注意事项,不是教程。
selenium的使用
selenium 是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。
Selenium的核心Selenium Core基于JsUnit,完全由JavaScript编写,因此可以用于任何支持JavaScript的浏览器上。
selenium可以模拟真实浏览器,自动化测试工具,支持多种浏览器,爬虫中主要用来解决JavaScript渲染问题。这里要说一下比较重要的PhantomJS,PhantomJS是一个而基于WebKit的服务端JavaScript API,支持Web而不需要浏览器支持,其快速、原生支持各种Web标准:Dom处理,CSS选择器,JSON等等。PhantomJS可以用用于页面自动化、网络监测、网页截屏,以及无界面测试
PhantomJS是无界面浏览器,但他是基于WebKit的,我的网站不能使用。新的selenium已经不支持PhantomJS这款浏览器了,因此可以通过配置chrome的options进行替代
安装
在cmd里pip install selenium,如果特别慢,可以使用国内服务加速。或是换个时段,基本就可以了。
驱动
要根据你需要使用的浏览器来下载驱动。比如我要使用ie,就要下载对应的驱动。
chromedriver驱动下载地址:
http://chromedriver.storage.googleapis.com/index.html
iedriverserver:
http://selenium-release.storage.googleapis.com/index.html
注意:
- IEDriverServer的版本号和Selenium的版本号一定要一致,
- 我因为使用错了版本(也有说是32位和64位的关系)最终我换成了3.0版本的32位,解决了send_keys很慢的问题.
- 驱动文件要放在设置有环境变量的文件夹里,比较ie的所在路径根目录,python的所在路径根目录都可以。
- 我试了下,把驱动文件直接和py文件放在同一文件夹下使用也是可以的。
基本用法
打开网页
from selenium import webdriver
ie = webdriver.Ie()#声明浏览器
url ='https://www.baidu.com'
ie.get(url)#打开浏览器预设网址
ie.implicitly_wait(10)#隐式等待
print(ie.page_source)#打印网页源代码
ie.refresh() #刷新
ie.close()#关闭浏览器
定位元素并把字符发送上去
name_label = ie.find_element_by_id("usercode") #定位用户id录入框
name_label.clear()
name_label.send_keys('user') #录入自己的id
ul,ui节点,必须要先把父节点点击展开后,再点击子节点
ie.find_element_by_xpath("//*[@id='aa208020']").click() ##点击展开父节点
ie.find_element_by_xpath("//*[@id='bb108005']").click() #再点击子节点
上面的代码使用了xpath,这比by_id看起来复杂一点,实际可以使用IEXPath,直接查看元素的xpath,复制到代码即可,很方便。据说xpath比较慢,没有CSS定位的方法快。
如果遇到frame页面,就用switch_to_frame
ie.switch_to.frame(ie.find_element_by_name("mainFrame"))
ie.find_element_by_link_text("进入数据管理系统").click()
打开新页面时,跳转到新页面
##切换到跳转后的主页面
current_window = ie.current_window_handle # 获取当前窗口handle name
all_window=ie.window_handles # 返回当前会话中所有窗口的句柄。
for window in all_window: #通过遍历判断要切换的窗口
if window != current_window:
ie.switch_to.window(window) # 将定位焦点切换到指定的窗口,包含所有可切换焦点的选项
current_window = ie.current_window_handle # 获取当前窗口handle nam
查找页面中的图片
AA = "//img[contains(@id,'img')]"
img_urls = ie.find_elements_by_xpath(AA) #读取图片链接对象list
ulist=[]
for imgx in img_urls:
ulist.append(imgx.get_attribute('src')) #将图片链接对象转换成链接list
print('该生共有图片张数'+str(len(ulist)))
上面代码为查找id包含img的元素,返一个列表,这个列表是元素的列表,我最早时的用法是,在后面的截图方法中,直接遍历这个img_urls列表,再imgx.get_attribute('src')取得url,但总是报错,原因不明,怀疑这个img_urls后面会变动,于是在取得img_urls后,直接遍历它,转换成url列表--ulist后面备用。
关于xpath语法里为什么用contains,我一开始是用通配符的,后来发现不行,看到下面的语法,才改用了contains:
部分属性值匹配
WebElement ele = driver.findElement(By.xpath("//input[start-with(@id,'fuck')]"));//匹配id以fuck开头的元素,id='fuckyou'
WebElement ele = driver.findElement(By.xpath("//input[ends-with(@id,'fuck')]"));//匹配id以fuck结尾的元素,id='youfuck'
WebElement ele = driver.findElement(By.xpath("//input[contains(@id,'fuck')]"));//匹配id中含有fuck的元素,id='youfuckyou'
上面取得图片url后,开始遍历下载这个页面所有的图片:
m = 1
for img_url in ulist: #遍历图片链接list
ie.get(img_url)
ie.find_element_by_xpath('/HTML/BODY/IMG').click()
##由于原图的url链接是js的,要点击一下才出大图。
im=get_image(ie,'/HTML/BODY/IMG') ##这里调用get_image方法,获取元素的截图
if not os.path.exists(datatitle+'/'+xm):
os.makedirs(datatitle+'/'+xm) ##创建文件夹
im.convert('RGB').save(datatitle+'/'+xm+'/'+xm+str(m)+'.jpg')
## convert('RGB')是png转成jpg的前提,RGBA意思是红色,绿色,蓝色,Alpha的色彩空间,
Alpha指透明度。而JPG不支持透明度,所以要么丢弃Alpha,要么保存为.png文件
# print('下载该生第'+str(m)+'张图片')
print("\r", end="")
print('\r下载该人图片:{0} {1}/{2}'.format('▉'*m,m,len(ulist)), end='')
m=m+1
print("完成")
上图先打开url,再点击一次出大图,然后调用get_image方法来取得img对象,并创建文件夹,先把img转成RGB格式,再保存为jpg。如果直接保存png,图像较大,如果直接保存jpg,会报错。因为png包含有透明通道,必须转RGB。
print('\r 是个小技巧,这里的作用是打印下载图片的进度条而不换行:
\r
表示将光标的位置回退到本行的开头位置\b
表示将光标的位置回退一位在
python
语言中, 使用在
python 3.x
中,end
参数,可以指定输出的结束符,默认是\n
。
上面代码中的get_image方法,先截全屏,再定准元素的大小和坐标,把元素截取出来并返回img对象:
def get_snap(driver): # 对目标网页进行截屏。这里截的是全屏
driver.save_screenshot('full_snap.png')
page_snap_obj=Image.open('full_snap.png')
return page_snap_obj
def get_image(driver,xpathstr): # 对验证码所在位置进行定位,然后截取验证码图片
img = driver.find_element_by_xpath(xpathstr)
time.sleep(1)
location = img.location
size = img.size
left = location['x']
top = location['y']
right = left + size['width']
bottom = top + size['height']
page_snap_obj = get_snap(driver)
image_obj = page_snap_obj.crop((left, top, right, bottom))
return image_obj
上面这个函数,在校验码识别中也有应用。
关于校验码识别,下面具体说说:
我们需要 pillow 和 pytesseract 这两个库,pip install 安装就好。
还需要安装 Tesseract-OCR.exe ,找到环境变量--系统变量的 Path ,将 Tesseract-OCR 的安装目录添加进去
新建系统变量 : TESSDATA_PREFIX
变量值为 tessdata 文件夹的路径(在Tesseract-OCR的安装目录下)
进入cmd 输入下面的命令查看版本,正常运行则安装成功:tesseract --version
然后就可以在python中调用了
yzmimg=get_image(ie,'//*[@id="captcha-img"]/IMG') #截取验证码图像
jym=pytesseract.image_to_string(yzmimg,lang='num',config='--psm 7 szt')[0:4] #取得校验码str
## 图像识别时,参数lang=num,num为我下载的200个tif验证码训练的结果,--psm 7的意思是单行字符串,szt是我自定义的数字和字母白名单
## 训练结果和白名单文件都在tesseract文件夹内,环境变量不配置的话,使用要加上地址。
代码中我用到了自己训练的语言文件,方法参看:
https://blog.csdn.net/sylsjane/article/details/83751297
这篇比较具体,我就是按照这个训练了200张验证码图片。
Tesseract-OCR-3.0.5 数字识别训练与合并多次训练数据
效果还是不理想,于是我想到的白名单
打开
tessdata/configs/digits,复制一份,改名为szt(自定义)
改成
tessedit_char_whitelist ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ckmnosuvwxz
这个就是白名单,想识别谁就写进去,我要识别的代码里实际上只有大写字母和数字,后面的小写字母是因为它会把大写字母和小写字母接近的,识别成小写,如果白名单不加上这几个小写的话,就会出错。
另外命令里需要用digits,不然上边的设置没用,例如上面代码里config='--psm 7 szt'
这样,识别率大大提高,但总有识别错的时候,怎么办呢?
我做了个循环,最大尝试10次,每次输入完验证码后,就尝试点击跳转页的点击跳转的元素,如成功,就退出循环,不成功就继续下一循环,再次尝试识别新的验证码、登录。
为了更方便的登录下载,我单独做了个登录验证模块,登录成功后,返回一个浏览器对象,给要下载数据的模块使用。这次以后再要下载这个系统别的数据,只要简单调用这个模块就好了。
以下是登录模块全部代码(由于最早是按chrome写的,所有浏览器对象就命名为chrome了,实际调用的ie)
from selenium import webdriver
import time
from selenium.webdriver.common.keys import Keys
from PIL import Image #图像处理
# from selenium.webdriver.common.action_chains import ActionChains
import pytesseract
# 用于图片转文字,本模块PIP后,还要安装tesseract并配置环境变量,为了提高提别率,我还训练了识别模型,设置了只包括数字和字母大小写的白名单。
def automatic_login(name,pwd,url):
print('正在启动浏览器')
chrome = webdriver.Ie()
chrome.get(url)
print('打开网页')
chrome.implicitly_wait(10)
chrome.maximize_window()
##登录 循环10次,如果成功就返回浏览器对象,如果失败就循环尝试登录。
err_jym=[] #错误校验码记录列表
sub = 1
while sub < 10:
#发送id,密码,验证码后,点击登录
name_label = chrome.find_element_by_id("usercode")
name_label.clear()
name_label.send_keys(name)
pwd_label = chrome.find_element_by_name("password")
pwd_label.clear()
pwd_label.send_keys(pwd)
yzmimg=get_image(chrome,'//*[@id="captcha-img"]/IMG') #截取验证码图像
jym=pytesseract.image_to_string(yzmimg,lang='num',config='--psm 7 szt')[0:4] #取得校验码str
print('.....发送....')
## 图像识别时,参数lang=num,num为我下载的200个tif验证码训练的结果,--psm 7的意思是单行字符串,szt是我自定义的数字和字母白名单
## 训练结果和白名单文件都在tesseract文件夹内,环境变量不配置的话,使用要加上地址。
jym_label = chrome.find_element_by_name("verifycode")
jym_label.clear()
jym_label.send_keys(jym)
login_label = chrome.find_element_by_name("Submit")
login_label.click()
""" ***以下一堆代码是第一次使用的IE驱动为2.53版本的64位,输入字符很慢,且点击无效,现在改用3.0版本32驱动,上面的代码正常使用,下面的JS方式备用***
js = 'document.getElementById("usercode").value="'+ name +'"' # 采用js的方法来输入/备用,如果上面速度慢,可用这个
chrome.execute_script(js)
pwjs = 'document.getElementsByName("password")[0].value="'+ pwd +'"'
chrome.execute_script(pwjs)
jyjs =r'document.getElementsByName("verifycode")[0].value="' + jym + '"'
chrome.execute_script(jyjs)
chrome.find_element_by_name("Submit").send_keys(Keys.ENTER) ##由于点击无效,这里改成模拟回车 """
try:
time.sleep(1)
##登录后跳转页
chrome.switch_to.frame(chrome.find_element_by_name("mainFrame"))
chrome.find_element_by_link_text("xxxx系统").click()
##切换到跳转后的主页面
current_window = chrome.current_window_handle # 获取当前窗口handle name
all_window=chrome.window_handles # 返回当前会话中所有窗口的句柄。
for window in all_window: #通过遍历判断要切换的窗口
if window != current_window:
chrome.switch_to.window(window) # 将定位焦点切换到指定的窗口,包含所有可切换焦点的选项
current_window = chrome.current_window_handle # 获取当前窗口handle nam
print("登录模块已成功登录:",chrome.title)
return chrome
except:
time.sleep(1)
print("authCode Error:", jym , "正在重新尝试登录,请稍候")
if jym in err_jym:
chrome.refresh() ##如果列表中已经存在这个错误校验码,说明上次识别错误后,页面没有刷新,这里强制刷新一下。
time.sleep(1)
err_jym.append(jym) #把错误的校验码写入记录备用
sub=sub+1
return False
def get_snap(driver): # 对目标网页进行截屏。这里截的是全屏
print('....',end='')
driver.save_screenshot('full_snap.png')
print('......',end='')
page_snap_obj=Image.open('full_snap.png')
return page_snap_obj
def get_image(driver,xpathstr): # 对验证码所在位置进行定位,然后截取验证码图片
print('正在识别验证码',end='')
img = driver.find_element_by_xpath(xpathstr)
time.sleep(1)
print('....',end='')
location = img.location
size = img.size
left = location['x']
top = location['y']
right = left + size['width']
bottom = top + size['height']
print('....',end='')
page_snap_obj = get_snap(driver)
image_obj = page_snap_obj.crop((left, top, right, bottom))
return image_obj
if __name__ == "__main__":
name = "xxxxxx"
pwd = "xxxxxxx"
url = "http://xxxxxxxxxx/"
automatic_login(name,pwd,url)
某数据下载模块,其中的登录部分直接调用上面的模块,上面的py文件放在module文件夹下,同时该文件夹下要放一个__init__.py文件。
from selenium import webdriver
import time
from selenium.webdriver.common.keys import Keys
from PIL import Image
# from selenium.webdriver.common.action_chains import ActionChains
import os
from openpyxl import Workbook
from module.load_ahzs import automatic_login #加载登录模块
#定义常量
name = "xxxxxx"
pwd = "xxxxxxxx"
url = "http://xxxxxxxxxx/"
data_select = 1 # 1:某数据下载 2:另一项数据下载
##读取列表,抓取数据
savepath=""
datatitle=""
if data_select==1: #根据不同项目来确认图版保存地点/选择点击节点
dataxpath="//*[@id='ZY108500']"
datatitle='某数据下载'
else:
dataxpath="//*[@id='ZY108300']"
datatitle='另一数据下载'
def get_snap(driver): # 对目标网页进行截屏。这里截的是全屏
driver.save_screenshot('full_snap.png')
page_snap_obj=Image.open('full_snap.png')
return page_snap_obj
def get_image(driver,xpathstr): # 对验证码所在位置进行定位,然后截取验证码图片
img = driver.find_element_by_xpath(xpathstr)
time.sleep(1)
location = img.location
size = img.size
left = location['x']
top = location['y']
right = left + size['width']
bottom = top + size['height']
page_snap_obj = get_snap(driver)
image_obj = page_snap_obj.crop((left, top, right, bottom))
return image_obj
wb=Workbook() #创建xlsx
ws = wb.active
chrome = automatic_login(name,pwd,url)
chrome.implicitly_wait(10)
##两步点击进入某数据页面
chrome.find_element_by_xpath("//*[@id='paZY108000']").click() ##进入点击父节点
# time.sleep(1)
chrome.find_element_by_xpath(dataxpath).click() #进入子节点
# time.sleep(1)
chrome.find_element_by_id("btn-cx").click()
print('开始下载'+datatitle)
##取得列表链接 //*[@id="main"]/DIV/TABLE/TBODY/TR[2]/TD[1]/DIV/A
list1=[]
A="//*[@id='main']/DIV/TABLE/TBODY/TR["
B="]/TD[1]/DIV/A"
C=A+"*"+B
num = 1
while num < 21:
link_url = chrome.find_elements_by_xpath(C)
for link in link_url:
list1.append(link.get_attribute('href'))
print('正在下载列表第',num,'页,本次最多下载20页')
# 找到对应下一页的网页元素。执行点击操作
try:
chrome.find_element_by_link_text('下一页').click()
except:
break
num = num + 1
print('总数为:',len(list1))
xuhao=1
for ksurl in list1:
chrome.get(ksurl)
## 以下是内页测试
if data_select==1: #某数据拉取数据
sfz = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[2]/TD[2]').text
xm = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[3]/TD[2]').text
sfzdz = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[6]/TD[2]').text
hjd = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[7]/TD[2]').text
xj1 = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[9]/TD[2]').text
xj2 = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[10]/TD[2]').text
xj3 = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[11]/TD[2]').text
ksh = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[2]/TD[4]').text
mz = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[3]/TD[4]').text
bmd = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[4]/TD[4]').text
bylb = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[5]/TD[4]').text
ks=[sfz,xm,sfzdz,hjd,ksh,mz,bmd,bylb,xj1,xj2,xj3]
else: #另一数据拉取数据
sfz = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[2]/TD[2]').text
xm = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[3]/TD[2]').text
sfzdz = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[6]/TD[2]').text
ksh = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[2]/TD[4]').text
mz = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[3]/TD[4]').text
bmd = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[4]/TD[4]').text
bylb = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[1]/TBODY/TR[5]/TD[4]').text
if chrome.find_elements_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[2]/TBODY/TR[2]/TD[3]'):
jfxm = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[2]/TBODY/TR[2]/TD[2]').text
shyj = chrome.find_element_by_xpath('//*[@id="main"]/DIV/FORM/TABLE[2]/TBODY/TR[2]/TD[3]').text
else:
jfxm=shyj="未审核通过"
ks=[sfz,xm,sfzdz,ksh,mz,bmd,bylb,jfxm,shyj]
print('下载第'+str(xuhao)+'名数据:'+xm+' 总数:'+str(len(list1)))
xuhao=xuhao+1
ws.append(ks) #数据写入xlsx行
AA = "//img[contains(@id,'img')]"
img_urls = chrome.find_elements_by_xpath(AA) #读取图片链接对象list
ulist=[]
for imgx in img_urls:
ulist.append(imgx.get_attribute('src')) #将图片链接对象转换成链接list
print('该人共有图片张数'+str(len(ulist)))
m = 1
for img_url in ulist: #遍历图片链接list
chrome.get(img_url)
chrome.find_element_by_xpath('/HTML/BODY/IMG').click()
im=get_image(chrome,'/HTML/BODY/IMG')
if not os.path.exists(datatitle+'/'+xm):
os.makedirs(datatitle+'/'+xm) ##创建文件夹
im.convert('RGB').save(datatitle+'/'+xm+'/'+xm+str(m)+'.jpg')
## convert('RGB')是png转成jpg的前提,RGBA意思是红色,绿色,蓝色,Alpha的色彩空间,Alpha指透明度。而JPG不支持透明度,所以要么丢弃Alpha,要么保存为.png文件
print("\r", end="")
print('\r下载该人图片:{0} {1}/{2}'.format('▉'*m,m,len(ulist)), end='')
m=m+1
print("完成")
wb.save(datatitle+'/'+datatitle+str(len(list1))+'人.xlsx') ##写入xls
print('下载完成')
#关闭和退出浏览器
chrome.close()
chrome.quit()