利用Selenium实现交互式WebService接口 --处理验证码

来源:互联网 发布:淘宝能卖视频吗 编辑:程序博客网 时间:2024/05/17 04:07

OverView


写于2017年7月25日,如有不当之处,贵请指导!

  作者现在需要爬取一个某政务型 查询网站,实现WebService接口。本文为了方便叙述,把接口和调用接口的业务系统合并—统称为系统,如果不懂,忽略此句
  简要概括就是:

  1. 系统填一个表单、并将验证码交给用户识别
  2. 用户填完验证码提交后系统继续填写页面表单
  3. 最后将抓取的结果返回给用户。

为了方便大家理解,画流程图如下:

Created with Raphaël 2.1.0客户:扫一扫客户:数据JSON上传系统:接收并填写号码网站:判断号码网站:显示验证码系统:验证码发送客户端客户:填写验证码系统:表单提交用户:获取结果yesnoyesno

一、准备

1、网站交互分析(F12)

  大致了解需求后就是开发的过程,首先你做的就是网页交互分析,请按F12,在Network(网络)那一栏观察分析,务必!!!

如果能直接找到URL接口的,千万不要用Selenium方案!

F12
以此政务网站为例,第一、第三行就系统交互:先填写发票代码,网页JS验证代码并Ajax获取验证码。这个验证码是不同颜色字符组成的,填写时会让你选择其中部分颜色的字,提交表单时这里的加密也需要提交。(PS:所以这里我们基本可以判定,必须Selenium)。填写完验证码和其他数据后,我们便可以点击查验。

Created with Raphaël 2.1.0填写发票代码显示验证码填写其他数据+验证码查验

为了加速填写,表单填写采用了复制粘贴。可恰巧的是这个网站表单的个别box有限制粘贴,故采用粘贴与键盘输入结合。

2、系统分析

  结合一开始的需求分析,WebService设计如下:
网站结构

  相比较以往抓取,WebService最大的问题就是将原本线性过程转变成分步的交互过程。所以必须暂存drive,暂存你的操作过程。

  • 线性的抓取过程:写好代码逻辑,一步一步执行抓取并返回结果。
  • Http 交互过程 :一个http请求完成一次逻辑并返回结果,每一次请求都是一次执行。

所以,我们必须将原来的过程断开,在每一次请求时分步执行。

Tips:为了提高响应速度,当验证码发送给用户填写时,后台已经在异步执行其他数据的填写操作了。当用户填好验证码发送至系统时,系统只需补充验证码便可以确认查验了。

二、系统搭建

环境准备

  技术实现采用的Selenium+Java Servlet,目录如下:Tips:Springboot好像不能操作系统的剪切板,故而简单的来。
目录结构

  • 关于selenium+Java基本准备,可以参照此篇文档:Selenium+Java基础 –此处默认大家都熟悉Selenium+Java环境搭建。

三、Coding

1、工具类PageUtil

