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

举报
lurayvis 发表于 2017/12/05 09:15:37 2017/12/05
【摘要】 3 Selenium3.1 介绍网络爬虫在互联网领域有着广泛的应用。Selenium是一个页面自动化控制框架。能够模拟实际操作,自动化获取网站提供的页面资源信息。Selenium能够自定义页面操作的行为,按照用户指定的跳转路径访问,具有实现跟实际用户一样填充信息、提交表单请求的能力,适用于专门网站特定信息的获取。比如:特定图片网站图片的获取,购物网站商品信息的获取等等。3.2

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:\python\sdk\tesseract\tesseract-ocr-3.01-win32-portable\Tesseract-ocr\Tesseract.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:\python\sdk\tesseract\tesseract-ocr-3.01-win32-portable\Tesseract-ocr\Tesseract.exe big_test4.png ret.txt -psm 7 char_digits

新建文件:

D:\python\sdk\tesseract\tesseract-ocr-3.01-win32-portable\Tesseract-ocr\tessdata\configs\char_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\ryan_soft\py_depot\xxx_reaper_browser\src\build\pyi.win32\xxx_reape

r\outPYZ1.pyz\win32\IEDriver.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\ryan_soft\py_depot\xxx_reaper_browser\src\build\pyi.win32\xxx_reaper\outPYZ1.pyz/browser_man_lib", line 44, in __init__

  File "E:\project\ryan_soft\py_depot\rsm_reaper_browser\src\build\pyi.win32\xxx_reaper\outPYZ1.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:\Python27\Lib\site-packages\selenium-2.20.0-py2.7.egg\selenium\webdriver\ie\win32\ 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精美更新

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200