zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    .net core读取appsettings.config中文乱码问题
    vs2017错误:当前页面的脚本发生错误
    VS Code中无法识别npm命令
    Visual Studio报错/plugin.vs.js,行:1074,错误:缺少标识符、字符串或数字
    记录一次在生成数据库服务器上出现The timeout period elapsed prior to completion of the operation or the server is not responding.和Exception has been thrown by the target of an invocation的解决办法
    Java集合框架
    java hash表
    Java Dictionary 类存储键值
    java数据结构 栈stack
    java封装
  • 原文地址:https://www.cnblogs.com/2020-zhy-jzoj/p/13165708.html
Copyright © 2011-2022 走看看