Python+selenium下载数据初探

 

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 语言中, 使用 print 打印输出时,默认是会进行换行的。如何让其不换行呢?

python 3.x 中,print有一个 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 数字识别训练与合并多次训练数据

下载:jTessBoxEditor安装包

效果还是不理想,于是我想到的白名单

打开
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()







    



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值