最近有个项目需要爬取“国家企业信用信息公示系统”的数据,在该网站点击搜索按钮时,会弹出极验(geetest)的拖动式验证码。 遂一番google之,发现果然有哥们已经破解了这套验证码系统,甚至放出源码来了。学以致用。 原理很简单,首先定位缺口的位置,然后驱动浏览器将按钮移动到该位置。至于如何定位缺口位置,其实这个验证图是分上下两张的,底图是完整图,上一层则是有缺口的图,另外这两张图都是打散的,需要先还原出原图,然后再逐像素对比两张图片就可以得到缺口位置。移动按钮看似简单,但如果只是简单的将按钮设置到目标位置,极验后台会返回“怪物吃了拼图”,因为该验证码系统会将按钮的移动轨迹提交到极验后台,并验证该轨迹是否像一个人类的行为,所以我们需要尽可能模拟出人类的拖动行为。 代码示例: # -*- coding: utf-8 -*- import logging import time import random import re import requests import urlparse from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains from PIL import Image from io import BytesIO """ 程序目的: 访问国家企业信用信息公示系统(http://www.gsxt.gov.cn/index.html), 输入查询关键字, 破解弹出的极验验证码系统(geetest)并搜索,最后获取搜索结果。 """ VERSION = "1.0" CONFIG = { 'log_format': "%(asctime)s pid[%(process)d] %(levelname)7s %(name)s.%(funcName)s - %(message)s", 'save_temp_file': False, } class Gsxt(object): """""" def __init__(self, driver): """构造函数""" super(Gsxt, self).__init__() self.logger = logging.getLogger(self.__class__.__name__) self.logger.setLevel(logging.DEBUG) self.logger.debug("Init Gsxt instance") self.browser = None self.__setup_browser(driver) pass def __del__(self): """析构函数""" self.logger.debug("Del Gsxt instance") if self.browser is not None: self.browser.quit() pass def __setup_browser(self, driver): """装载浏览器驱动""" self.logger.debug("Setup selenium webdriver") driver = driver.lower() if "phantomjs" == driver: self.browser = webdriver.PhantomJS() elif "chrome" == driver: self.browser = webdriver.Chrome() elif "firefox" == driver: raise Exception("请不要使用firefox,因为geckodriver暂时有点功能不全!凸(-。-;") else: raise Exception("不识别的浏览器驱动") # 设置打开页面超时时间(但这对firefox的geckodriver 0.13.0版本无效,不知道后续改进没有) self.browser.set_page_load_timeout(8) # 设置查询dom的隐式等待时间(影响find_element_xxx,find_elements_xxx) self.browser.implicitly_wait(10) pass def search(self, keyword): """ Args: keyword: 要搜索的关键字 Returns: """ if type(keyword) is str: keyword = keyword.decode('utf-8') self.logger.debug(u"准备搜索: %s", keyword) # 打开搜索页面 self.browser.get("http://www.gsxt.gov.cn/index.html") # 输入关键字 dom_input_keyword = self.browser.find_element_by_id("keyword") dom_input_keyword.send_keys(keyword) time.sleep(random.uniform(0.3, 1.0)) # 点击搜索按钮 dom_btn_query = self.browser.find_element_by_id("btn_query") dom_btn_query.click() time.sleep(random.uniform(0.8, 1.5)) flag_success = False while not flag_success: # 下载完整的验证图 image_full_bg = self.get_image("gt_cut_fullbg_slice") # 下载有缺口的验证图 image_bg = self.get_image("gt_cut_bg_slice") # 对比两张验证图,获得缺口的位置(x_offset) diff_x = self.get_diff_x(image_full_bg, image_bg) self.logger.debug(u"缺口位置x_offset = %s", diff_x) # 根据缺口位置计算移动轨迹 track = self.get_track(diff_x) # 移动滑块 result = self.simulate_drag(track) # 判断滑动验证的结果 if u"通过" in result: flag_success = True pass elif u"吃" in result: self.logger.debug(u"准备重试") time.sleep(5) pass else: self.logger.warn(u"未知结果") break pass # 获取搜索结果 if flag_success: time.sleep(random.uniform(0.8, 1.5)) # do sth pass pass def get_image(self, class_name): """ 下载并还原极验的验证图 Args: class_name: 验证图所在的html标签的class name Returns: 返回验证图 Errors: IndexError: list index out…
python的浏览器“驱动”库:selenium
上两周的时候,陈怡同学问我怎么通过程序自动化截屏浏览器页面,她说有篇论文用的是python与selenium。当时我的心理活动是这样的:“卧槽selenium是什么鬼,女博士果然是见多识广。” =。=# 然后查了一下,selenium大概可以理解成一个浏览器模拟器(或者实际上是浏览器驱动器,Selenium WebDriver),selenium提供多种编程语言的接口让我们可以通过程序来驱动本地浏览器,并执行我们想要的操作。 以陈怡同学的需求为例,就是通过python调用selenium接口来驱动我的Firefox,打开xx网站,截图保存。 我的环境: python 2.7.6,selenium 2.48.0 安装selenium库: 参考https://pypi.python.org/pypi/selenium #从selenium库引入webdriver类 from selenium import webdriver #新建一个browser对象(驱动firefox浏览器) browser = webdriver.Firefox() #打开news.baidu.com页面 browser.get('http://news.baidu.com/') #保存截图 browser.save_screenshot('capture.png') #关闭浏览器 browser.close() PS:既然selenium可以驱动浏览器来干活,那么先前我做python爬虫的时候,遇到整个页面都是js动态生成的情况,我当时就束手无策了,因为之前只用过一个叫做beautiful soup的python库,他只能解析html文档,无法执行js脚本来动态更新。那么有了selenium,不就可以直接叫浏览器渲染出来页面,再将整个页面的dom交给beautiful soup嘛。 又ps:早期版本的firefox好像可以直接驱动,后面版本的需要加geckodriver,驱动chrome则需要chromedriver。 后面发现这个selenium+beautiful soup的想法其实是有完全没必要的!首先,beautiful soup对象的构造只能读取html格式的文件或字符串,如果硬要跟selenium结合起来的话,只能将selenium webdriver的page_source(页面的html源码)传递给beautiful soup,这还不如直接用urllib来的快速;其次,selenium webdriver自己就已经解析出页面的dom模型,可以通过selenium webdriver的API来定位、编辑网页元素,对页面执行各种操作,根本不用再借助其他工具。下面我们尝试一下使用selenium驱动Firefox来打开百度,并搜索selenium关键字。 from selenium import webdriver driver = webdriver.Firefox() driver.get("https://www.baidu.com") print driver.title, driver.current_url #百度首页搜索输入框的元素id='kw',搜索按钮的元素id='su' driver.find_element_by_id('kw').send_keys('selenium') driver.find_element_by_id('su').click() print driver.title, driver.current_url #driver.close() headless browser 在上面的两个例子中我们驱动的浏览器都是Firefox,每次运行程序都会打开Firefox窗口。这在做测试的时候是很有用的,但如果只是做爬虫应用,那么每次都打开浏览器窗口并渲染网页其实是浪费时间的。所谓的headless browser是指这样一种浏览器,它没有图形化界面,主要用来做web的自动化测试以及爬虫应用。 selenium webdriver现在支持有两款headless browser:PhantomJS、HtmlUnit。 以python+selenium+phantomjs为例,改写上面百度搜索的例子: # PhantomJS - baidu from selenium import webdriver driver = webdriver.PhantomJS() driver.set_window_size(1440, 900) driver.get("https://www.baidu.com") print driver.title, driver.current_url driver.find_element_by_id('kw').send_keys('selenium') element = driver.find_element_by_id('su') element.click() print driver.title, driver.current_url driver.quit() 注意上面代码的第5行driver.set_window_size(1440, 900),我们给无窗口浏览器phantomjs设置了窗口大小,这看起来很诡异。但如果注释掉这行的话,该程序会在后面的element.click()函数中抛出ElementNotVisibleException异常。这不知道该算是phantomjs的bug还是selenium webdriver的bug。 另外,最后一句得改为webdriver.quit()而不是webdriver.close(),否则phantomjs进程还会在后台驻留。 参考: http://selenium-python.readthedocs.org/index.html https://realpython.com/blog/python/headless-selenium-testing-with-python-and-phantomjs/ 还有这篇中文介绍写的不错:https://wizardforcel.gitbooks.io/selenium-webdriver-simple-tutorial/content/