因为公司需要爬取淘宝的店铺商品列表,所以研究了下,最后结果是失败的,技术不行没办法,做一个记录,等待以后有大神搞定。
一、selenium的使用
引入jar包
<dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-server</artifactId> </dependency>
同时呢 还需要一个chromeDriver.exe,直接放在项目的目录下,
先说遇到的问题吧,代码在最后附上。淘宝的店铺商品列表需要登录状态下才能看到,这就需要有登录状态,有了登录状态,还是有问题, 当你打开爬取的店铺页面,尽在页面上采用点击的方式进行翻页,3-5秒这样一个频率是没问题的,如果你以10秒或10秒一下这样一个频率去操作打开不同的店铺页面爬取数据,会出现频繁访问的弹窗,10秒到15秒随机等待的话,我试了试貌似能支持很久,这个弹窗需要手动解除滑块,问题卡就卡在滑块上了,无论是登陆的滑块,还是频繁访问的滑块,用驱动器操作都过不了,我也尝试了滑块动作的时候速度变化,模拟人手动滑动也不行,而且在测试的时候还发现了另一个问题,滑块出现的次数多了,会出现封号,甚至封ip的现象。
1、滑块解决不了 , 2、滑块频繁出现后,会封号甚至封ip
这两个要命的问题直接给我弄崩溃了,可能解决滑块还是解决不了问题,方向可能就是错的,应该想办法让他不出滑块 ,说一下前后过程吧。
开始的时候想用2台或多台windows服务器做这个爬取的服务器,登陆靠人工,依靠mq发送消息,只要之后程序能保持登陆状态就行,这个时候需要解决滑块问题,
@Slf4j public class BootStrap { public static ChromeDriver driver; static { try { //启动浏览器 getDriver(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { //启动消息监听 // start(); } @SuppressWarnings("resource") public static void start() throws InterruptedException { new ClassPathXmlApplicationContext("spring-activemq.xml"); //等待消息发送 Thread.sleep(1000); while(true){ Thread.sleep(10 * 60 * 1000); log.info("keep session ..."); driver.get("https://www.tmall.com/"); } } /** * 获取 ChromeDriver * @throws InterruptedException */ private static void getDriver() throws InterruptedException{ String os = System.getProperty("os.name"); if (os.toLowerCase().startsWith("win")) { System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "\\chromedriver_win32\\chromedriver.exe"); } else { System.setProperty("webdriver.chrome.driver", "/usr/bin/chromedriver"); } ChromeOptions options = new ChromeOptions(); // 关闭界面上的---Chrome正在受到自动软件的控制 options.addArguments("--disable-infobars"); // 允许重定向 options.addArguments("--disable-web-security"); // 最大化 options.addArguments("--start-maximized"); options.addArguments("--no-sandbox"); List<String> excludeSwitches = Lists.newArrayList("enable-automation"); options.setExperimentalOption("excludeSwitches", excludeSwitches); driver = new ChromeDriver(options); //响应等待时间 driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS); driver.get("https://login.tmall.com"); Thread.sleep(1000); while(true) { if(isLogin()){ break; } Thread.sleep(2000); } } /** * 判断是否登录 * @return boolean */ private static boolean isLogin(){ Document doc = Jsoup.parse(driver.getPageSource()); if(doc.text().contains("Hi,") || doc.text().contains("Hi!")) { return true; } return false; } }
上面是启动浏览器打开登陆页面,这个时候找到淘宝有三个登陆,一个是天猫的,一个是淘宝的 ,还有一个是淘宝登陆页里面有个 微博登陆的窗口,淘宝和天猫的都有滑块,而微博登陆的窗口是图片验证码,如果有道友对图片验证码有信心可以去试试。
这时候用人工扫码登陆,但是爬取的时候发现频繁访问窗口, 尝试解锁发现过不去,这个时候发现换个账号就不会出现解锁弹窗,就想着如果出现弹窗就 换个账号重新登录,这就需要解决自动登录,事实证明自己太年轻,想的太简单了,登录页面也有滑块,
但是发现微博登录淘宝的页面是验证码,可以尝试下。
先贴拟人滑块登陆的代码吧
public class Test { public static ChromeDriver driver; public static List<Integer> back_tracks = new ArrayList<>(); static { try { //启动浏览器 getDriver(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException, InterruptedException { // start(); } /** * 获取 ChromeDriver * @throws InterruptedException */ private static void getDriver() throws InterruptedException{ String os = System.getProperty("os.name"); if (os.toLowerCase().startsWith("win")) { System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "\\chromedriver_win32\\chromedriver.exe"); } else { System.setProperty("webdriver.chrome.driver", "/usr/bin/chromedriver"); } ChromeOptions options = new ChromeOptions(); // 关闭界面上的---Chrome正在受到自动软件的控制 options.addArguments("--disable-infobars"); // 允许重定向 options.addArguments("--disable-web-security"); // 最大化 options.addArguments("--start-maximized"); options.addArguments("--no-sandbox"); List<String> excludeSwitches = Lists.newArrayList("enable-automation"); options.setExperimentalOption("excludeSwitches", excludeSwitches); driver = new ChromeDriver(options); driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS); driver.get("https://login.taobao.com/member/login.jhtml"); WebElement itemDiv = driver.findElement(By.id("J_Quick2Static")); itemDiv.click(); WebElement tpl_username_1 = driver.findElement(By.id("TPL_username_1")); WebElement TPL_password_1 = driver.findElement(By.id("TPL_password_1")); WebElement nc_1_n1z = driver.findElement(By.id("nc_1_n1z")); WebElement usernameFeld = driver.findElement(By.className("username-field")); usernameFeld.click(); tpl_username_1.sendKeys("12"); TPL_password_1.click(); TPL_password_1.sendKeys("12"); Actions actions = new Actions(driver); actions.clickAndHold(nc_1_n1z); List<Double> tacks = getTacks(); Random ra =new Random(); for (Double tack : tacks) { int a = tack.intValue(); int yoffset_random = ra.nextInt(6) - 2; actions.moveByOffset(a, yoffset_random).perform(); Thread.sleep(20+ ra.nextInt(20)); } Thread.sleep(200+ ra.nextInt(400)); for (Integer back_track : back_tracks) { int yoffset_random = ra.nextInt(4) - 2; actions.moveByOffset(back_track, yoffset_random).perform(); } int x = ra.nextInt(3) - 4; int y = ra.nextInt(4) - 2; actions.moveByOffset(x, y).perform(); x = ra.nextInt(2) +1; y = ra.nextInt(4) - 2; actions.moveByOffset(x, y).perform(); Thread.sleep(200+ ra.nextInt(400)); while(true) { if(isLogin()){ break; } Thread.sleep(2000); } } /** * 判断是否登录 * @return boolean */ private static boolean isLogin(){ Document doc = Jsoup.parse(driver.getPageSource()); if(doc.text().contains("淘宝账户登录") ) { return false; } return true; } /** * 改变拖拽速度,更加拟人化 * @return */ public static List<Double> getTacks(){ List<Double> list = new ArrayList<>(); Random ra =new Random(); int length = 258; double mid1 = Math.round(length *( Math.random() * 0.1 + 0.1)); double mid2 = Math.round(length *( Math.random() * 0.1 + 0.65)); double mid3 = Math.round(length *( Math.random() * 0.04 + 0.84)); double current = 0; double a = 0; double v = 0; double t = 0.2; while (current < length){ if(current < mid1){ a = ra.nextInt(5)+ 10; }else if(current < mid2){ a = ra.nextInt(10) + 30; }else if(current < mid3){ a = -70; }else{ a = ra.nextInt(7)-25; } double v0 = v; v = v0 + a * t; v = v > 0 ? v : 0; double move = v0 * t + ((a * (t * t))/2); move = Math.round(move > 0 ? move : 1); current += move; list.add(move); } double out_range = length - current; if (out_range < -8){ int sub = ((Double)(out_range + 8)).intValue(); back_tracks.add(-1); back_tracks.add(sub); back_tracks.add(-3); back_tracks.add(-1); back_tracks.add(-1); back_tracks.add(-1); back_tracks.add(-1); }else if(out_range < -2){ int sub = ((Double)(out_range + 3)).intValue(); back_tracks.add(-1); back_tracks.add(-1); back_tracks.add(sub); } System.out.println(list); return list; } }
下面贴验证码识别的, 我用的是百度的图片识别,基本是没有能正确识别的,其他代码差不多,我就贴截图和识别的代码。
/** * 获取 ChromeDriver * @throws InterruptedException */ private static void getDriver() throws InterruptedException{ String os = System.getProperty("os.name"); if (os.toLowerCase().startsWith("win")) { System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "\\chromedriver_win32\\chromedriver.exe"); } else { System.setProperty("webdriver.chrome.driver", "/usr/bin/chromedriver"); } ChromeOptions options = new ChromeOptions(); // 关闭界面上的---Chrome正在受到自动软件的控制 options.addArguments("--disable-infobars"); // 允许重定向 options.addArguments("--disable-web-security"); // 最大化 options.addArguments("--start-maximized"); options.addArguments("--no-sandbox"); List<String> excludeSwitches = Lists.newArrayList("enable-automation"); options.setExperimentalOption("excludeSwitches", excludeSwitches); driver = new ChromeDriver(options); //响应等待时间 driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS); // driver.get("https://login.tmall.com"); driver.get("https://login.taobao.com/member/login.jhtml"); WebElement itemDiv = driver.findElement(By.id("J_Quick2Static")); itemDiv.click(); WebElement element = driver.findElement(By.className("weibo-login")); element.click(); Thread.sleep(1000); //微博登陆 Document doc = Jsoup.parse(driver.getPageSource()); if(doc.text().contains("快速登录")) { WebElement spanBg = driver.findElementByCssSelector(".btn_tip > .W_btn_g > span"); spanBg.click(); }else{ WebElement username = driver.findElement(By.name("username")); WebElement password = driver.findElement(By.name("password")); Actions action = new Actions(driver); action.click(username).perform(); username.clear(); username.sendKeys(""); action.click(password).perform(); password.clear(); password.sendKeys(""); Thread.sleep(2000); }
//截取整个页面 File screenShot = driver.getScreenshotAs(OutputType.FILE); try { String name = "C:\\Users\\Administrator\\Desktop\\image\\"+System.currentTimeMillis()+".jpg"; FileUtils.copyFile(screenShot,new File(name));
//找到验证码位置,截取验证码 WebElement code = driver.findElementByCssSelector(".code > img"); Point location = code.getLocation(); Dimension size = code.getSize(); Image img = Toolkit.getDefaultToolkit().getImage(name); BufferedImage read = toBufferedImage(img); BufferedImage subimage = read.getSubimage(location.getX(), location.getY(), size.getWidth(), size.getHeight()); String newName = "C:\\Users\\Administrator\\Desktop\\image\\"+System.currentTimeMillis()+".jpg"; System.out.println(newName); ImageIO.write(subimage, "jpg", new File(newName)); //使用百度图片识别 AipOcr client = new AipOcr("11", "22", "33"); client.setConnectionTimeoutInMillis(2000); client.setSocketTimeoutInMillis(60000); HashMap<String, String> param = new HashMap<>(16); param.put("detect_direction", "true"); param.put("probability", "true"); JSONObject res = client.basicAccurateGeneral(newName, param); System.out.println(res.toString(2)); } catch (IOException e) { e.printStackTrace(); } Thread.sleep(1000); while(true) { if(isLogin()){ break; } Thread.sleep(2000); } } public static BufferedImage toBufferedImage(Image image) { if (image instanceof BufferedImage) { return (BufferedImage)image; } image = new ImageIcon(image).getImage(); BufferedImage bimage = null; GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); try { int transparency = Transparency.OPAQUE; GraphicsDevice gs = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = gs.getDefaultConfiguration(); bimage = gc.createCompatibleImage( image.getWidth(null), image.getHeight(null), transparency); } catch (HeadlessException e) { // The system does not have a screen } if (bimage == null) { // Create a buffered image using the default color model int type = BufferedImage.TYPE_INT_RGB; bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type); } Graphics g = bimage.createGraphics(); g.drawImage(image, 0, 0, null); g.dispose(); return bimage; }
这里我做了整个页面的截图 ,然后又把验证码单独截取出来,因为验证码的这张图片它不正经。
最后验证码识别成功率很低,正想换个识别验证码的方法,结果这个时候发现了封ip的现象,这个时候我双手离开键盘了,mmp。
现在想了想,这条路可能就不对,当然我技术低微,说不定有大神能解决。
未去实践的想法
1、觉得应该想办法让他不出滑块,找到一个合适的策略将每个服务器的爬取频率尽可能的放大, 这样也许能满足需求,因为一个店铺页面内,你仅仅是点击翻页,是问题不大的,我的业务需求是我的页面和淘宝页面展示一样的商品,用户点击翻页,我去淘宝爬取数据,那我可以一次性的多拿几页,比如拿10页,尽可能的给服务器创造一个最大的爬取间隔,个人感觉只要超过10秒,很有可能长时间不出现弹窗。
2、可以尝试在app端做点事情,不知道移动端有没有滑块,当然也需要解决登录问题。