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")
第三方安装包lxml比Python自带的xml模块具有更为强大的解析xml或html的功能,推荐使用。最新的版本是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()
lxml有默认的解析器,但是对于非规则的网页处理非常不好。相当多的网站不是规则的html语法,所以需要选择外部的解析器。
在lxml里调用beautifulsoap。这个处理中文比较好,首选解析器。
3.3.6 中文字符的处理
Ø 基本支持
在源文件的第一行,在任何代码和注释前面,加入如下语句:
#encoding=utf-8
一般需要在写任何中文和代码之前加入这句话。如果先在代码里出现了中文,然后贴这句话,或导致乱码。
Ø 保存中文网页
保存网页数据。通常的网页数据是unicode,在eclispe中运行的时候可以正确的保存,但是单独运行就不行了。错误如下:
使用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截图如下,可以正常识别。
但是用软件自动放大之后,什么都识别不出来,连乱码都没有了,对比下两个图,没什么区别,只是旁边多了一些黑点,猜想是不是因为这些黑点的原因导致认不出来。用工具将黑点全部去掉,结果能正常识别。所以还需要用软件把旁边的黑点crop掉。
CROP的方法
crop_reg = (20, 20, w-20, h-20)
big_pic = big_pic.crop(crop_reg)
Ø 获取验证码图片
参考多篇文章,最可靠的是截屏
查看元素信息如下:
截屏处理:
用图片软件看截屏的图和位置信息,可以看出,尺寸和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两个文件:
解决方法:
用的2.48 selenium,修改两处firefox_profile.py源码文件。
然后把这两个文件复制到单文件的exe同路径下就可以用了。
2.48 selenium代码修改, 都是改了路径为当前路径。
修改为:
'''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打包的结果
这些是包含的文件
打包报错如下:
无论如何找不到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这个异步网络库来处理网络通讯,架构清晰,并且包含了各种中间件接口,可以灵活的完成各种需求。
官网地址:
3.2.3 PySpider
PySpider 是一个非常方便并且功能强大的爬虫框架,支持多线程爬取、JS动态解析,提供了可操作界面、出错重试、定时爬取等等的功能,使用非常人性化。
开源托管地址:
https://github.com/binux/pyspider/
作者|lurayvis撰写初稿,fhk精美更新
- 点赞
- 收藏
- 关注作者
评论(0)