• Python精确指南——第三章 Selenium和爬虫


    3       Selenium

    3.1     介绍

    网络爬虫在互联网领域有着广泛的应用。

    Selenium是一个页面自动化控制框架。能够模拟实际操作,自动化获取网站提供的页面资源信息。

    Selenium能够自定义页面操作的行为,按照用户指定的跳转路径访问,具有实现跟实际用户一样填充信息、提交表单请求的能力,适用于专门网站特定信息的获取。比如:特定图片网站图片的获取,购物网站商品信息的获取等等。

    3.2     下载与安装

    Selenium最新的版本是3.8.0,目前支持Python 2.7和3.4+版本。

    在线安装:pip install -U selenium

    离线安装:在PyPI网站上下载对应安装包,参考1.4 章节Python安装包 进行离线安装。

    3.3     关键技术要点

    Selenium框架的开发步骤这里不做详细介绍,可以参考如下链接进行开发前学习。

    开发文档及样例:

    http://seleniumhq.github.io/selenium/docs/api/py/

    Selenimu开发包API:

    https://seleniumhq.github.io/selenium/docs/api/py/api.html

    下面就开发中遇到的几个关键技术要点进行详解。

    3.3.1  浏览器的选择

    Selenium针对不同的浏览器有对应的驱动引擎,在64位系统上,一般IE是64bit。如果用32bit的IEDriverServer.exe,在第二页就会看到web browser not get,然后运行出错。但是如果用64bit的IEDriverServer.exe,在填表格的时候就会特别慢,原因不明。

    使用不同的浏览器,需要使用到不同浏览器的驱动Driver,下面是各个浏览器Selenium Client Drivers的下载页面:

    http://seleniumhq.org/download/

    Ø  Selenium的WebDriver打开IE失败的解决办法

    在运行IE浏览器时,会报下面的错误:

    WebDriverException: Message: u'Unexpected error launching Internet Explorer. Protected Mode must be set to the same value (enabled or disabled) for all zones.'

    两种方法:

    1)修改IE的安全策略,就像Exception里面提示的那样。

    2)在生成webdriver对象之前先执行这些代码:

    from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

    DesiredCapabilities.INTERNETEXPLORER['ignoreProtectedModeSettings'] = True

    firefox速度会比较快,首选firefox。

    3.3.2  XPath

    XPath是XML路径语言,用来查找定位xml树状结构中的节点,同样适用html。

    参考学习手册:

    http://www.w3school.com.cn/xpath/index.asp

    判断定义好的xpath规则是否找到定位节点:

    uname = self.master.html_elem.xpath("//tr/td/form[@name = 'frmResult']")     
    
        if len(uname) == 0:
    
            return False

    3.3.3  StringIO

    StringIO模块可以将内存中的xml结构字符串保存成离线html文件,实现在线转离线的功能:

    import StringIO
    
    blank _xml = StringIO.StringIO(<root></root>)
    
    root_test = etree.parse(blank_xml)
    
    root_test.write("test.xml")

    3.3.4  lxml

    第三方安装包lxml比Python自带的xml模块具有更为强大的解析xml或html的功能,推荐使用。最新的版本是4.1.1。

    下载页面:

    https://pypi.python.org/pypi/lxml/4.1.1

    找到对应的版本,使用pip安装即可。

    像3.3.3 StringIO章节中介绍的保存的离线xml或者html,lxml就可以像解析在线request请求到的网页内容一样进行解析:

    url:

    xxx_sample  = urllib2.urlopen(url).read()
    
    Doc = html.fromstring(xxx_sample)

    文件:

    xxx_sample = HTML.parse('xxx_sample.htm')
    
    Doc = xxx_sample.getroot()

    3.3.5  解析器的选择

    lxml有默认的解析器,但是对于非规则的网页处理非常不好。相当多的网站不是规则的html语法,所以需要选择外部的解析器。

    在lxml里调用beautifulsoap。这个处理中文比较好,首选解析器。

    3.3.6  中文字符的处理

    Ø  基本支持

    在源文件的第一行,在任何代码和注释前面,加入如下语句:

    #encoding=utf-8

    一般需要在写任何中文和代码之前加入这句话。如果先在代码里出现了中文,然后贴这句话,或导致乱码。

    Ø  保存中文网页

    保存网页数据。通常的网页数据是unicode,在eclispe中运行的时候可以正确的保存,但是单独运行就不行了。错误如下:

    123.jpg

    使用encode("gb2312", "replace")就可以解决问题。

    代码如下:

    file_object.write(page_text.encode("gb2312", "replace"))

    Ø  Unicode字符串

    所有涉及到中文的字符串前加上u,实际上,可以在使用#encoding=utf-8之后,任何字串,包括英文字前面加上u也都是没有问题,例子:

    text1 = u”中文字符”
    
    text2 = u”mystring”

    Ø  中文字符的编码转换

    在线request网页:

    在读取文本或者读取网页内容的时候,有时候无法判断字符,需要使用import chardet来自动判断字符的编码格式,然后装换成unicode,例子:

       content = urllib2.urlopen(url_to_fetch).read()           
    
        xxx_encode = chardet.detect(content)
    
        unicode_content = unicode(content, xxx_encode["encoding"], "replace")
    
    

    离线html网页:

    解析中文离线网页的方法:

    parser = etree.HTMLParser(encoding = 'gb2312')
    
    xxx_sample = HTML.parse('xxx.htm', Parser)
    
    xxx_root = xxx_sample.getroot()
    
    ct = xxx_root.xpath('//title')
    
    try_name = ct[0].text

    这样就可以得到中文title。

    Ø  中文字符的匹配

    需要使用Python内置正则表达式re模块,例子:

    中文正则

    cn_str = u"计算机科学与技术"
    
    m = re.search(u"([u4e00-u9fa5]+)科学")
    
    t=m.group(1)

    这样就可以得到“计算机”。

    3.1.1  开发要点

    • 获得页面源码

    page_text = chr_browser.page_source
    • 在页面执行自定义script脚本

    平时用到的比较多的全选,复制等。

    chr_browser.execute_script('document.execcommand("selectall"))

    查找document.execcommand可以得到各种命令。

    • 打开多个Selenium控制的实例

    Selenium控制的实例需要指定一个端口,默认使用的是一个,所以无法启动多个selenium的实例。如果指定了不同的端口,就能在一台PC上控制多个浏览器实例。

    3.1.2  验证码的识别

    在使用Selenium访问网站时,用户名和密码的表单提交相对简单,最难的是各个网站登录时的验证码识别。

    Pytesseract:python封装的tesseract

    Tesseract是图片识别的一个开源项目,托管网址:

    https://github.com/tesseract-ocr/tesseract

    PyPI下载路径和基本使用介绍:

    https://pypi.python.org/pypi/pytesseract

    Pytesseract的使用还依赖于PIL库,PIL只能使用exe安装,否则很麻烦,而且只支持32位的python,下载网址:

    http://www.pythonware.com/products/pil/

    Pytesseract其实并没有做什么事情,把命令打印出来,就是在线程里执行如下的结果:

    ['tesseract', 'C:\Users\L00163~1\AppData\Local\Temp\tess_2.bmp', 'C:\Users\L00163~1\AppData\Local\Temp\tess_3', '-l', 'eng']

    很明显,Pytesseract把输入的流变成bmp文件,然后让tesseract.exe去识别,然后从文本文件里获得数据。

    ['tesseract', 'C:\Users\L00163~1\AppData\Local\Temp\tess_2.bmp', 'C:\Users\L00163~1\AppData\Local\Temp\tess_3', '-l', 'eng', 'batch.nochop', 'makebox']
    
    D:pythonsdk	esseract	esseract-ocr-3.01-win32-portableTesseract-ocrTesseract.exe zhilian_code1.bmp ret.txt -l eng -psm 7

    注意把tesseract的目录加入到路径里,必须重启才能让PATH生效。

    追加:加到全局path里不行,会崩溃,把执行命令找出来,在dos下执行,就会看到崩溃的真正原因。是因为找不到“./tessdata/eng.traineddata”. 实际上是有这个文件的说明还是路径问题。干脆修改tesseract_cmd。Pytesseract本身是非常简单的,需要修改两处。注意只能修改Pytesseract安装包源码,而不是修改安装之后的文件,是改不了的。

    在pytesseract.py里增加一个函数接口。

    def set_tesseract_cmd(tesseract_file_path):
    
        tesseract_cmd = tesseract_file_path

    另外在pytesseract的__init__.py里增加一句。

    from pytesseract import set_tesseract_cmd

    改完之后,重新执行setup.py build和setup.py install就可以了。

    不过为了简单起见,直接把pytesseract.py复制到工程里就可以了,修改最方便。因为工程里import会优先找本地的文件,虽然同是也安装了pytesseract,但是使用的是本地的pytesseract.py。

    在函数里修改文件的全局变量,需要加上global。如下面的修改

    tesseract_cmd = 'tesseract'
    
    def set_tesseract_cmd(tesseract_file_path):
    
    tesseract_cmd = tesseract_file_path

    是没有效果的,set_tesseract_cmd的tesseract_cmd是另外一个局部变量。

    Ø  去底噪预处理

    适合于底噪不是很强烈,并且没有单点深色噪音的识别码。

    例如下图,来自于火车票订票网站。

    import Image,ImageFilter
    
    # load a color image
    
    im = Image.open('passCodeAction.do6.jpg')
    
    big_img = im.resize((120, 40))
    
    bim = big_img.filter(ImageFilter.SMOOTH)
    
    Lim = big_img.convert('L')
    
    Lim.save('fun_Level.jpg')
    
    Lim.show()
    
    # setup a converting table with constant threshold
    
    threshold = 160
    
    table = []
    
    for i in range(256):
    
        if i < threshold:
    
            table.append(0)
    
        else:
    
            table.append(1)
    
    # convert to binary image by the table
    
    bim = Lim.point(table, '1')
    
    bim.show()
    
    bim.save('fun_binary.tif')

    Ø  去单点强底噪的方法

    原始图片,虽然底噪简单,但是有不少单点的深色底噪。

    处理方法:

    im = Image.open(image_name)
    
          im = im.filter(ImageFilter.MedianFilter())
    
          enhancer = ImageEnhance.Contrast(im)
    
          im = enhancer.enhance(2)
    
          im = im.convert('1')
    
          im.show()

    经过以上处理,按道理及按照网络上的资料,简单英文和数字的识别率是很高的,实际上在用验证码的时候,识别率很低。而绝大部分图形实际上是非常清楚的,比tesseract自带样本还要清楚。最后觉得原因可能是因为图片太小,只有80×80的原因。

    尝试用windows自带的看图软件将识别码图片放大,然后截图,另存为一个文件,结果一下子就认准了,说明就是图片大小的问题,不是清晰度的原因。

    Ø  图片放大方法

    def dzoom_img(pic, ratio):
    
        w = pic.size[0] * ratio
    
        h = pic.size[1] * ratio
    
        big_pic = pic.resize((w,h), Image.BILINEAR)
    
        big_pic.show()
    
        big_pic.save('auto_big.bmp')
    
    return big_pic

    Ø  图片CROP

    手工放大后用hypersnap截图如下,可以正常识别。

    2q.jpg

    但是用软件自动放大之后,什么都识别不出来,连乱码都没有了,对比下两个图,没什么区别,只是旁边多了一些黑点,猜想是不是因为这些黑点的原因导致认不出来。用工具将黑点全部去掉,结果能正常识别。所以还需要用软件把旁边的黑点crop掉。

    3q.png

    CROP的方法

        crop_reg = (20, 20, w-20, h-20)
    
    big_pic = big_pic.crop(crop_reg)

    Ø  获取验证码图片

    参考多篇文章,最可靠的是截屏

    查看元素信息如下:

    image.png

    截屏处理:

    用图片软件看截屏的图和位置信息,可以看出,尺寸和size都是能对应上的,所以可以用crop函数精确的截取。不过同时要注意,图片周边的小点会产生明显的影响,所以直接在crop阶段去掉就可以了。在(515,165)处就能除掉边界。所以截取的区域为

    (x-2, y-2,x+w-4, y+h-4)

    Ø  白名单研究:只处理数字和字母

    发现验证码的识别率很低,只有全数字的识别率高,尝试只识别数字和字母

    D:pythonsdk esseract esseract-ocr-3.01-win32-portableTesseract-ocrTesseract.exe big_test4.png ret.txt -psm 7 char_digits

    新建文件:

    D:pythonsdk esseract esseract-ocr-3.01-win32-portableTesseract-ocr essdataconfigschar_digits

    内容如下,限制只能识别这些字符

    tessedit_char_whitelist 0123456789ABCDEFGHIJKLMNOPQRSTYVWXKZ

    3.1.1  打包部署问题

    Selenium工程开发完成后,在需要在不同机器上进行部署搭建,不能依赖开发环境,在打包完成后遇到有下面的问题,最终也是通过修改源码的方式完美解决。

    Python打包的部分在后面的章节会详细介绍,这里只讨论IE浏览器和firefox浏览器的selenium工程打包部署过程中遇到问题的解决。以下打包过程使用PyInstaller工具。

    Ø  Firefox浏览器

    webdriver里使用firefox打包报错如下,找不到webdriver.xpi和webdriver_prefs.json两个文件:

    image.png

    解决方法:

    用的2.48 selenium,修改两处firefox_profile.py源码文件。

    然后把这两个文件复制到单文件的exe同路径下就可以用了。

    2.48 selenium代码修改, 都是改了路径为当前路径。

    image.png

    修改为:

    '''with open(os.path.join(os.path.dirname(__file__),
    
                                       WEBDRIVER_PREFERENCES)) as default_prefs:
    
                    FirefoxProfile.DEFAULT_PREFERENCES = json.load(default_prefs)'''
    
               
    
       if os.path.exists(WEBDRIVER_PREFERENCES):
    
            with open(WEBDRIVER_PREFERENCES) as default_prefs:
    
                 FirefoxProfile.DEFAULT_PREFERENCES = json.load(default_prefs)
    
       else:   
    
            with open(os.path.join(os.path.dirname(__file__),
    
                               WEBDRIVER_PREFERENCES)) as default_prefs:
    
                 FirefoxProfile.DEFAULT_PREFERENCES = json.load(default_prefs)   

    修改为:

    '''if addon == WEBDRIVER_EXT:
    
               = os.path.join(os.path.dirname(__file__), WEBDRIVER_EXT)'''
    
       if addon == WEBDRIVER_EXT:
    
           if os.path.exists(WEBDRIVER_PREFERENCES):
    
               addon = os.path.join(WEBDRIVER_EXT)
    
           else:
    
               addon = os.path.join(os.path.dirname(__file__), WEBDRIVER_EXT)

    Ø  IE浏览器

    webdriver里使用ie打包的结果

    这些是包含的文件

    image.png

    打包报错如下:

    image.png

    无论如何找不到dll,pyinstaller的各种方法都已经试过了,就是不行。

    最后根据运行的提示信息,找到browser_man_lib.py的44行,点击进去找到webdriver.py,发现是这么一行代码:

    try:
    
        self.iedriver = CDLL(os.path.join(os.path.dirname(__file__),"win32", "IEDriver.dll"))
    
    except WindowsError:
    
        try:
    
            self.iedriver = CDLL(os.path.join(os.path.dirname(__file__),"x64", "IEDriver.dll"))
    
        except WindowsError:
    
            raise WebDriverException("Unable to load the IEDriver.dll component")

    肯定是CDLL(os.path.join(os.path.dirname(__file__),"x64", "IEDriver.dll"))执行不成功,猜测CDLL 只是要load一个dll而已,并不是要求确定的路由,因为输入参数显然就是全路径的dll。所以先把这个路径打印出来。代码如下:

    dll_test_path = os.path.join(os.path.dirname(__file__),"win32", "IEDriver.dll")
    
    print dll_test_path
    
    try:
    
        self.iedriver = CDLL(dll_test_path)
    
    except WindowsError:
    
        try:
    
            self.iedriver = CDLL(os.path.join(os.path.dirname(__file__),"x64", "IEDriver.dll"))
    
        except WindowsError:
    
            raise WebDriverException("Unable to load the IEDriver.dll component")

    最后在执行的时候发现打印如下:

    E:project
    yan_softpy_depotxxx_reaper_browsersrcuildpyi.win32xxx_reape
    
    routPYZ1.pyzwin32IEDriver.dll
    
    Traceback (most recent call last):
    
      File "<string>", line 352, in <module>
    
      File "<string>", line 336, in main
    
      File "<string>", line 243, in __init__
    
      File "E:project
    yan_softpy_depotxxx_reaper_browsersrcuildpyi.win32xxx_reaperoutPYZ1.pyz/browser_man_lib", line 44, in __init__
    
      File "E:project
    yan_softpy_depot
    sm_reaper_browsersrcuildpyi.win32xxx_reaperoutPYZ1.pyz/selenium.webdriver.ie.webdriver", line 61, in __init__
    
    selenium.common.exceptions.WebDriverException: Message: 'Unable to load the E:\
    
    project\ryan_soft\py_depot\xxx_reaper_browser\src\build\pyi.win32\xxx_r
    
    eaper\outPYZ1.pyz\win32\IEDriver.dll'

    说明执行程序试图找到outPYZ1.pyz\win32\IEDriver.dll,这显然是不可能的。所以需要修改一下路径,先尝试用绝对路径,代码修改如下,并且将

    D:Python27Libsite-packagesselenium-2.20.0-py2.7.eggseleniumwebdriveriewin32 IEDriver.dll

    复制到d:\IEDriver.dll。重新编译打包。

    dll_tmp_path = os.path.join("d:\IEDriver.dll")
    
    try:
    
        self.iedriver = CDLL(dll_tmp_path)
    
    except WindowsError:
    
        try:
    
            self.iedriver = CDLL(os.path.join(os.path.dirname(__file__),"x64", "IEDriver.dll"))
    
        except WindowsError:
    
            raise WebDriverException("Unable to load the IEDriver.dll component")

    这下问题解决了。当时使用相对路径肯定是不行的,所以采用相对路径测试。

    将代码改为

    dll_tmp_path = os.path.join("IEDriver.dll")
    
    try:
    
        self.iedriver = CDLL(dll_tmp_path)
    
    except WindowsError:
    
        try:
    
            self.iedriver = CDLL(os.path.join(os.path.dirname(__file__),"x64", "IEDriver.dll"))
    
        except WindowsError:
    
            raise WebDriverException("Unable to load the IEDriver.dll component")

    然后将IEDriver.dll复制到调用打包后exe的路径,而不是exe的路径。区别在于,如果是命令行在其他目录调用exe,那么IEDriver.dll需要复制到那个路径。如果windows下双击,当然IEDriver.dll就是需要和exe同一个目录下。最干脆的解决方法是放到一个系统path能找到的地方,不管如何执行exe都没有问题。

    3.1     注意事项

    使用Selenium应用获取网站信息的时候,最好与网站官方有合作关系,否则在访问频率上需要格外注意。如果网站没有健全的后台系统,无节制的快速访问,有可能致使网站崩溃,或者IP地址及账户被官方记录,列入访问黑名单。

    3.2     其他爬虫框架

    下面简单介绍几种其他Python中常用的爬虫框架。

    3.2.1  内置模块

    Python内置的urllib和urllib2可以实现简单的request请求,获取服务器的反馈数据。

    Post网页:

    import urllib
    
    import urllib2
    
        postdata=urllib.urlencode({
    
            'username':'psstby',
    
            'password':'by201109'
    
        })
    
       
    
        req = urllib2.Request(
    
        url = 'http://hwrd.zhaopin.com/loginmgr/loginproc.asp',
    
        data = postdata
    
    )
    
    urllib2.urlopen(req).read()

    3.2.2  Scrapy

        Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。

        Scrapy 使用 Twisted这个异步网络库来处理网络通讯,架构清晰,并且包含了各种中间件接口,可以灵活的完成各种需求。

          官网地址:

    https://scrapy.org/

    3.2.3  PySpider

    PySpider 是一个非常方便并且功能强大的爬虫框架,支持多线程爬取、JS动态解析,提供了可操作界面、出错重试、定时爬取等等的功能,使用非常人性化。

    官网地址:

    http://www.pyspider.cn/

    开源托管地址:

    https://github.com/binux/pyspider/

    作者|lurayvis撰写初稿,fhk精美更新

    Python精确指南-第三章selenium和爬虫.pdf

    来源:华为云社区  作者:lurayvis

  • 相关阅读:
    json参数http post请求
    获取文本的节点数据
    mongodb robo3t 查询所有 更改固定的50一页
    mongdb 更新字段类型
    数据库表的统计表更新 解决Sql Timeout 时间已到的问题
    html背景图圆角图片设置方法
    abp.vnext vue 跨域设置
    Springboot结合ESAPI——配置XSS过滤
    centos docker安装rabbitmq
    JAVA byte[]转String 中文问题
  • 原文地址:https://www.cnblogs.com/2020-zhy-jzoj/p/13165708.html
Copyright © 2020-2023  润新知