/** *  * @author minzhou<mingxuan.zhou@outlook.com> * @version 1.0 * Time 20170703 */public class PageUtils {    /**     * 获取IE的WebDriver     *     * @param page     * @return     */    public static WebDriver getWebDriver(String url) {        System.setProperty("webdriver.ie.driver", "D:/Develop/WebCrawler/IEDriverServer_x64_3.4.0/IEDriverServer.exe");        DesiredCapabilities ieCapabilities = DesiredCapabilities.internetExplorer();        ieCapabilities.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS,true);        WebDriver driver = new InternetExplorerDriver();        driver.get(url);        return driver;    }    /**     * 复制粘贴的两种方案,推荐第一个     * @param driver     * @param str     */    public static void CtrlC_V(WebDriver driver,String str) {        StringSelection stringSelection = new StringSelection(str);        Toolkit.getDefaultToolkit().getSystemClipboard()                .setContents(stringSelection, null);        new Actions(driver).sendKeys(Keys.chord(Keys.CONTROL+"v")).perform();    }    public static void keyOperation(String str){        StringSelection stringSelection = new StringSelection(str);        Toolkit.getDefaultToolkit().getSystemClipboard()                .setContents(stringSelection, null);        //系统级粘贴        Robot robot = null;        try {            robot = new Robot();        } catch (AWTException e1) {            e1.printStackTrace();        }        robot.keyPress(KeyEvent.VK_CONTROL);        robot.keyPress(KeyEvent.VK_V);        robot.keyRelease(KeyEvent.VK_V);        robot.keyRelease(KeyEvent.VK_CONTROL);    }}

2、获取验证码ImageServlet

  原始图片的静态jpg文件,刷新后的图片为base64,依此,我们可以根据src的变化判断是否出现验证码,并在验证码出现后获取src,截取相应部分并将base64编码的图片的String以及提示信息发送到客户端即可。

    /**     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)     */    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        HttpSession session=request.getSession();        WebDriver driver = null;        Invoice invoice=new Invoice();        invoice.setInvoiceCode(request.getParameter("invoiceCode"));        invoice.setInvoiceCheckSum(request.getParameter("invoiceCheckSum"));        invoice.setInvoiceDate(request.getParameter("invoiceDate"));        invoice.setInvoiceNum(request.getParameter("invoiceNum"));        String url="https://inv-veri.chinatax.gov.cn/";        //Use session storage drive        if (session.getAttribute("driver") == null) {            driver = PageUtils.getWebDriver(url);            session.setAttribute("driver", driver);//首次访问时,新建dirve对象        }else {            driver =(WebDriver) session.getAttribute("driver");//非首次打开时,获取对象            if (driver.getCurrentUrl()==url) {                driver.navigate().refresh();//URL为目标页,刷新            } else {                driver.navigate().to(url);//不是目标页导航至目标页            }        }        //InvoiceCode==发票代码        WebElement invoiceCodeInput = driver.findElement(By.id("fpdm"));        new Actions(driver).click(invoiceCodeInput).perform();        //new Actions(driver).moveToElement(invoiceCodeInput).click().perform();        //PageUtils.keyOperation(invoice.getInvoiceCode());        PageUtils.CtrlC_V(driver, invoice.getInvoiceCode());        //waiting for verification information        try {            new WebDriverWait(driver, 500).until(new ExpectedCondition<Boolean>(){                            public Boolean apply(WebDriver d){                                return d.findElement(By.id("yzm_img"))                                        .getAttribute("src")                                        .contains("base64");                            }                        });        } catch (Exception e) {        }        WebElement codeImage = driver.findElement(By.id("yzm_img"));        WebElement codeTips = driver.findElement(By.id("yzminfo"));        try {            request.setAttribute("codeImage", codeImage);            request.setAttribute("codeTips", codeTips);            /*            // convert to json            Gson gson=new Gson();            Map<String,String> map=new HashMap<>();            map.put("codeImage", codeImage.getAttribute("src")+"");            map.put("codeTips", codeTips.getText()+"");            String json = gson.toJson(map);            //json信息返回            PrintWriter out = response.getWriter();            out.println(json);            out.flush();            out.close();*/        } catch (JSONException e) {            e.printStackTrace();        }        request.getRequestDispatcher("/views/image.jsp").forward(request, response);    }

3、填写验证码并解析

从session中取出drive并继续执行操作,并解析最终页面。

作者在操作时候发现不同浏览器对于selenium命令执行的结果是不一致的。比如IE,根据id定位对象的时候居然可以跨iframe,但是同样的代码chrome就不行。由于页面复杂,并没有注意到iframe存在,故而检查了好久。所以在此给大家提示:确认定位正确而找不到元素,可以利用F12在源码中搜索是否有iframe存在

    /**     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)     */    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        request.setCharacterEncoding("UTF-8");        response.setCharacterEncoding("UTF-8");        HttpSession session = request.getSession();        WebDriver driver = (WebDriver) session.getAttribute("driver");        Invoice invoice = (Invoice) session.getAttribute("invoice");        //获取前台传来的验证码        invoice.setCode(request.getParameter("checkNum").trim());        //selenium操作        WebElement CodeInput = driver.findElement(By.id("yzm"));        new Actions(driver).moveToElement(CodeInput).click().perform();        PageUtils.CtrlC_V(driver,invoice.getCode());        WebElement InvoiceSubmit = driver.findElement(By.id("checkfp"));        //等待按钮可点        new WebDriverWait(driver, 1000).until(new ExpectedCondition<Boolean>(){            public Boolean apply(WebDriver d){                return d.findElement(By.id("checkfp"))                        .getAttribute("style")                        .contains("inline-block");            }        });        //移动到上面点击,防止页面CSS遮挡        new Actions(driver).click(InvoiceSubmit).perform();        new WebDriverWait(driver, 1500).until(ExpectedConditions.presenceOfElementLocated(By.tagName("dialog")));        driver.switchTo().frame("dialog-body");        String example=driver.findElement(By.id("sbbh_dzfp")).getText();        System.out.println(example);    }

至此,页面抓取完成。
关于此次抓取,三种浏览器都试了,就速度而言,IE最慢,非常不推荐使用。

阅读全文
2 0