由於公司須要爬取淘寶的店鋪商品列表,因此研究了下,最後結果是失敗的,技術不行沒辦法,作一個記錄,等待之後有大神搞定。html
1、selenium的使用java
引入jar包 web
<dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-server</artifactId> </dependency>
同時呢 還須要一個chromeDriver.exe,直接放在項目的目錄下,spring
先說遇到的問題吧,代碼在最後附上。淘寶的店鋪商品列表須要登陸狀態下才能看到,這就須要有登陸狀態,有了登陸狀態,仍是有問題, 當你打開爬取的店鋪頁面,盡在頁面上採用點擊的方式進行翻頁,3-5秒這樣一個頻率是沒問題的,若是你以10秒或10秒一下這樣一個頻率去操做打開不一樣的店鋪頁面爬取數據,會出現頻繁訪問的彈窗,10秒到15秒隨機等待的話,我試了試貌似能支持好久,這個彈窗須要手動解除滑塊,問題卡就卡在滑塊上了,不管是登錄的滑塊,仍是頻繁訪問的滑塊,用驅動器操做都過不了,我也嘗試了滑塊動做的時候速度變化,模擬人手動滑動也不行,並且在測試的時候還發現了另外一個問題,滑塊出現的次數多了,會出現封號,甚至封ip的現象。chrome
一、滑塊解決不了 , 二、滑塊頻繁出現後,會封號甚至封ipwindows
這兩個要命的問題直接給我弄崩潰了,可能解決滑塊仍是解決不了問題,方向可能就是錯的,應該想辦法讓他不出滑塊 ,說一下先後過程吧。瀏覽器
開始的時候想用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; } }
上面是啓動瀏覽器打開登錄頁面,這個時候找到淘寶有三個登錄,一個是天貓的,一個是淘寶的 ,還有一個是淘寶登錄頁裏面有個 微博登錄的窗口,淘寶和天貓的都有滑塊,而微博登錄的窗口是圖片驗證碼,若是有道友對圖片驗證碼有信心能夠去試試。微信
這時候用人工掃碼登錄,可是爬取的時候發現頻繁訪問窗口, 嘗試解鎖發現過不去,這個時候發現換個帳號就不會出現解鎖彈窗,就想着若是出現彈窗就 換個帳號從新登陸,這就須要解決自動登陸,事實證實本身太年輕,想的太簡單了,登陸頁面也有滑塊,session
可是發現微博登陸淘寶的頁面是驗證碼,能夠嘗試下。
先貼擬人滑塊登錄的代碼吧
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。
如今想了想,這條路可能就不對,固然我技術低微,說不定有大神能解決。
未去實踐的想法
一、以爲應該想辦法讓他不出滑塊,找到一個合適的策略將每一個服務器的爬取頻率儘量的放大, 這樣也許能知足需求,由於一個店鋪頁面內,你僅僅是點擊翻頁,是問題不大的,個人業務需求是個人頁面和淘寶頁面展現同樣的商品,用戶點擊翻頁,我去淘寶爬取數據,那我能夠一次性的多拿幾頁,好比拿10頁,儘量的給服務器創造一個最大的爬取間隔,我的感受只要超過10秒,頗有可能長時間不出現彈窗。
二、能夠嘗試在app端作點事情,不知道移動端有沒有滑塊,固然也須要解決登陸問題。
-------------------------------------------------------------------------------
2019-11-05,我擦,終於搞通了爬取店鋪商品信息的問題了,太他媽麻煩了,有須要的能夠加微信 (yxrg15266767073)。